#pragma once // ----------------------------------------------------------------------------- // Стихии урона и сопротивления. // // Зачем они вообще нужны в игре про бесконечный спуск: сила отряда растёт // медленнее, чем угроза (закон масштаба, docs/20-gdd.md), и разрыв обязан // закрываться ЗНАНИЕМ. Стихии — первый и главный канал такого знания: тварь, // на которую взяли верное оружие, умирает втрое быстрее, чем от неверного, и // цифра эта не растёт от прокачки. // // Ручных действий игра не заводит (спек 5): стихия — свойство ОРУЖИЯ, а не // кнопка. Всё решается тем, что отряд взял вниз. // // Здесь только арифметика урона. Статусы (горение, оледенение) и реакции между // стихиями — следующая веха, они живут поверх этого файла, а не внутри него. // ----------------------------------------------------------------------------- #include #include 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; }