122 lines
5.8 KiB
C
122 lines
5.8 KiB
C
|
|
#pragma once
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Стихии урона и сопротивления.
|
|||
|
|
//
|
|||
|
|
// Зачем они вообще нужны в игре про бесконечный спуск: сила отряда растёт
|
|||
|
|
// медленнее, чем угроза (закон масштаба, docs/20-gdd.md), и разрыв обязан
|
|||
|
|
// закрываться ЗНАНИЕМ. Стихии — первый и главный канал такого знания: тварь,
|
|||
|
|
// на которую взяли верное оружие, умирает втрое быстрее, чем от неверного, и
|
|||
|
|
// цифра эта не растёт от прокачки.
|
|||
|
|
//
|
|||
|
|
// Ручных действий игра не заводит (спек 5): стихия — свойство ОРУЖИЯ, а не
|
|||
|
|
// кнопка. Всё решается тем, что отряд взял вниз.
|
|||
|
|
//
|
|||
|
|
// Здесь только арифметика урона. Статусы (горение, оледенение) и реакции между
|
|||
|
|
// стихиями — следующая веха, они живут поверх этого файла, а не внутри него.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
#include <algorithm>
|
|||
|
|
#include <cstdint>
|
|||
|
|
|
|||
|
|
enum class Element : uint8_t
|
|||
|
|
{
|
|||
|
|
KIN = 0, // кинетика: базовый урон, режется бронёй
|
|||
|
|
INC, // жар
|
|||
|
|
CRY, // холод
|
|||
|
|
TOX, // кислота
|
|||
|
|
ARC, // дуга
|
|||
|
|
RAD, // радиация
|
|||
|
|
VOID, // пустота
|
|||
|
|
COUNT,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
constexpr int ELEMENT_COUNT = int(Element::COUNT);
|
|||
|
|
|
|||
|
|
inline const char* ElementName(Element e)
|
|||
|
|
{
|
|||
|
|
switch (e)
|
|||
|
|
{
|
|||
|
|
case Element::KIN: return "KIN";
|
|||
|
|
case Element::INC: return "INC";
|
|||
|
|
case Element::CRY: return "CRY";
|
|||
|
|
case Element::TOX: return "TOX";
|
|||
|
|
case Element::ARC: return "ARC";
|
|||
|
|
case Element::RAD: return "RAD";
|
|||
|
|
default: return "VOID";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Из чего состоит удар. Доли, а не абсолютные числа: урон оружия остаётся одним
|
|||
|
|
// числом, а стихия говорит, КАК он делится. Иначе каждое оружие пришлось бы
|
|||
|
|
// балансировать семь раз.
|
|||
|
|
//
|
|||
|
|
// Сумма долей всегда 1: доля, не попавшая ни в одну стихию, — это кинетика.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
struct ElementMix
|
|||
|
|
{
|
|||
|
|
float share[ELEMENT_COUNT] = {1.0f, 0, 0, 0, 0, 0, 0};
|
|||
|
|
|
|||
|
|
// Смесь «столько-то одной стихии, остальное кинетика». Ровно так задаётся
|
|||
|
|
// почти всё оружие: чистые стихии — редкость и признак поздней глубины.
|
|||
|
|
static ElementMix Of(Element e, float part)
|
|||
|
|
{
|
|||
|
|
ElementMix m;
|
|||
|
|
const float p = std::min(std::max(part, 0.0f), 0.85f);
|
|||
|
|
m.share[int(Element::KIN)] = 1.0f - p;
|
|||
|
|
m.share[int(e)] = (e == Element::KIN) ? 1.0f : p;
|
|||
|
|
return m;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Полностью убрать кинетику нельзя (потолок 0.85 выше). Оружие без
|
|||
|
|
// физического урона перестало бы работать по целям, у которых нет
|
|||
|
|
// сопротивлений вовсе, — а такие есть на каждом этаже.
|
|||
|
|
float Share(Element e) const { return share[int(e)]; }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Профиль сопротивлений вида. Доля поглощённого урона: 0.5 — вдвое меньше,
|
|||
|
|
// −0.4 — на 40% больше (уязвимость).
|
|||
|
|
//
|
|||
|
|
// Иммунитета нет ни у кого, и это правило: тварь, которую физически нельзя
|
|||
|
|
// ранить, — не сложность, а тупик для игрока без нужной стихии.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
constexpr float RESIST_MAX = 0.85f; // выше не поглощает никто
|
|||
|
|
constexpr float RESIST_MIN = -0.60f; // ниже уязвимость не опускается
|
|||
|
|
|
|||
|
|
struct Resists
|
|||
|
|
{
|
|||
|
|
float r[ELEMENT_COUNT] = {};
|
|||
|
|
|
|||
|
|
Resists() = default;
|
|||
|
|
|
|||
|
|
// Порядок как в enum: KIN, INC, CRY, TOX, ARC, RAD, VOID.
|
|||
|
|
Resists(float kin, float inc, float cry, float tox, float arc, float rad, float vd)
|
|||
|
|
: r{kin, inc, cry, tox, arc, rad, vd}
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
float Of(Element e) const { return r[int(e)]; }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Итоговый урон. Единственное место, где стихии превращаются в число.
|
|||
|
|
//
|
|||
|
|
// raw — урон источника до сопротивлений
|
|||
|
|
// mix — из чего он состоит
|
|||
|
|
// res — сопротивления цели
|
|||
|
|
// pen — пробитие сборки, одно на все стихии (аффиксы придут позже)
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
inline float ResolveDamage(float raw, const ElementMix& mix, const Resists& res, float pen = 0.0f)
|
|||
|
|
{
|
|||
|
|
float total = 0.0f;
|
|||
|
|
for (int i = 0; i < ELEMENT_COUNT; ++i)
|
|||
|
|
{
|
|||
|
|
const float part = raw * mix.share[i];
|
|||
|
|
if (part <= 0.0f) continue;
|
|||
|
|
|
|||
|
|
const float effective = std::min(std::max(res.r[i] - pen, RESIST_MIN), RESIST_MAX);
|
|||
|
|
total += part * (1.0f - effective);
|
|||
|
|
}
|
|||
|
|
return total;
|
|||
|
|
}
|