#pragma once // ----------------------------------------------------------------------------- // Что означает глубина N. // // Игра — это бесконечный спуск: чем глубже, тем страшнее окружение, злее твари, // богаче находки. Весь этот наклон живёт ЗДЕСЬ, в одной функции, и больше // нигде. Причина простая: кривая сложности — это то, что правят чаще всего и // правят на ощупь. Размазанная по генератору, каталогу и рендеру, она через // месяц перестаёт поддаваться правке вообще, потому что «сделать десятый этаж // чуть злее» превращается в раскопки по трём файлам. // // Чего здесь НЕТ намеренно: // * множителей урона и здоровья тварей. «Тот же бредущий, но со втрое // большим HP» — это не страшнее, это дольше. Глубина меняет, КТО выходит // и сколько их, а не сколько раз надо выстрелить в одного и того же; // * потолка. Функция определена для любого N: спуск бесконечен, и этаж 200 // обязан собраться, даже если каталог кончился на 12-м. // // Всё, что отсюда выходит, — данные. Ни рендер, ни симуляция про «глубину» не // знают, они читают готовые числа. // ----------------------------------------------------------------------------- #include #include "engine/theme.h" struct FloorSpec { int depth = 1; // Отделка этажа. Комнаты со своей темой её сохраняют — тема этажа // достаётся коридорам и тем комнатам, которые от неё не отказались. uint8_t theme = 0; // Сколько «опасности» разрешено выпустить на этаж (цены — в каталоге). // Главный рычаг сложности: он растёт всегда, а состав того, на что его // тратят, меняется вместе с ним. float dangerBudget = 4.0f; // Множитель шанса срабатывания точек спавна. Бюджет отвечает за то, // НАСКОЛЬКО злой этаж, этот множитель — за то, насколько он плотный. float spawnChanceScale = 1.0f; // Лут: сколько предметов лежит на этаже и какого качества. int lootDrops = 2; int lootTier = 0; // Насколько сжат обзор. Render-only: свет и туман войны на симуляцию не // влияют (docs/02-architecture.md), поэтому темнеть с глубиной безопасно. // Именно это и делает окружение страшнее — не новые спрайты, а то, что // видно всё меньше. float sightScale = 1.0f; // Насколько ярко помнится пройденное. Меньше — темнее позади отряда. float ambient = 0.42f; // Опыт за этаж — множитель к цене убитого. Растёт медленнее опасности: // спускаться должно быть выгодно, но не настолько, чтобы пропуск этажей // стал единственной разумной игрой. float xpScale = 1.0f; }; // ----------------------------------------------------------------------------- // Коэффициенты кривой. Раньше они стояли числами прямо в DescribeFloor, то есть // «чуть злее с десятого этажа» требовало пересборки движка. Теперь это данные // проекта (project.txt), и ФОРМА кривой при этом остаётся в коде: проект гнёт // её, но не может подменить, скажем, линейный рост экспонентой. // // Так и задумано. Проект, которому нужна другая форма, — это повод завести ещё // одно поле здесь, а не язык выражений в текстовом файле: кривую правят на // ощупь и часто, и она обязана оставаться читаемой целиком за один взгляд. // ----------------------------------------------------------------------------- struct FloorCurve { // Опасность: base + linear*(d-1) + quad*(d-1)^2. // // База 1.6, а не 3.0: первый этаж — это «полупустая лаборатория, две вялых // твари» (docs/20-gdd.md §1), а не три штуки на одинокого оперативника с // ножом. Три и были: бюджета 3.0 хватало ровно на трёх бредущих, и стартовый // забег превращался в отбивание от толпы. float dangerBase = 2.2f; float dangerLinear = 2.2f; float dangerQuad = 0.10f; // Плотность точек: 1 + perFloor*(d-1), но не выше cap. float densityPerFloor = 0.05f; float densityCap = 1.5f; // Лут: base + d/floorsPerDrop предметов, тир — ступень на floorsPerTier. int lootBase = 2; int floorsPerDrop = 4; int floorsPerTier = 3; // Обзор: sightStart - perFloor*(d-1), но не ниже floorAt. // // Начинается ВЫШЕ единицы: наверху ещё работает освещение, и хоррор там не // из темноты, а из того, что игрок один. Темнота — инструмент глубины, и // тратить её на первый этаж значит не иметь запаса дальше. float sightStart = 1.30f; float sightPerFloor = 0.035f; float sightFloorAt = 0.60f; // Освещение самого этажа: насколько ярко видно УЖЕ ПРОЙДЕННОЕ. Наверху свет // ещё горит (0.42), внизу остаётся только память о том, где ты был (0.13). // Это и есть «атмосфера становится страшнее» — не спрайтами, а тем, что // сзади темнеет. float ambientStart = 0.42f; float ambientPerFloor = 0.022f; float ambientFloorAt = 0.13f; // Опыт: 1 + perFloor*(d-1). float xpPerFloor = 0.15f; // С какого этажа начинается каждая тема отделки. Последняя полоса открытая: // дальше тема не меняется, потому что менять её больше не на что. int themeFrom[THEME_COUNT] = {1, 4, 8, 13}; }; // Активная кривая. Ставится один раз при загрузке проекта, как и g_proj. inline FloorCurve g_curve; // Кривая. Определена для любого depth >= 1. FloorSpec DescribeFloor(int depth); // Подпись этажа для HUD: "D3 RUST". Здесь, а не в HUD, потому что имя темы // этажа — часть кривой, и знать, какая тема на каком этаже, HUD не обязан. // Пишет в buf не больше cap символов, включая ноль. void FloorLabel(const FloorSpec& spec, char* buf, int cap);