Отряд из пяти не проходил первый этаж ни на одном сиде, а одиночка проходил. Дело было не в ширине коридоров: у бойцов не было поиска пути вообще. Боец, оказавшийся за стеной от своего слота, seek'ом тянулся В СТЕНУ, обход стены толкал его обратно, и он замирал в углу навсегда. Теперь у каждого бойца своё поле путей (BFS от клетки его цели) с натягиванием верёвки: он берёт самую дальнюю точку пути, которую видит. Включается обход по двум условиям сразу — боец упёрся (хочет идти и не едет) И потерял строй. Счётчик упора сбрасывается по факту «цель видно», а не «поехал»: сброс по скорости давал дребезг, и боец полз шаг за две секунды. По дороге вскрылись две неверные МЕРКИ: * критерий 11.6 считал колонной «разброс вдоль больше, чем поперёк» — и потому награждал отряд, у которого двое навсегда остались по ту сторону стены. Теперь колонна меряется самим проёмом: разом в горловине не больше двух, и прошли ВСЕ. * зонд --wall читал «стены не нашлось» (ноль) как «стена на нулевом этаже» и объявлял пятёрку, не сломавшуюся нигде, худшей из трёх. Обе переписаны и проверены на сломанной сборке. Замеры: --play 6 5 на восьми сидах 47/48 (было 0/48), --accept PASS, шаг 6.1 мкс, --wall 30 одиночка D20 / трое D20 / пятеро D31 (не найдена), --descend 8/30, hollow --descend 6, --ui-check, оба проекта --check — без ошибок. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
570 lines
41 KiB
C++
570 lines
41 KiB
C++
#pragma once
|
||
#include <algorithm>
|
||
|
||
#include "engine/config.h"
|
||
// -----------------------------------------------------------------------------
|
||
// Единственное место, где живут числа ИГРЫ.
|
||
// В коде не должно быть магических констант — всё берётся отсюда.
|
||
//
|
||
// * блок COMPILE-TIME — размеры буферов игры, менять только пересборкой;
|
||
// * struct Tuning — всё, что подкручивается в рантайме ([ ] выбор, - = шаг).
|
||
//
|
||
// Размеры кадра, тайла и карты сюда БОЛЬШЕ НЕ ВХОДЯТ: это хозяйство движка, оно
|
||
// в engine/config.h. Имена те же, файл включён выше — по коду игры ничего не
|
||
// меняется, но движок теперь собирается без единого файла игры.
|
||
// -----------------------------------------------------------------------------
|
||
|
||
// --- compile-time константы --------------------------------------------------
|
||
|
||
// Точка старта отряда (центральная арена).
|
||
constexpr float SQUAD_START_X = 26.5f; // ближе к кучке: дальность стала 7.2
|
||
constexpr float SQUAD_START_Y = 26.5f;
|
||
|
||
constexpr int NUM_AGENTS = 5;
|
||
constexpr int NUM_TARGETS = 12; // мишени приёмки: расстановка захардкожена
|
||
constexpr int MAX_BULLETS = 512;
|
||
|
||
// Твари, которых боевой режим доставляет СВЕРХ мишеней. В песочнице приёмки их
|
||
// нет: расстановка мишеней и все замеры на ней остаются прежними.
|
||
constexpr int NUM_EXTRA_ENEMIES = 12;
|
||
|
||
// Разрушаемые объекты (ящики, бочки, шкафчики). Спавнятся ТОЛЬКО в боевом режиме;
|
||
// приёмка их не видит. Приоритет — ниже всех: ИИ переключается на них только
|
||
// если ни одного живого врага в поле зрения нет.
|
||
constexpr int NUM_PROPS = 8;
|
||
|
||
// Потолок на число целей в мире. Он не «на всякий случай»: MeleeWeapon::hitMask —
|
||
// это маска «кого уже задел ЭТИМ взмахом», индексируемая номером цели, и цель
|
||
// за её пределами молча получала бы удары дважды за взмах.
|
||
//
|
||
// Было 32. Игра про бесконечный спуск упёрлась в этот потолок примерно на
|
||
// шестнадцатом этаже: бюджет опасности перестал влезать в число тел, и глубже
|
||
// этажи переставали становиться страшнее. Маска расширена до 64 бит — это не
|
||
// «запас на будущее», а ровно та цифра, которой сейчас не хватало.
|
||
constexpr int MAX_TARGETS = 64;
|
||
static_assert(NUM_TARGETS + NUM_EXTRA_ENEMIES + NUM_PROPS <= MAX_TARGETS,
|
||
"целей больше, чем битов в MeleeWeapon::hitMask");
|
||
// У движка потолок населения свой (MAX_LEVEL_SPAWNS в engine/level_gen.h):
|
||
// генератор уровней не обязан знать про маску удара клинком. Что одно влезает
|
||
// в другое, проверяется там, где видны оба, — в src/game.h.
|
||
|
||
// Сколько слотов строя занимают бойцы ближнего боя (слоты 0..N-1).
|
||
// В клине это остриё и левое крыло — те, кто и так идёт первым.
|
||
constexpr int DEFAULT_MELEE_COUNT = 2;
|
||
|
||
// Сколько ПОСЛЕДНИХ слотов носят пистолет вместо карабина.
|
||
// Короткий ствол уезжает в хвост клина: ему там и место.
|
||
constexpr int DEFAULT_PISTOL_COUNT = 1;
|
||
|
||
// Сколько тайлов пола могут нести запечённую кровь. Каждый — своя копия
|
||
// спрайта 32x16 (2 КБ), то есть потолок ~2.4 МБ. Дальше новые пятна ложатся
|
||
// только на уже запачканные тайлы: декали вечные, но не бесконечные.
|
||
constexpr int BLOOD_TILE_BUDGET = 1200;
|
||
|
||
// Решатель огневых позиций: 3 кольца x 12 направлений + текущая позиция.
|
||
constexpr int SOLVER_RINGS = 3;
|
||
constexpr int SOLVER_DIRS = 12;
|
||
constexpr int SOLVER_CANDIDATES = SOLVER_RINGS * SOLVER_DIRS + 1;
|
||
|
||
// --- runtime-тюнинг ----------------------------------------------------------
|
||
|
||
struct Tuning
|
||
{
|
||
// движение агента
|
||
// Скорость отряда. Была 1.75 при курсоре 3.3: отряд ВСЕГДА отставал, и
|
||
// управление ощущалось как «тяну за верёвку», а не «веду людей». Поднято до
|
||
// 2.6 — это по-прежнему медленнее курсора (иначе поводок теряет смысл), но
|
||
// разрыв закрывается за доли секунды, а не за метры.
|
||
float moveSpeed = 2.60f; // юнитов/сек: хоррор, а не шутер-беготня
|
||
float moveSpeedFast = 4.4f; // догоняют курсор быстро — иначе отряд отстаёт
|
||
float steerAccel = 26.0f; // тяжелее; НИЖЕ 16 нельзя — ломается критерий 11.4
|
||
float bodyRadius = 0.28f;
|
||
float separationRadius = 0.7f;
|
||
float wSeek = 1.0f;
|
||
// Как быстро строй доворачивается к направлению хода. Мгновенный доворот
|
||
// заставлял бойцов бегать вбок на каждом повороте (sim/squad.cpp).
|
||
float formTurnRate = 7.0f;
|
||
// Насколько боец может отстать от своего слота, прежде чем бросит всё и
|
||
// вернётся в строй. Ниже 2 отряд перестаёт разбредаться по огневым
|
||
// позициям вообще, выше 4 — снова застревает цепочкой (sim/squad.cpp).
|
||
float straggleDist = 2.8f;
|
||
// Когда включается поле путей. Не по расстоянию до цели, а по факту упора:
|
||
// боец хочет идти и не едет. Расстоянием этот порог не задать — на подходе
|
||
// к проходу цель не видна никому, и по «не видно» все пятеро получили бы
|
||
// одну путевую точку и вошли бы в горловину шеренгой (критерий 11.6).
|
||
float navStuckTime = 0.35f; // секунд упора до обхода
|
||
float navStuckSpeed = 0.25f; // «не едет» — это доля от скорости хода
|
||
float navGoalReached = 0.35f; // ближе этого цель считается достигнутой
|
||
float wSeparation = 1.0f;
|
||
float wLaneAvoid = 2.4f; // строго выше wSeparation (спек 6.7)
|
||
float wWallAvoid = 1.2f;
|
||
float wallLookahead = 0.8f;
|
||
float wallSlide = 1.4f; // касательная составляющая обхода стен
|
||
float arriveRadius = 0.18f;
|
||
float turnRateDeg = 300.0f; // ствол тяжёлый
|
||
float wTargetPush = 1.0f; // мишень — тело, а не пустое место
|
||
float targetPushGap = 0.10f; // зазор поверх суммы радиусов
|
||
|
||
// оружие (это ПАРАМЕТРЫ КАРАБИНА — оружия по умолчанию, на котором меряются
|
||
// критерии приёмки. Остальные стволы — записи каталога sim/weapon_defs.h)
|
||
float weaponRange = 7.2f; // -20% от исходных 9.0
|
||
float fireInterval = 0.35f;
|
||
float laneHalfWidth = 0.10f;
|
||
float damage = 12.0f;
|
||
float safetyMargin = 0.15f;
|
||
float muzzleOffset = 0.3f;
|
||
float bulletSpeed = 22.0f;
|
||
float bulletRadius = 0.04f;
|
||
float bulletKnock = 0.35f; // импульс мишени от попадания пули
|
||
float aimToleranceDeg = 5.0f; // ствол должен быть доведён до цели
|
||
|
||
// --- разброс -------------------------------------------------------------
|
||
// Стоя ствол почти не гуляет, на полном ходу гуляет сильно, очередь его
|
||
// раскрывает. Тот же конус проверяется на своих перед спуском: правило 6.3
|
||
// не ослаблено, оно просто считает по конусу вместо отрезка.
|
||
float spreadStandDeg = 0.9f;
|
||
float spreadMoveDeg = 4.2f;
|
||
float spreadRecoilDeg = 0.55f; // прибавка за выстрел
|
||
float spreadRecoilMax = 3.0f;
|
||
float spreadRecoilDecay = 3.2f; // градусов/сек оседания
|
||
float spreadScale = 1.0f; // общий множитель, ручка для замеров
|
||
|
||
// --- перезарядка ---------------------------------------------------------
|
||
float magazineRifle = 24.0f; // патронов в магазине карабина
|
||
float reloadRifle = 1.9f; // сек
|
||
// Запас на манёвр союзника за время полёта пули (доля от moveSpeed).
|
||
// 0 = верить экстраполяции скорости буквально.
|
||
float shotPredictSlack = 0.3f;
|
||
|
||
// --- ближний бой ---------------------------------------------------------
|
||
// Три фазы: замах видно, урон живёт в коротком окне, после удара боец открыт.
|
||
float meleeReach = 1.40f; // от центра тела до конца замаха
|
||
float meleeArcDeg = 100.0f; // ширина сектора удара
|
||
float meleeWindup = 0.17f; // замах — телеграф
|
||
float meleeStrike = 0.13f; // активное окно урона
|
||
float meleeRecover = 0.24f; // после удара
|
||
float meleeInterval = 0.60f; // между началами замахов
|
||
float meleeDamage = 130.0f; // 4 удара на мишень против 42 пуль
|
||
float meleeBladeDeg = 34.0f; // ширина полосы самого лезвия
|
||
float meleeAimToleranceDeg = 30.0f; // допуск разворота на старте замаха
|
||
float meleeAllyMargin = 0.14f; // запас к телу союзника в секторе
|
||
// На сколько взмахов вперёд смотреть на союзника при старте замаха
|
||
// (в долях windup+strike). 0 = верить только текущему положению.
|
||
float meleeStartPredict = 1.0f;
|
||
// Доля бокового размаха сектора, которую клинок резервирует в реестре линий.
|
||
// 1.0 = вся дуга; тогда своим нечем дышать. 0 = резерв шириной с отрезок.
|
||
float meleeReserveFrac = 0.55f;
|
||
float meleeGap = 0.12f; // зазор при подходе вплотную
|
||
float meleeLungeSpeed = 3.4f; // рывок в момент удара, юнитов/сек
|
||
float meleeLungeAccel = 120.0f; // рывок обязан успеть за 0.13 с
|
||
float meleeWindupScale = 0.20f; // множитель скорости на замахе
|
||
float meleeRecoverScale= 0.55f;
|
||
float meleeKnockback = 2.4f; // импульс мишени, юнитов/сек
|
||
// СВОЙ круг бойца: его отпускают от диска дальше, чем стрелка.
|
||
float meleeCohesionRadius = 5.6f;
|
||
float meleeSeekRadius = 7.0f; // дальше этого боец за целью не идёт вообще
|
||
// Штраф за уже занятую цель, в юнитах дистанции. Двое с клинками, вцепившиеся
|
||
// в одного, половину взмахов обрывают друг об друга — пусть расходятся.
|
||
float meleeClaimPenalty = 3.0f;
|
||
// Сколько держится связка после удара. Не успел продолжить — начинаешь
|
||
// серию заново с первого удара.
|
||
float meleeComboWindow = 1.10f;
|
||
|
||
// --- живучесть отряда и тварей -------------------------------------------
|
||
// У мишеней приёмки эти числа не работают: DUMMY не бьёт и не ходит,
|
||
// поэтому --accept от них не зависит вообще.
|
||
float agentHP = 100.0f; // запас бойца до брони и оберегов
|
||
float enemyDamageScale = 1.0f; // общая сложность: во сколько раз больно
|
||
float enemyRespawn = 16.0f; // через сколько тварь возвращается
|
||
float enemyLightPower = 0.14f; // как ярко тлеют глаза в темноте
|
||
// Сколько цель «светится» после попадания. Единственный признак, по которому
|
||
// в темноте видно, что отряд по кому-то реально работает, а не мажет.
|
||
float targetHurtTime = 0.22f;
|
||
|
||
// --- расход ------------------------------------------------------------------
|
||
// Патроны на вылазку. Не «на этаж»: запас едет вниз вместе с отрядом, и
|
||
// решение «ещё глубже или наверх» упирается именно в него.
|
||
float startAmmo = 120.0f;
|
||
// При какой доле здоровья боец сам возьмётся за бинт и на каком удалении
|
||
// от ближайшей твари это ему позволено. Лечиться в бою нельзя: перевязка
|
||
// посреди схватки обесценила бы урон.
|
||
float healAtFrac = 0.55f;
|
||
float healSafeDist = 9.0f;
|
||
|
||
// --- спуск, находки, опыт ---------------------------------------------------
|
||
// Ничего из этого не работает в песочнице приёмки: там нет ни лута, ни
|
||
// спуска, и `--accept` этих чисел не касается.
|
||
//
|
||
// Радиус подбора заметно больше тела бойца: подбор пассивный, и промазать
|
||
// мимо лежащего предмета, пройдя вплотную, игрок не должен — он ведь по
|
||
// нему не кликал.
|
||
float lootPickupRadius = 0.65f;
|
||
// На каком расстоянии от точки спуска отряд считается дошедшим. Меряется
|
||
// по ЯКОРЮ, а не по бойцам: спуск — это решение игрока, а не то, что
|
||
// случается от того, что один боец забрёл не туда.
|
||
float descendRadius = 1.10f;
|
||
// Сколько опыта даёт единица опасности убитого. Опыт этажа сверх этого
|
||
// множится на xpScale из кривой глубины (engine/progression.h).
|
||
float xpPerDanger = 10.0f;
|
||
|
||
// --- разрушаемая обстановка ------------------------------------------------
|
||
// Радиус, в котором ОДНО ПРИСУТСТВИЕ живой твари запрещает бойцу работать
|
||
// по хламу. Намеренно шире дальности любого ствола: привязать его к дальности
|
||
// значило бы «пока не достаю — стреляю по ящику», и боец встречал бы
|
||
// подошедшую тварь с пустым магазином. Ящик ломают, когда вокруг ЧИСТО.
|
||
float propYieldRadius = 15.0f;
|
||
|
||
// --- атаки тварей ---------------------------------------------------------
|
||
// Форма каждой атаки (сектор, радиус круга, длина рывка) — свойство ВИДА и
|
||
// живёт в EnemyDef (sim/target.cpp). Здесь только то, что общее для всех:
|
||
// именно этими двумя ручками правится «успеваю ли я среагировать».
|
||
//
|
||
// enemyWindupScale — множитель ВСЕХ телеграфов сразу. Опускать его ниже
|
||
// ~0.5 нечестно: замах становится короче времени реакции, и смерть бойца
|
||
// снова выглядит случайной.
|
||
float enemyWindupScale = 1.0f;
|
||
// Сколько захват держится без подтверждения от твари. Тварь каждый шаг
|
||
// продлевает его на эту величину; убили её — боец освобождается сам, и
|
||
// ни bullet.cpp, ни клинку не приходится знать про захват вообще.
|
||
float enemyGrabHold = 0.15f;
|
||
|
||
// мишени
|
||
float targetHP = 500.0f;
|
||
float targetRespawn = 3.0f;
|
||
float targetRadius = 0.34f;
|
||
float targetKnockDamp = 9.0f; // гашение импульса от удара
|
||
float targetReturnRate= 5.0f; // возврат на своё место
|
||
// Общий множитель сопротивления отбросу. Само сопротивление у каждой мишени
|
||
// своё (sim/target.cpp): тяжёлую клинком не сдвинуть, лёгкая улетает.
|
||
float knockResistScale = 1.0f;
|
||
|
||
// отряд
|
||
float holdDelay = 0.22f; // сек без ввода -> HOLDING
|
||
float cohesionRadius = 2.1f; // стрелки держатся кучнее
|
||
float minEngageDist = 1.6f; // отмасштабировано под дальность 7.2
|
||
float formationSpacing = 1.0f; // строй плотнее
|
||
float cameraSmooth = 9.0f;
|
||
float anchorRadius = 0.3f; // якорь тоже скользит вдоль стен
|
||
// Диск отзывчивее бойцов: команда читается мгновенно, а догоняют её ногами.
|
||
float anchorSpeed = 3.3f;
|
||
float anchorSpeedFast = 4.8f;
|
||
float anchorAccel = 20.0f; // разгон/торможение курсора, юнитов/сек^2
|
||
// Поводок УПРУГИЙ, а не жёсткий: без него быстрый диск просто отрывается от
|
||
// отряда и теряет смысл, но жёсткий упор намертво фиксирует дистанцию, и по
|
||
// ней уже не отличить «подправил направление» от «тяну изо всех сил».
|
||
// anchorLeash — мягкая граница: до неё курсор идёт свободно;
|
||
// anchorLeashMax — жёсткий предел: страховка, до неё обычно не доходит;
|
||
// leashDrag — насколько тяжелеет курсор между границами (0..1).
|
||
float anchorLeash = 2.5f;
|
||
float anchorLeashMax = 4.2f;
|
||
float leashDrag = 0.75f; // на жёсткой границе остаётся 25% скорости
|
||
|
||
// --- натяжение поводка (см. docs/05-squad-ai.md) --------------------------
|
||
// Курсор упёрся в поводок, а игрок продолжает давить -> он хочет идти
|
||
// быстрее, чем отряд шагает. Это и есть команда «бегом»: отряд ускоряется,
|
||
// строй растягивается, а на верхней трети шкалы огонь прекращается вовсе.
|
||
float urgeStartFrac = 0.50f; // с какой доли поводка начинается натяжение
|
||
// Натяжение набирается быстро, но НЕ мгновенно, и порог спринта поднят.
|
||
// При urgeAttack 5.2 отряд уходил в спринт от любого движения курсора, а
|
||
// спринт растягивает строй и выключает огонь: в коридорах это выглядело как
|
||
// «отряд застрял». Поймано автопилотом (--play): первый этаж переставал
|
||
// проходиться вообще.
|
||
float urgeAttack = 3.4f; // как быстро натяжение набирается
|
||
float urgeRelease = 3.4f; // и как быстро отпускает
|
||
float sprintAt = 0.95f; // выше — спринт: бежим, не атакуем.
|
||
// Порог высокий намеренно: при удержании направления натяжение садится
|
||
// примерно на 0.54, то есть отряд идёт быстрым шагом И ПРОДОЛЖАЕТ работать
|
||
// по целям. Спринт остаётся тем, чем должен быть, — отдельным решением
|
||
// (Shift) или следствием того, что курсор реально уехал.
|
||
|
||
// --- дыхание строя --------------------------------------------------------
|
||
float holdSpacing = 0.90f; // на стоянке отряд собирается
|
||
// Строй в тесноте: во сколько раз сжимается вбок и вытягивается вдоль хода.
|
||
// 0.28 — это колонна: пять тел проходят в дверь шириной в одну клетку.
|
||
float tightSpacing = 0.28f;
|
||
float tightStretch = 1.60f;
|
||
float sprintSpacing = 1.30f; // в беге расходится
|
||
float sprintStretch = 1.55f; // и вытягивается вдоль движения
|
||
float formMorphRate = 4.0f; // скорость перестроения, 1/сек
|
||
|
||
// решатель огневых позиций (6.5)
|
||
float solveInterval = 0.25f;
|
||
float commitTime = 0.6f;
|
||
float hysteresis = 15.0f;
|
||
float ring1 = 0.7f; // кольца решателя сжаты вслед за строем
|
||
float ring2 = 1.4f;
|
||
float ring3 = 2.1f;
|
||
float candMinAlly = 0.60f; // минимум до другого агента (было МЕНЬШЕ диаметра тела)
|
||
float sLaneClear = 100.0f;
|
||
float sCoverage = 25.0f;
|
||
float sBlockAlly = 35.0f;
|
||
float sDistCost = 9.0f;
|
||
float sCohesionCost = 24.0f; // отбегать от отряда дорого
|
||
float sTooClose = 60.0f;
|
||
// Жёсткая граница огневой позиции: дальше cohesionRadius + postLeash от диска
|
||
// кандидат просто отбраковывается. Одной цены мало — линия огня стоит 100
|
||
// очков, и за неё решатель готов уйти сколько угодно далеко. А отряд, из
|
||
// которого стрелок ушёл на пять метров, отрядом быть перестаёт.
|
||
float postLeash = 1.0f;
|
||
|
||
// расступание из чужих линий (6.6)
|
||
float laneAvoidStrength = 7.0f;
|
||
// Ширина РЕЗЕРВА линии относительно порога «тело в линии» из 6.3.
|
||
// >1 означает: агента отжимает ДО того, как он влез в чужой сектор.
|
||
// Правило стрельбы (6.3) от этого не меняется — см. docs/08-decisions.md.
|
||
float laneReserveScale = 2.0f;
|
||
|
||
// ручное прицеливание (6.8)
|
||
float aimConeDeg = 20.0f;
|
||
float aimTurnRateDeg = 110.0f; // прицел ведётся тяжелее
|
||
|
||
// метрики
|
||
float uptimeWindow = 10.0f; // окно FIRE UPTIME, сек
|
||
float settleQuorum = 4.0f; // сколько агентов нужно для SETTLE TIME
|
||
|
||
// --- свет и туман войны (render-only, на симуляцию не влияет) -----------
|
||
float sightRadius = 7.6f; // где обзор гаснет полностью
|
||
float sightFullRadius = 2.2f; // дальше сразу начинается спад: свет должен таять
|
||
float sightConeDeg = 100.0f; // конус взгляда агента
|
||
float sightConeSoftDeg = 22.0f; // мягкий край конуса
|
||
float sightPersonalRadius= 1.9f; // круговой «личный» ореол вокруг агента
|
||
float sightBrightness = 0.86f; // яркость видимого; остаток отдан вспышкам
|
||
float fogMemoryLevel = 0.13f; // как тускло помнится уже увиденное
|
||
float fogWallBleed = 0.92f; // затухание света вглубь блока стен
|
||
float fogFadeRate = 9.0f; // сглаживание во времени, чтобы не щёлкало
|
||
float fogEntityReveal = 0.30f; // ниже этого света сущность не показывается
|
||
float eyeRevealLight = 0.05f; // глаз мишени тлеет почти в полной темноте
|
||
float eyeRadiusPx = 3.0f;
|
||
float eyeLiftPx = 11.0f; // на уровне «головы» капсулы
|
||
float agentMinLight = 0.34f; // свои читаемы всегда: игрок обязан их различать
|
||
|
||
// вспышки выстрелов как источники света
|
||
float flashPower = 0.55f;
|
||
float flashRadius = 4.0f;
|
||
float flashTime = 0.11f;
|
||
|
||
// рампа освещения (меняет саму таблицу — требует перепечь)
|
||
float lightGamma = 1.45f;
|
||
float lightMaxGain = 1.25f;
|
||
|
||
// --- эффекты стрельбы ---------------------------------------------------
|
||
float fxIntensity = 1.0f;
|
||
float tracerLength = 1.30f; // длина светящегося штриха, юнитов мира
|
||
float bulletLiftPx = 8.0f; // пуля летит на уровне груди, не по земле
|
||
float muzzleTime = 0.07f;
|
||
float muzzleRadiusPx = 5.0f;
|
||
float sparkTime = 0.16f;
|
||
float sparkRadiusPx = 4.0f;
|
||
float bladeLiftPx = 9.0f; // дуга метёт на уровне груди, не по земле
|
||
float bladeTrailFrac = 0.80f; // какая доля пройденной дуги ещё светится
|
||
float meleeShake = 1.4f; // отдача камеры на попадание клинком
|
||
|
||
// --- напряжение и постобработка -----------------------------------------
|
||
float dreadRadius = 7.0f; // с какой дистанции мишень начинает пугать
|
||
float dreadAttack = 6.0f; // страх набегает быстро
|
||
float dreadRelease = 0.5f; // отпускает медленно
|
||
float vigStartCalm = 0.42f; // где начинается виньетка
|
||
float vigStartTense = 0.20f;
|
||
float vigDepthCalm = 0.55f; // насколько темнеет край
|
||
float vigDepthTense = 0.85f;
|
||
float grainCalm = 6.0f; // амплитуда зерна (макс 127)
|
||
float grainTense = 16.0f;
|
||
float heartBpmCalm = 58.0f;
|
||
float heartBpmTense = 132.0f;
|
||
float heartAmp = 0.10f; // насколько сердцебиение давит картинку
|
||
float shotFlash = 0.12f; // подсветка кадра на выстрел
|
||
float shakePerShot = 0.55f; // отдача камеры, пикселей на выстрел
|
||
float shakeMax = 3.0f;
|
||
float shakeDecay = 9.0f;
|
||
|
||
// --- кровь (render-only, декали запекаются в спрайты пола) ---------------
|
||
float bloodAmount = 1.0f; // общий множитель размера пятен, 0 = выключить
|
||
float bloodRadiusHit = 0.26f; // пуля
|
||
float bloodRadiusMelee = 0.50f; // клинок
|
||
float bloodRadiusKill = 0.78f; // смерть: лужа
|
||
float bloodDrops = 5.0f; // сколько брызг летит по направлению удара
|
||
float bloodOpacity = 0.92f; // насколько густо пятно перекрывает пол
|
||
|
||
// --- дымка и пепел в воздухе (render-only) --------------------------------
|
||
float hazeAmount = 0.34f; // сила дымки, 0 = выключить
|
||
float hazeScale = 1.0f; // крупность клубов
|
||
float hazeDrift = 0.05f; // скорость сноса, экранных ячеек/сек
|
||
float hazeGate = 24.0f; // ниже этой яркости дымки нет: тьма остаётся тьмой
|
||
float ashDensity = 0.75f; // пылинок на тайл
|
||
float ashSpeed = 0.30f; // скорость дрейфа пепла
|
||
float ashBrightness = 0.60f;
|
||
float ashLiftPx = 13.0f; // на какой высоте висит взвесь
|
||
|
||
// флаги
|
||
bool freeFireSecondary = true; // 6.8: стрелять по вторичным целям в движении
|
||
bool friendlyFire = true; // 6.9: всегда true, тумблер только для проверки
|
||
};
|
||
|
||
// --- связка ударов ближнего боя ----------------------------------------------
|
||
//
|
||
// Чередование «влево-вправо» — это не связка, а метроном: два удара, между
|
||
// которыми нет разницы ни на глаз, ни по последствиям. Связка обязана иметь
|
||
// ФОРМУ: быстрый вход, злой возврат, тяжёлое завершение.
|
||
//
|
||
// Множители, а не абсолютные числа: базовые характеристики клинка остаются
|
||
// одним набором в Tuning и продолжают крутиться на ходу, а связка их гнёт.
|
||
struct MeleeComboStep
|
||
{
|
||
const char* name;
|
||
float arcMul; // ширина сектора
|
||
float dmgMul;
|
||
float knockMul;
|
||
float windupMul; // замах — телеграф; у тяжёлого удара он длиннее
|
||
float strikeMul; // активное окно
|
||
float recoverMul; // и расплата после
|
||
float intervalMul; // пауза до следующего удара связки
|
||
bool flipSide; // менять сторону захода лезвия
|
||
};
|
||
|
||
inline const MeleeComboStep MELEE_COMBO[] = {
|
||
// 1. Косой заход. Обычный удар, с него всё начинается.
|
||
{"slash", 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, true},
|
||
// 2. Возврат с обратной стороны. Быстрее и почти без паузы — именно это
|
||
// читается как «связка», а не как два отдельных удара.
|
||
{"backhand",0.85f, 1.00f, 0.85f, 0.55f, 0.80f, 0.70f, 0.50f, true},
|
||
// 3. Раскрутка. Широкая, тяжёлая, с долгим восстановлением: в тесноте её
|
||
// просто не дадут начать — сектор не будет чист, и это правильная цена.
|
||
{"spin", 1.70f, 1.75f, 2.30f, 1.50f, 1.40f, 1.70f, 1.35f, false},
|
||
};
|
||
|
||
inline constexpr int MELEE_COMBO_STEPS = int(sizeof(MELEE_COMBO) / sizeof(MELEE_COMBO[0]));
|
||
|
||
inline Tuning g_tune;
|
||
|
||
// Максимально возможная скорость агента НА ХОДУ. Проверка спуска на время полёта
|
||
// пули обязана считать по НЕЙ, иначе рывок союзника не учитывается.
|
||
//
|
||
// Рывок бойца ближнего боя сюда намеренно не входит: он не «манёвр», а прямой
|
||
// отрезок с уже известным направлением, и AllyInterceptsShot проверяет его
|
||
// точно — отдельной экстраполяцией (см. ai/firing_solver.cpp).
|
||
inline float MaxAgentSpeed() { return std::max(g_tune.moveSpeed, g_tune.moveSpeedFast); }
|
||
|
||
// --- таблица параметров для подкрутки в оверлее ------------------------------
|
||
|
||
struct TunableParam
|
||
{
|
||
const char* name;
|
||
float Tuning::* member;
|
||
float step;
|
||
float minV;
|
||
float maxV;
|
||
};
|
||
|
||
inline const TunableParam TUNABLES[] = {
|
||
{"safetyMargin", &Tuning::safetyMargin, 0.01f, 0.0f, 1.0f},
|
||
{"laneHalfWidth", &Tuning::laneHalfWidth, 0.01f, 0.0f, 0.5f},
|
||
{"bodyRadius", &Tuning::bodyRadius, 0.01f, 0.1f, 0.8f},
|
||
{"wLaneAvoid", &Tuning::wLaneAvoid, 0.1f, 0.0f, 10.0f},
|
||
{"laneAvoidStr", &Tuning::laneAvoidStrength,0.25f, 0.0f, 30.0f},
|
||
{"laneReserve", &Tuning::laneReserveScale, 0.05f, 1.0f, 4.0f},
|
||
{"predictSlack", &Tuning::shotPredictSlack, 0.05f, 0.0f, 2.0f},
|
||
{"wSeparation", &Tuning::wSeparation, 0.1f, 0.0f, 10.0f},
|
||
{"steerAccel", &Tuning::steerAccel, 1.0f, 2.0f, 60.0f},
|
||
{"formTurnRate", &Tuning::formTurnRate, 0.5f, 0.5f, 30.0f},
|
||
{"straggleDist", &Tuning::straggleDist, 0.2f, 1.0f, 12.0f},
|
||
{"navStuckTime", &Tuning::navStuckTime, 0.05f, 0.05f, 3.0f},
|
||
{"navStuckSpeed", &Tuning::navStuckSpeed, 0.05f, 0.02f, 1.0f},
|
||
{"navGoalReached", &Tuning::navGoalReached, 0.05f, 0.05f, 2.0f},
|
||
{"moveSpeed", &Tuning::moveSpeed, 0.1f, 0.5f, 10.0f},
|
||
{"moveSpeedFast", &Tuning::moveSpeedFast, 0.1f, 0.5f, 12.0f},
|
||
{"weaponRange", &Tuning::weaponRange, 0.25f, 2.0f, 20.0f},
|
||
{"fireInterval", &Tuning::fireInterval, 0.01f, 0.05f, 2.0f},
|
||
{"bulletSpeed", &Tuning::bulletSpeed, 1.0f, 5.0f, 60.0f},
|
||
{"solveInterval", &Tuning::solveInterval, 0.05f, 0.05f, 2.0f},
|
||
{"commitTime", &Tuning::commitTime, 0.05f, 0.0f, 3.0f},
|
||
{"hysteresis", &Tuning::hysteresis, 1.0f, 0.0f, 100.0f},
|
||
{"cohesionRadius", &Tuning::cohesionRadius, 0.1f, 1.0f, 12.0f},
|
||
{"minEngageDist", &Tuning::minEngageDist, 0.1f, 0.0f, 8.0f},
|
||
{"sLaneClear", &Tuning::sLaneClear, 5.0f, 0.0f, 400.0f},
|
||
{"sCoverage", &Tuning::sCoverage, 1.0f, 0.0f, 200.0f},
|
||
{"sBlockAlly", &Tuning::sBlockAlly, 1.0f, 0.0f, 200.0f},
|
||
{"sDistCost", &Tuning::sDistCost, 0.5f, 0.0f, 100.0f},
|
||
{"sCohesionCost", &Tuning::sCohesionCost, 0.5f, 0.0f, 100.0f},
|
||
{"sTooClose", &Tuning::sTooClose, 2.0f, 0.0f, 300.0f},
|
||
{"postLeash", &Tuning::postLeash, 0.1f, 0.0f, 10.0f},
|
||
{"formSpacing", &Tuning::formationSpacing, 0.05f, 0.4f, 4.0f},
|
||
{"tightSpacing", &Tuning::tightSpacing, 0.02f, 0.1f, 1.0f},
|
||
{"anchorSpeed", &Tuning::anchorSpeed, 0.1f, 0.5f, 12.0f},
|
||
{"anchorSpeedFast",&Tuning::anchorSpeedFast, 0.1f, 0.5f, 16.0f},
|
||
{"anchorLeash", &Tuning::anchorLeash, 0.1f, 0.2f, 12.0f},
|
||
{"anchorLeashMax", &Tuning::anchorLeashMax, 0.1f, 0.4f, 16.0f},
|
||
{"leashDrag", &Tuning::leashDrag, 0.05f, 0.0f, 1.0f},
|
||
{"anchorAccel", &Tuning::anchorAccel, 1.0f, 2.0f, 80.0f},
|
||
// --- натяжение и строй ---
|
||
{"urgeStart", &Tuning::urgeStartFrac, 0.05f, 0.0f, 0.95f},
|
||
{"urgeAttack", &Tuning::urgeAttack, 0.2f, 0.1f, 20.0f},
|
||
{"urgeRelease", &Tuning::urgeRelease, 0.2f, 0.1f, 20.0f},
|
||
{"sprintAt", &Tuning::sprintAt, 0.02f, 0.05f, 1.0f},
|
||
{"holdSpacing", &Tuning::holdSpacing, 0.02f, 0.4f, 2.0f},
|
||
{"sprintSpacing", &Tuning::sprintSpacing, 0.02f, 0.4f, 3.0f},
|
||
{"sprintStretch", &Tuning::sprintStretch, 0.05f, 0.5f, 4.0f},
|
||
{"formMorph", &Tuning::formMorphRate, 0.25f, 0.25f, 20.0f},
|
||
// --- разброс и перезарядка ---
|
||
{"spreadStand", &Tuning::spreadStandDeg, 0.1f, 0.0f, 20.0f},
|
||
{"spreadMove", &Tuning::spreadMoveDeg, 0.1f, 0.0f, 30.0f},
|
||
{"spreadRecoil", &Tuning::spreadRecoilDeg, 0.05f, 0.0f, 10.0f},
|
||
{"spreadRecMax", &Tuning::spreadRecoilMax, 0.1f, 0.0f, 20.0f},
|
||
{"spreadDecay", &Tuning::spreadRecoilDecay,0.2f, 0.1f, 40.0f},
|
||
{"spreadScale", &Tuning::spreadScale, 0.05f, 0.0f, 4.0f},
|
||
{"magazine", &Tuning::magazineRifle, 1.0f, 1.0f, 200.0f},
|
||
{"reloadTime", &Tuning::reloadRifle, 0.05f, 0.1f, 8.0f},
|
||
{"bulletKnock", &Tuning::bulletKnock, 0.05f, 0.0f, 6.0f},
|
||
{"knockResist", &Tuning::knockResistScale, 0.05f, 0.0f, 2.0f},
|
||
// --- живучесть и сложность ---
|
||
{"agentHP", &Tuning::agentHP, 5.0f, 10.0f, 500.0f},
|
||
{"enemyDamage", &Tuning::enemyDamageScale, 0.05f, 0.0f, 4.0f},
|
||
{"enemyRespawn", &Tuning::enemyRespawn, 1.0f, 1.0f, 300.0f},
|
||
{"enemyWindup", &Tuning::enemyWindupScale, 0.05f, 0.25f, 3.0f},
|
||
{"enemyGrabHold", &Tuning::enemyGrabHold, 0.01f, 0.02f, 1.0f},
|
||
{"propYield", &Tuning::propYieldRadius, 0.5f, 0.0f, 64.0f},
|
||
// --- ближний бой ---
|
||
{"meleeReach", &Tuning::meleeReach, 0.05f, 0.4f, 4.0f},
|
||
{"meleeArc", &Tuning::meleeArcDeg, 5.0f, 20.0f, 300.0f},
|
||
{"meleeWindup", &Tuning::meleeWindup, 0.01f, 0.02f, 1.0f},
|
||
{"meleeStrike", &Tuning::meleeStrike, 0.01f, 0.02f, 1.0f},
|
||
{"meleeRecover", &Tuning::meleeRecover, 0.01f, 0.0f, 1.5f},
|
||
{"meleeInterval", &Tuning::meleeInterval, 0.02f, 0.1f, 3.0f},
|
||
{"meleeDamage", &Tuning::meleeDamage, 5.0f, 1.0f, 999.0f},
|
||
{"meleeLunge", &Tuning::meleeLungeSpeed, 0.1f, 0.0f, 12.0f},
|
||
{"meleeKnock", &Tuning::meleeKnockback, 0.1f, 0.0f, 12.0f},
|
||
{"meleeCohesion", &Tuning::meleeCohesionRadius, 0.2f, 1.0f, 20.0f},
|
||
{"meleeAllyMargin",&Tuning::meleeAllyMargin, 0.02f, 0.0f, 1.0f},
|
||
{"meleePredict", &Tuning::meleeStartPredict,0.1f, 0.0f, 3.0f},
|
||
{"meleeReserve", &Tuning::meleeReserveFrac, 0.05f, 0.0f, 1.5f},
|
||
{"meleeShake", &Tuning::meleeShake, 0.1f, 0.0f, 6.0f},
|
||
// --- хоррор-часть ---
|
||
{"sightRadius", &Tuning::sightRadius, 0.25f, 2.0f, 24.0f},
|
||
{"sightCone", &Tuning::sightConeDeg, 5.0f, 20.0f, 360.0f},
|
||
{"sightPersonal", &Tuning::sightPersonalRadius, 0.1f, 0.5f, 10.0f},
|
||
{"fogMemory", &Tuning::fogMemoryLevel, 0.02f, 0.0f, 1.0f},
|
||
{"fogReveal", &Tuning::fogEntityReveal, 0.02f, 0.0f, 1.0f},
|
||
{"flashPower", &Tuning::flashPower, 0.05f, 0.0f, 2.0f},
|
||
{"lightGamma", &Tuning::lightGamma, 0.05f, 0.3f, 4.0f},
|
||
{"lightGain", &Tuning::lightMaxGain, 0.05f, 0.2f, 3.0f},
|
||
{"fxIntensity", &Tuning::fxIntensity, 0.1f, 0.0f, 4.0f},
|
||
{"tracerLength", &Tuning::tracerLength, 0.1f, 0.1f, 6.0f},
|
||
{"grainTense", &Tuning::grainTense, 1.0f, 0.0f, 100.0f},
|
||
{"vigDepthTense", &Tuning::vigDepthTense, 0.05f, 0.0f, 1.0f},
|
||
{"dreadRadius", &Tuning::dreadRadius, 0.5f, 1.0f, 20.0f},
|
||
{"shakePerShot", &Tuning::shakePerShot, 0.05f, 0.0f, 4.0f},
|
||
// --- кровь, дымка, пепел ---
|
||
{"bloodAmount", &Tuning::bloodAmount, 0.05f, 0.0f, 3.0f},
|
||
{"bloodOpacity", &Tuning::bloodOpacity, 0.02f, 0.0f, 1.0f},
|
||
{"bloodDrops", &Tuning::bloodDrops, 1.0f, 0.0f, 16.0f},
|
||
{"hazeAmount", &Tuning::hazeAmount, 0.02f, 0.0f, 2.0f},
|
||
{"hazeScale", &Tuning::hazeScale, 0.05f, 0.2f, 4.0f},
|
||
{"hazeDrift", &Tuning::hazeDrift, 0.01f, 0.0f, 1.0f},
|
||
{"hazeGate", &Tuning::hazeGate, 1.0f, 0.0f, 128.0f},
|
||
{"ashDensity", &Tuning::ashDensity, 0.05f, 0.0f, 4.0f},
|
||
{"ashSpeed", &Tuning::ashSpeed, 0.05f, 0.0f, 3.0f},
|
||
{"ashBright", &Tuning::ashBrightness, 0.05f, 0.0f, 2.0f},
|
||
{"ashLift", &Tuning::ashLiftPx, 1.0f, 0.0f, 40.0f},
|
||
};
|
||
|
||
inline constexpr int TUNABLE_COUNT = int(sizeof(TUNABLES) / sizeof(TUNABLES[0]));
|