164 lines
9.0 KiB
C
164 lines
9.0 KiB
C
|
|
#pragma once
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Дерево улучшений отряда.
|
|||
|
|
//
|
|||
|
|
// ЧТО БЫЛО И ПОЧЕМУ ЭТОГО МАЛО. До этой вехи опыт тратился на четыре строки:
|
|||
|
|
// патроны, переноска, обзор, живучесть. Каждая — просто «+число за уровень».
|
|||
|
|
// Выбора там не было вообще: купить в итоге можно всё, порядок ни на что не
|
|||
|
|
// влияет, и через две вылазки экран улучшений превращался в кнопку «продолжить».
|
|||
|
|
//
|
|||
|
|
// Дерево устроено иначе, и разница ровно в одном: ВЫБОР ЕСТЬ, ПОТОМУ ЧТО ВСЁ НЕ
|
|||
|
|
// ВЗЯТЬ. Узлы стоят дорого и дорожают, ветвь открывается по цепочке, и опыт —
|
|||
|
|
// единственная валюта на все пять ветвей сразу.
|
|||
|
|
//
|
|||
|
|
// Пять ветвей — это пять способов закрыть разрыв из закона масштаба
|
|||
|
|
// (docs/20-gdd.md §2): угроза растёт квадратично, сила отряда — линейно, и
|
|||
|
|
// разрыв закрывается ЗНАНИЕМ. Ветви и есть виды знания:
|
|||
|
|
//
|
|||
|
|
// СТВОЛ — про огонь: урон, пробитие, боезапас;
|
|||
|
|
// КЛИНОК — про ближний бой: урон, парирование, скорость;
|
|||
|
|
// СТИХИИ — про реакции и статусы: то, чем берут защищённых;
|
|||
|
|
// СНАБЖЕНИЕ — про ресурсы вылазки: свет, переноска, патроны;
|
|||
|
|
// ОТРЯД — про живучесть и людей: здоровье, броня, ярость.
|
|||
|
|
//
|
|||
|
|
// НИ ОДИН УЗЕЛ НЕ ЗАВОДИТ НОВОГО КАНАЛА ВЛИЯНИЯ. Каждый кладёт своё число туда,
|
|||
|
|
// куда уже кладут снаряжение и аффиксы (sim/affix.h): статы бойца, множители
|
|||
|
|
// оружия, ресурсы вылазки. Это то же правило, что и у предметов, и оно тут
|
|||
|
|
// важнее вдвойне — дерево растёт, и первый же «особенный» узел со своей веткой
|
|||
|
|
// в коде превратил бы SyncLoadout в свалку.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
#include <cstdint>
|
|||
|
|
|
|||
|
|
// Ветви. Порядок — порядок столбцов на экране.
|
|||
|
|
enum class Branch : uint8_t
|
|||
|
|
{
|
|||
|
|
GUN = 0,
|
|||
|
|
BLADE,
|
|||
|
|
ELEMENT,
|
|||
|
|
SUPPLY,
|
|||
|
|
SQUAD,
|
|||
|
|
COUNT,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
constexpr int BRANCH_COUNT = int(Branch::COUNT);
|
|||
|
|
constexpr int NODES_PER_BRANCH = 8;
|
|||
|
|
constexpr int TREE_NODES = BRANCH_COUNT * NODES_PER_BRANCH;
|
|||
|
|
|
|||
|
|
inline const char* BranchName(Branch b)
|
|||
|
|
{
|
|||
|
|
switch (b)
|
|||
|
|
{
|
|||
|
|
case Branch::GUN: return "FIREARMS";
|
|||
|
|
case Branch::BLADE: return "BLADES";
|
|||
|
|
case Branch::ELEMENT: return "ELEMENTS";
|
|||
|
|
case Branch::SUPPLY: return "SUPPLY";
|
|||
|
|
default: return "SQUAD";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Что узел даёт. Тег — адрес числа в уже существующих каналах, не «новая
|
|||
|
|
// механика». Если для эффекта пришлось бы заводить новый канал, такому узлу
|
|||
|
|
// в дереве не место, пока канал не появился сам по себе.
|
|||
|
|
enum class NodeEffect : uint8_t
|
|||
|
|
{
|
|||
|
|
GUN_DAMAGE = 0, // множитель урона стволов
|
|||
|
|
GUN_PEN, // пробитие сопротивлений
|
|||
|
|
AMMO, // запас патронов на вылазку
|
|||
|
|
RELOAD, // скорость перезарядки
|
|||
|
|
BLADE_DAMAGE, // множитель урона клинков
|
|||
|
|
PARRY, // шанс парирования
|
|||
|
|
MELEE_SPEED, // темп ударов клинка
|
|||
|
|
REACTION, // сила реакций стихий
|
|||
|
|
STATUS, // длительность статусов
|
|||
|
|
CONVERT, // доля урона в стихию оружия
|
|||
|
|
LIGHT, // заряд фонаря
|
|||
|
|
CARRY, // норма переноски
|
|||
|
|
SALVAGE, // сколько добычи уцелевает при вайпе
|
|||
|
|
MATERIALS, // сколько материалов приносит вылазка
|
|||
|
|
HEALTH, // запас здоровья
|
|||
|
|
ARMOR, // броня
|
|||
|
|
SPEED, // скорость хода
|
|||
|
|
SIGHT, // радиус обзора
|
|||
|
|
RAGE, // ярость отряда за павшего
|
|||
|
|
COUNT,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const char* NodeEffectName(NodeEffect e);
|
|||
|
|
|
|||
|
|
// Одна запись дерева. Данные, а не код: узлы правят таблицей, глядя на неё
|
|||
|
|
// целиком, и добавление узла не требует трогать ни одного алгоритма.
|
|||
|
|
struct TreeNode
|
|||
|
|
{
|
|||
|
|
const char* name = "-";
|
|||
|
|
Branch branch = Branch::GUN;
|
|||
|
|
int row = 0; // место в ветви, оно же порядок открытия
|
|||
|
|
NodeEffect effect = NodeEffect::GUN_DAMAGE;
|
|||
|
|
float perRank = 0.0f; // сколько даёт один уровень
|
|||
|
|
int maxRank = 5;
|
|||
|
|
float costBase = 0.0f; // цена первого уровня; дальше растёт
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Таблица всех узлов. Определена в tree.cpp.
|
|||
|
|
const TreeNode& TreeNodeAt(int index);
|
|||
|
|
|
|||
|
|
// Индекс узла по ветви и месту в ней. Экран рисует сетку, а профиль хранит
|
|||
|
|
// плоский массив — переводит одно в другое эта пара функций.
|
|||
|
|
inline int TreeIndex(Branch b, int row) { return int(b) * NODES_PER_BRANCH + row; }
|
|||
|
|
|
|||
|
|
// Цена СЛЕДУЮЩЕГО уровня.
|
|||
|
|
//
|
|||
|
|
// Растёт трижды, и каждый множитель отвечает за своё:
|
|||
|
|
// * место узла в ветви — нижние узлы сильнее и дороже, иначе выгодно было бы
|
|||
|
|
// докупать верхние по мелочи и никуда не спускаться;
|
|||
|
|
// * уровень узла — «добить до максимума» обязано быть отдельным решением;
|
|||
|
|
// * СКОЛЬКО ВСЕГО УЖЕ ВЛОЖЕНО — каждая взятая точка поднимает цену всех
|
|||
|
|
// следующих.
|
|||
|
|
//
|
|||
|
|
// Третий множитель — главный, и без него дерево умирает. Опыт растёт с глубиной
|
|||
|
|
// квадратично (`--tool economy`: этаж 100 даёт 190 тысяч), а дерево с
|
|||
|
|
// фиксированными ценами выкупается целиком за половину одного глубокого
|
|||
|
|
// прохода. Первая версия так и вышла: всё дерево стоило 9% одного прохода
|
|||
|
|
// 1..100, то есть к сороковому этажу выбор кончался навсегда.
|
|||
|
|
constexpr float TREE_ESCALATION = 0.15f;
|
|||
|
|
|
|||
|
|
float TreeNodeCost(int index, int currentRank, int totalRanks);
|
|||
|
|
|
|||
|
|
// Требование: узел открывается, когда предыдущий в ЕГО ВЕТВИ взят хотя бы на
|
|||
|
|
// первый уровень. Цепочка, а не свободная сетка, — потому что ветвь должна
|
|||
|
|
// читаться как путь: «я иду в стихии» видно по дереву, а не по таблице цифр.
|
|||
|
|
inline int TreeNodeRequires(int index)
|
|||
|
|
{
|
|||
|
|
const int row = index % NODES_PER_BRANCH;
|
|||
|
|
return (row == 0) ? -1 : index - 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Итог дерева: сумма всех взятых уровней. Считается один раз при сборке этажа,
|
|||
|
|
// дальше читается как готовые числа — лазать по узлам из правил боя нельзя, там
|
|||
|
|
// про дерево не знают вообще.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
struct TreeBonus
|
|||
|
|
{
|
|||
|
|
float gunDamage = 0.0f; // доля, прибавка к множителю
|
|||
|
|
float gunPen = 0.0f;
|
|||
|
|
float ammo = 0.0f;
|
|||
|
|
float reload = 0.0f; // доля, ускорение перезарядки
|
|||
|
|
float bladeDamage = 0.0f;
|
|||
|
|
float parry = 0.0f;
|
|||
|
|
float meleeSpeed = 0.0f;
|
|||
|
|
float reaction = 0.0f;
|
|||
|
|
float status = 0.0f;
|
|||
|
|
float convert = 0.0f;
|
|||
|
|
float light = 0.0f;
|
|||
|
|
float carry = 0.0f;
|
|||
|
|
float salvage = 0.0f;
|
|||
|
|
float materials = 0.0f;
|
|||
|
|
float health = 0.0f;
|
|||
|
|
float armor = 0.0f;
|
|||
|
|
float speed = 0.0f;
|
|||
|
|
float sight = 0.0f;
|
|||
|
|
float rage = 0.0f;
|
|||
|
|
|
|||
|
|
void Add(NodeEffect e, float amount);
|
|||
|
|
};
|