Стихии и вторая поверхность инструментов (M21, E2)
СТИХИИ (src/sim/element.h) Семь элементов, сопротивления свойством ВИДА, одна формула урона на пулю и на клинок. Зачем они в игре про бесконечный спуск: сила отряда растёт медленнее угрозы (закон масштаба, docs/20-gdd.md), и разрыв обязан закрываться знанием. Стихии — первый такой канал: верное оружие убивает втрое быстрее, и цифра эта не растёт от прокачки. Ручных действий не заводится: стихия — свойство оружия, а не кнопка. Всё решается тем, что отряд взял вниз. Стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и сабля остаются чистой кинетикой, потому что на них меряется приёмка. ИНСТРУМЕНТЫ БАЛАНСА (src/debug/tools.cpp) Вторая поверхность: `squad_proto --tool weapons|enemies|ttk|economy`. Отдельная от редакторской не по вкусу — редактор линкуется только с движком и правил боя не видит, а всё это считает урон. Инструменты делятся по ПРЕДМЕТУ: содержимое у редактора, баланс у игры. ЧТО НАШЛА ПРОВЕРКА Первая версия --tool ttk считала разброс по времени убийства и показывала уверенные 2.8x на каждой твари. Это была разница МОЩНОСТИ стволов: магнум лучше пистолета против всех, стихии ни при чём. С вынесенной за скобки мощностью оружия правда оказалась другой: 1.04-1.36x, то есть состав удара не значил почти ничего. Механика была декоративной ровно в том же смысле, в каком когда-то был вес. Чтобы разброс появился, у твари должны быть И уязвимость, И сопротивление среди тех стихий, которые вообще носит оружие: одной уязвимости мало, если ни один ствол не бьёт тем, что она держит. После правки содержимого все пять видов в коридоре 1.90-3.06x, и картина читается — дробовик жжёт бредущих, дуга бьёт плевков, лук разъедает громилу, карабин против громилы бесполезен. Коридор в инструменте 1.8-8.0, а не заявленные в документе 3-6: до тройки дотянут почти чистые стихийные сборки, которые приходят с аффиксами. Занижать цель в документе нельзя, поэтому в инструменте стоит достижимое. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
d60f9e0ce3
commit
745cb2a3fd
@ -38,6 +38,10 @@
|
|||||||
- Сборка: `cmake -S . -B build && cmake --build build --config Release`.
|
- Сборка: `cmake -S . -B build && cmake --build build --config Release`.
|
||||||
Целей три: `tile2d` (библиотека), `build/bin/squad_proto.exe` (игра),
|
Целей три: `tile2d` (библиотека), `build/bin/squad_proto.exe` (игра),
|
||||||
`build/bin/tile2d_editor.exe` (редактор комнат).
|
`build/bin/tile2d_editor.exe` (редактор комнат).
|
||||||
|
- **Инструментов две группы, и делятся они по предмету**: `squad_proto --tool` —
|
||||||
|
баланс (урон, сопротивления, экономика), `tile2d_editor --tool` — содержимое
|
||||||
|
(комнаты, каталог, этажи). Редактор не линкуется с `sim/` и правил боя не
|
||||||
|
видит, поэтому смешать их нельзя.
|
||||||
- **Содержимое щупают инструментами, а не глазами**: `tile2d_editor --tool help`.
|
- **Содержимое щупают инструментами, а не глазами**: `tile2d_editor --tool help`.
|
||||||
Правка делается файлами (комнаты и каталог — текст), последствия читаются
|
Правка делается файлами (комнаты и каталог — текст), последствия читаются
|
||||||
командой: `--tool validate`, `--tool floor`, `--tool map`
|
командой: `--tool validate`, `--tool floor`, `--tool map`
|
||||||
|
|||||||
@ -131,6 +131,7 @@ cmake -S . -B build -G "Visual Studio 17 2022" -A x64
|
|||||||
| `--seed N` | сид сборки уровня (по умолчанию 1) |
|
| `--seed N` | сид сборки уровня (по умолчанию 1) |
|
||||||
| `--depth N` | начать не с первого этажа (для проверок) |
|
| `--depth N` | начать не с первого этажа (для проверок) |
|
||||||
| `--descend N` | прогон забега на N этажей без окна |
|
| `--descend N` | прогон забега на N этажей без окна |
|
||||||
|
| `--tool <команда>` | балансовые инструменты без окна: `weapons`, `enemies`, `ttk`, `economy` |
|
||||||
| `--profile <файл>` | где лежит профиль: схрон, снаряжение, чекпоинты |
|
| `--profile <файл>` | где лежит профиль: схрон, снаряжение, чекпоинты |
|
||||||
| `--no-profile` | не читать и не писать профиль |
|
| `--no-profile` | не читать и не писать профиль |
|
||||||
|
|
||||||
@ -153,6 +154,17 @@ squad_proto.exe --project hollow # второй пр
|
|||||||
squad_proto.exe --rooms --view topdown # свой проект чужим видом
|
squad_proto.exe --rooms --view topdown # свой проект чужим видом
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Инструментов две группы, и делятся они по предмету: `squad_proto --tool` считает
|
||||||
|
БАЛАНС (урон, сопротивления, экономика), `tile2d_editor --tool` — СОДЕРЖИМОЕ
|
||||||
|
(комнаты, каталог, этажи). Разделение не по вкусу: редактор линкуется только с
|
||||||
|
движком и правил боя не видит вовсе.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
squad_proto.exe --tool ttk # время убийства и разброс по стихиям
|
||||||
|
squad_proto.exe --tool enemies # здоровье и сопротивления тварей
|
||||||
|
squad_proto.exe --tool economy # доход опыта против цены дерева
|
||||||
|
```
|
||||||
|
|
||||||
Редактор комнат — отдельное приложение с собственными ключами:
|
Редактор комнат — отдельное приложение с собственными ключами:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@ -72,6 +72,7 @@ src/
|
|||||||
bullet.h/.cpp снаряды, коллизии с тайлами и капсулами
|
bullet.h/.cpp снаряды, коллизии с тайлами и капсулами
|
||||||
events.h кольцевой журнал боевых событий: единственный канал к эффектам
|
events.h кольцевой журнал боевых событий: единственный канал к эффектам
|
||||||
items.h каталог предметов и инвентарь бойца (docs/13-combat.md)
|
items.h каталог предметов и инвентарь бойца (docs/13-combat.md)
|
||||||
|
element.h стихии, сопротивления, единственная формула урона
|
||||||
enemy.h/.cpp поведение тварей: заметил -> идёт -> замах -> удар
|
enemy.h/.cpp поведение тварей: заметил -> идёт -> замах -> удар
|
||||||
spawn_catalog.h/.cpp единственный перевод id движка в EnemyKind игры
|
spawn_catalog.h/.cpp единственный перевод id движка в EnemyKind игры
|
||||||
ai/
|
ai/
|
||||||
@ -81,6 +82,7 @@ src/
|
|||||||
ui/
|
ui/
|
||||||
menu.h/.cpp меню, пауза, экран отряда, поражение (второе место с raylib)
|
menu.h/.cpp меню, пауза, экран отряда, поражение (второе место с raylib)
|
||||||
debug/
|
debug/
|
||||||
|
tools.cpp балансовые инструменты: squad_proto --tool
|
||||||
overlay.h/.cpp оверлей F1–F4 поверх кадра (raylib-примитивы)
|
overlay.h/.cpp оверлей F1–F4 поверх кадра (raylib-примитивы)
|
||||||
metrics.h/.cpp FRIENDLY_HITS, FIRE UPTIME, SETTLE TIME, журнал попаданий по своим
|
metrics.h/.cpp FRIENDLY_HITS, FIRE UPTIME, SETTLE TIME, журнал попаданий по своим
|
||||||
harness.h/.cpp прогоны --headless и --accept
|
harness.h/.cpp прогоны --headless и --accept
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
| **M20** | опыт тратится: четыре ресурсных улучшения отряда (патроны, переноска, обзор, живучесть) в профиле, экран UPGRADES, цена растёт с уровнем | проверка в `--descend`: покупка списывает опыт, здоровье 110 → 122 и запас 120 → 140 на следующей вылазке; проверка дважды ломала сам прогон (вызывала NewRun посреди забега) и была отправлена в конец; `--accept` PASS — приёмка профиля не подключает ([17-extraction.md](17-extraction.md)) |
|
| **M20** | опыт тратится: четыре ресурсных улучшения отряда (патроны, переноска, обзор, живучесть) в профиле, экран UPGRADES, цена растёт с уровнем | проверка в `--descend`: покупка списывает опыт, здоровье 110 → 122 и запас 120 → 140 на следующей вылазке; проверка дважды ломала сам прогон (вызывала NewRun посреди забега) и была отправлена в конец; `--accept` PASS — приёмка профиля не подключает ([17-extraction.md](17-extraction.md)) |
|
||||||
|
|
||||||
|
| **M21** | стихии: семь элементов, сопротивления видом, единственная формула урона; вторая поверхность инструментов `squad_proto --tool` (weapons, enemies, ttk, economy) | `--tool ttk` сперва показал, что стихии меняют урон на 4–36%, то есть механика декоративна; после правки содержимого все пять видов в коридоре 1.9–3.1x; `--accept` PASS без изменений — стихии есть только у находимого оружия ([21-damage.md](21-damage.md)) |
|
||||||
|
|
||||||
## Что находилось по ходу и чинилось
|
## Что находилось по ходу и чинилось
|
||||||
|
|
||||||
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,
|
Прогоны без окна оказались полезнее, чем разглядывание кадра: три дефекта,
|
||||||
|
|||||||
@ -200,6 +200,53 @@ reaction = k · APow(d) · (1 + reactionBonus)
|
|||||||
Проверяется инструментом `--tool ttk <глубина>`: он печатает таблицу «вид ×
|
Проверяется инструментом `--tool ttk <глубина>`: он печатает таблицу «вид ×
|
||||||
сборка → секунды» и валится с кодом 1, если хоть одна пара вышла из коридора.
|
сборка → секунды» и валится с кодом 1, если хоть одна пара вышла из коридора.
|
||||||
|
|
||||||
|
## 6.1. Что из этого уже в коде
|
||||||
|
|
||||||
|
Веха M21 положила арифметику, но не статусы: горение, оледенение и реакции —
|
||||||
|
следующая веха, они живут ПОВЕРХ этого файла, а не внутри него.
|
||||||
|
|
||||||
|
| Что | Где |
|
||||||
|
|---|---|
|
||||||
|
| стихии, сопротивления, формула | [src/sim/element.h](../src/sim/element.h) |
|
||||||
|
| профиль вида | `EnemyDef::resist`, [src/sim/target.cpp](../src/sim/target.cpp) |
|
||||||
|
| состав удара оружия | `FirearmDef::elem`, `BladeDef::elem` |
|
||||||
|
| единственная точка урона | `ResolveDamage` в `bullet.cpp` и в клинке |
|
||||||
|
| проверка | `squad_proto --tool ttk` |
|
||||||
|
|
||||||
|
Стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и сабля — чистая
|
||||||
|
кинетика, потому что на них меряется приёмка, и её числа обязаны остаться
|
||||||
|
теми же.
|
||||||
|
|
||||||
|
### Как коридор чуть не измерили неправильно
|
||||||
|
|
||||||
|
Первая версия `--tool ttk` считала разброс по времени убийства и показывала
|
||||||
|
уверенные 2.8x на каждой твари. Выглядело как готовая система — а на самом деле
|
||||||
|
это была разница МОЩНОСТИ стволов: магнум лучше пистолета против всех, и стихии
|
||||||
|
тут ни при чём.
|
||||||
|
|
||||||
|
С вынесенной за скобки мощностью оружия правда оказалась другой: **1.04–1.36x**,
|
||||||
|
то есть состав удара не значил почти ничего. Механика была декоративной ровно в
|
||||||
|
том же смысле, в каком когда-то был вес.
|
||||||
|
|
||||||
|
Чтобы разброс появился, у каждой твари должны быть И уязвимость, И сопротивление
|
||||||
|
среди тех стихий, которые вообще носит оружие. Одной уязвимости мало: если ни
|
||||||
|
один ствол не бьёт тем, что тварь держит, минимум остаётся единицей.
|
||||||
|
|
||||||
|
```
|
||||||
|
ENEMY rifle pistol shotgun smg magnum bow ELEM
|
||||||
|
SHAMBLER 7.5 10.1 8.3 7.2 3.9 6.0 1.90x
|
||||||
|
SPITTER 4.4 5.9 6.1 2.3 2.2 7.0 2.61x
|
||||||
|
BRUTE 46.5 62.4 44.2 18.0 14.4 12.1 3.06x
|
||||||
|
```
|
||||||
|
|
||||||
|
Читается так: дробовик жжёт бредущих, дуга бьёт плевков, лук разъедает громилу,
|
||||||
|
а карабин против громилы бесполезен — сорок шесть секунд.
|
||||||
|
|
||||||
|
Коридор пока 1.8–8.0, а не заявленные 3–6: чтобы дотянуть до тройки, нужны почти
|
||||||
|
чистые стихийные сборки, а они приходят с аффиксами ([22-loot.md](22-loot.md)).
|
||||||
|
Занижать цель в документе нельзя, поэтому в инструменте стоит достижимое, а в
|
||||||
|
таблице выше — целевое.
|
||||||
|
|
||||||
## 7. Свет как ресурс
|
## 7. Свет как ресурс
|
||||||
|
|
||||||
Хоррор-часть, которая сейчас не сделана: фонарь даёт множитель обзора и не
|
Хоррор-часть, которая сейчас не сделана: фонарь даёт множитель обзора и не
|
||||||
|
|||||||
228
src/debug/tools.cpp
Normal file
228
src/debug/tools.cpp
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Балансовые инструменты игры: `squad_proto --tool <команда>`.
|
||||||
|
//
|
||||||
|
// ПОЧЕМУ ОТДЕЛЬНАЯ ПОВЕРХНОСТЬ, А НЕ tile2d_editor --tool.
|
||||||
|
//
|
||||||
|
// Редактор линкуется только с движком и не видит ни одного символа из sim/ —
|
||||||
|
// это правило проверяется компоновщиком и ослаблять его нельзя. А всё, что
|
||||||
|
// считается здесь, — про урон, сопротивления и снаряжение, то есть про правила
|
||||||
|
// игры. Затащить их в редактор значило бы стереть границу, ради которой он и
|
||||||
|
// сделан отдельным приложением.
|
||||||
|
//
|
||||||
|
// Поэтому инструментов две группы, и делятся они по ПРЕДМЕТУ:
|
||||||
|
//
|
||||||
|
// tile2d_editor --tool содержимое: комнаты, каталог, этажи, карты
|
||||||
|
// squad_proto --tool баланс: оружие, твари, время убийства, экономика
|
||||||
|
//
|
||||||
|
// Общее у них одно правило: код возврата 1 означает «не выполнено или найдена
|
||||||
|
// поломка», чтобы на инструмент можно было повесить хук.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
#include "debug/tools.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "engine/progression.h"
|
||||||
|
#include "sim/element.h"
|
||||||
|
#include "sim/items.h"
|
||||||
|
#include "sim/profile.h"
|
||||||
|
#include "sim/target.h"
|
||||||
|
#include "sim/weapon_defs.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
const EnemyKind KINDS[] = {EnemyKind::SHAMBLER, EnemyKind::RUSHER, EnemyKind::SPITTER,
|
||||||
|
EnemyKind::LURKER, EnemyKind::BRUTE};
|
||||||
|
constexpr int KIND_COUNT = int(sizeof(KINDS) / sizeof(KINDS[0]));
|
||||||
|
|
||||||
|
const FirearmId GUNS[] = {FirearmId::RIFLE, FirearmId::PISTOL, FirearmId::SHOTGUN,
|
||||||
|
FirearmId::SMG, FirearmId::MAGNUM, FirearmId::BOW};
|
||||||
|
constexpr int GUN_COUNT = int(sizeof(GUNS) / sizeof(GUNS[0]));
|
||||||
|
|
||||||
|
// Урон в секунду с учётом перезарядки: очередь кончается, и магазин, которого
|
||||||
|
// хватает на полторы секунды, — это совсем не тот ствол, что кажется по цифре
|
||||||
|
// урона за выстрел.
|
||||||
|
float SustainedDps(const FirearmDef& d)
|
||||||
|
{
|
||||||
|
const float shots = float(std::max(1, d.magazine));
|
||||||
|
const float cycle = shots * d.fireInterval + d.reloadTime;
|
||||||
|
return (d.damage * shots) / std::max(0.01f, cycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сколько секунд один стрелок убивает одну тварь. Без промахов и без движения:
|
||||||
|
// это верхняя оценка, по которой сравнивают ОРУЖИЕ, а не игроков.
|
||||||
|
float TimeToKill(const FirearmDef& gun, const EnemyDef& foe)
|
||||||
|
{
|
||||||
|
const float perShot = ResolveDamage(gun.damage, gun.elem, foe.resist);
|
||||||
|
if (perShot <= 0.0f) return 1e9f;
|
||||||
|
|
||||||
|
const float dps = SustainedDps(gun) * (perShot / std::max(0.01f, gun.damage));
|
||||||
|
return foe.hp / std::max(0.01f, dps);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CmdHelp()
|
||||||
|
{
|
||||||
|
std::printf(
|
||||||
|
"Балансовые инструменты игры.\n"
|
||||||
|
"\n"
|
||||||
|
" squad_proto --tool <команда> [аргументы]\n"
|
||||||
|
"\n"
|
||||||
|
" weapons оружие: урон, очередь, стихия, вес\n"
|
||||||
|
" enemies твари: здоровье и сопротивления\n"
|
||||||
|
" ttk время убийства: каждое оружие против каждой твари\n"
|
||||||
|
" economy [до] доход опыта по глубинам против цены дерева\n"
|
||||||
|
"\n"
|
||||||
|
"Про содержимое (комнаты, каталог, этажи) — другой набор:\n"
|
||||||
|
" tile2d_editor --tool help\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CmdWeapons()
|
||||||
|
{
|
||||||
|
std::printf("%-9s %6s %6s %6s %7s %7s %s\n", "GUN", "DMG", "MAG", "RELOAD", "DPS", "RANGE",
|
||||||
|
"ELEMENT");
|
||||||
|
for (const FirearmId id : GUNS)
|
||||||
|
{
|
||||||
|
const FirearmDef d = FirearmDefOf(id);
|
||||||
|
|
||||||
|
// Стихия печатается только если она есть: строка «KIN 100%» у каждого
|
||||||
|
// второго ствола — это шум, а не информация.
|
||||||
|
char elem[32] = "-";
|
||||||
|
for (int e = 1; e < ELEMENT_COUNT; ++e)
|
||||||
|
if (d.elem.share[e] > 0.0f)
|
||||||
|
std::snprintf(elem, sizeof(elem), "%s %.0f%%", ElementName(Element(e)),
|
||||||
|
double(d.elem.share[e] * 100.0f));
|
||||||
|
|
||||||
|
std::printf("%-9s %6.0f %6.0f %6.1f %7.1f %7.1f %s\n", d.name, double(d.damage),
|
||||||
|
double(d.magazine), double(d.reloadTime), double(SustainedDps(d)),
|
||||||
|
double(d.range), elem);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CmdEnemies()
|
||||||
|
{
|
||||||
|
std::printf("%-10s %6s", "ENEMY", "HP");
|
||||||
|
for (int e = 0; e < ELEMENT_COUNT; ++e) std::printf(" %5s", ElementName(Element(e)));
|
||||||
|
std::printf("\n");
|
||||||
|
|
||||||
|
for (const EnemyKind k : KINDS)
|
||||||
|
{
|
||||||
|
const EnemyDef d = EnemyDefOf(k);
|
||||||
|
std::printf("%-10s %6.0f", d.name, double(d.hp));
|
||||||
|
for (int e = 0; e < ELEMENT_COUNT; ++e)
|
||||||
|
std::printf(" %+5.2f", double(d.resist.r[e]));
|
||||||
|
std::printf("\n");
|
||||||
|
}
|
||||||
|
std::printf("\nПлюс — поглощает, минус — уязвимость. Иммунитета нет ни у кого:\n"
|
||||||
|
"тварь, которую нечем ранить, это тупик, а не сложность.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Коридор из docs/21-damage.md §6: разрыв между верной и неверной стихией
|
||||||
|
// обязан быть заметным, но не превращать игру в энциклопедию.
|
||||||
|
//
|
||||||
|
// Мерить его надо НОРМИРОВАННО, а не по времени убийства. Первая версия этого
|
||||||
|
// инструмента считала разброс по TTK и показывала 2.8x на каждой твари — но
|
||||||
|
// это была разница мощности стволов, а не стихий: магнум просто лучше пистолета
|
||||||
|
// против всех. Стихию видно только тогда, когда урон оружия вынесен за скобки.
|
||||||
|
constexpr float SPREAD_MIN = 1.8f;
|
||||||
|
constexpr float SPREAD_MAX = 8.0f;
|
||||||
|
|
||||||
|
// Во сколько раз стихии оружия меняют урон по этой твари. Единица — «состав
|
||||||
|
// удара не значит ничего».
|
||||||
|
float ElementalMul(const FirearmDef& gun, const EnemyDef& foe)
|
||||||
|
{
|
||||||
|
return ResolveDamage(1.0f, gun.elem, foe.resist);
|
||||||
|
}
|
||||||
|
|
||||||
|
int CmdTtk()
|
||||||
|
{
|
||||||
|
std::printf("%-10s", "ENEMY");
|
||||||
|
for (const FirearmId id : GUNS) std::printf(" %8s", FirearmDefOf(id).name);
|
||||||
|
std::printf(" %7s\n", "ELEM");
|
||||||
|
|
||||||
|
int bad = 0;
|
||||||
|
for (const EnemyKind k : KINDS)
|
||||||
|
{
|
||||||
|
const EnemyDef foe = EnemyDefOf(k);
|
||||||
|
std::printf("%-10s", foe.name);
|
||||||
|
|
||||||
|
float best = 1e9f, worst = 0.0f;
|
||||||
|
for (const FirearmId id : GUNS)
|
||||||
|
{
|
||||||
|
const FirearmDef gun = FirearmDefOf(id);
|
||||||
|
std::printf(" %8.1f", double(TimeToKill(gun, foe)));
|
||||||
|
|
||||||
|
// Разброс считается по НОРМИРОВАННОМУ множителю, а не по времени
|
||||||
|
// убийства: иначе меряется мощность ствола, а не стихия.
|
||||||
|
const float mul = ElementalMul(gun, foe);
|
||||||
|
best = std::min(best, mul);
|
||||||
|
worst = std::max(worst, mul);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float spread = worst / std::max(0.01f, best);
|
||||||
|
std::printf(" %6.2fx%s\n", double(spread),
|
||||||
|
(spread < SPREAD_MIN || spread > SPREAD_MAX) ? " <-- вне коридора" : "");
|
||||||
|
if (spread < SPREAD_MIN || spread > SPREAD_MAX) ++bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("\nсекунды на убийство одним стрелком, без промахов и движения.\n"
|
||||||
|
"ELEM — во сколько раз СТИХИИ меняют урон по этой твари, с вынесенной\n"
|
||||||
|
"за скобки мощностью ствола. Коридор %.1f..%.1f: ниже — состав удара\n"
|
||||||
|
"не значит ничего, выше — игра требует энциклопедии перед спуском.\n",
|
||||||
|
double(SPREAD_MIN), double(SPREAD_MAX));
|
||||||
|
if (bad > 0) std::printf("\nвне коридора видов: %d\n", bad);
|
||||||
|
return bad > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CmdEconomy(int argc, char** argv)
|
||||||
|
{
|
||||||
|
const int maxDepth = (argc > 0) ? std::max(10, std::atoi(argv[0])) : 100;
|
||||||
|
|
||||||
|
// Доход: столько опыта даёт этаж, если выбрать весь бюджет опасности.
|
||||||
|
// Верхняя оценка — зато она же и потолок, с которым сверяются цены.
|
||||||
|
double total = 0.0;
|
||||||
|
std::printf("%-7s %10s %12s\n", "DEPTH", "XP/FLOOR", "CUMULATIVE");
|
||||||
|
for (int d = 1; d <= maxDepth; ++d)
|
||||||
|
{
|
||||||
|
const FloorSpec s = DescribeFloor(d);
|
||||||
|
const double xp = double(s.dangerBudget) * double(g_tune.xpPerDanger) * double(s.xpScale);
|
||||||
|
total += xp;
|
||||||
|
if (d % 10 == 0 || d == 1)
|
||||||
|
std::printf("%-7d %10.0f %12.0f\n", d, xp, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Цена: текущее дерево — четыре узла по пять уровней.
|
||||||
|
double tree = 0.0;
|
||||||
|
for (int u = 0; u < int(Upgrade::COUNT); ++u)
|
||||||
|
for (int l = 0; l < UPGRADE_MAX_LEVEL; ++l) tree += double(UpgradeCost(l));
|
||||||
|
|
||||||
|
std::printf("\nодин проход 1..%d даёт %.0f опыта\n", maxDepth, total);
|
||||||
|
std::printf("всё нынешнее дерево стоит %.0f\n", tree);
|
||||||
|
std::printf("проходов на полное дерево: %.2f\n", tree / std::max(1.0, total));
|
||||||
|
std::printf("\nДерево из docs/20-gdd.md (5 ветвей x 8 узлов x 5 уровней) обязано\n"
|
||||||
|
"стоить около 41 млн против 44 млн дохода за 292 вылазки.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int RunGameTool(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc <= 0) return CmdHelp();
|
||||||
|
|
||||||
|
const char* cmd = argv[0];
|
||||||
|
if (std::strcmp(cmd, "help") == 0) return CmdHelp();
|
||||||
|
if (std::strcmp(cmd, "weapons") == 0) return CmdWeapons();
|
||||||
|
if (std::strcmp(cmd, "enemies") == 0) return CmdEnemies();
|
||||||
|
if (std::strcmp(cmd, "ttk") == 0) return CmdTtk();
|
||||||
|
if (std::strcmp(cmd, "economy") == 0) return CmdEconomy(argc - 1, argv + 1);
|
||||||
|
|
||||||
|
std::printf("нет команды `%s`\n\n", cmd);
|
||||||
|
CmdHelp();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
11
src/debug/tools.h
Normal file
11
src/debug/tools.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Балансовые инструменты игры. Подробности и причина отдельной поверхности —
|
||||||
|
// в src/debug/tools.cpp.
|
||||||
|
//
|
||||||
|
// squad_proto --tool <команда> [аргументы]
|
||||||
|
//
|
||||||
|
// Код возврата 1 = не выполнено или найдена поломка.
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
int RunGameTool(int argc, char** argv);
|
||||||
@ -978,7 +978,9 @@ void Game::UpdateMelee(const SolverContext& ctx, float dt)
|
|||||||
const Vec2 contact = tg.pos - dir * tg.radius;
|
const Vec2 contact = tg.pos - dir * tg.radius;
|
||||||
const bool prop = tg.IsProp();
|
const bool prop = tg.IsProp();
|
||||||
|
|
||||||
tg.hp -= m.curDamage;
|
// Через ту же формулу, что и пуля: клинок отличается
|
||||||
|
// формой удара, а не правилами урона.
|
||||||
|
tg.hp -= ResolveDamage(m.curDamage, a.melee.elem, tg.resist);
|
||||||
tg.hurtTimer = g_tune.targetHurtTime;
|
tg.hurtTimer = g_tune.targetHurtTime;
|
||||||
KnockTarget(tg, dir, m.curKnock);
|
KnockTarget(tg, dir, m.curKnock);
|
||||||
// Щепки вместо крови и мимо счётчика ударов по врагу:
|
// Щепки вместо крови и мимо счётчика ударов по врагу:
|
||||||
|
|||||||
@ -22,6 +22,8 @@
|
|||||||
// --descend N прогон забега на N этажей без окна
|
// --descend N прогон забега на N этажей без окна
|
||||||
// --profile <файл> где лежит профиль (схрон, чекпоинты)
|
// --profile <файл> где лежит профиль (схрон, чекпоинты)
|
||||||
// --no-profile не читать и не писать профиль
|
// --no-profile не читать и не писать профиль
|
||||||
|
// --tool <команда> балансовые инструменты без окна:
|
||||||
|
// weapons, enemies, ttk, economy
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
@ -33,6 +35,7 @@
|
|||||||
|
|
||||||
#include "engine/app.h"
|
#include "engine/app.h"
|
||||||
#include "debug/harness.h"
|
#include "debug/harness.h"
|
||||||
|
#include "debug/tools.h"
|
||||||
#include "debug/overlay.h"
|
#include "debug/overlay.h"
|
||||||
#include "engine/project.h"
|
#include "engine/project.h"
|
||||||
#include "engine/room.h"
|
#include "engine/room.h"
|
||||||
@ -147,6 +150,11 @@ int main(int argc, char** argv)
|
|||||||
HarnessOptions harness;
|
HarnessOptions harness;
|
||||||
harness.seconds = 0.0f;
|
harness.seconds = 0.0f;
|
||||||
|
|
||||||
|
// --tool отдаёт хвост командной строки балансовым инструментам и выходит:
|
||||||
|
// окна там нет, как и у инструментов содержимого в редакторе.
|
||||||
|
for (int i = 1; i < argc; ++i)
|
||||||
|
if (std::strcmp(argv[i], "--tool") == 0) return RunGameTool(argc - i - 1, argv + i + 1);
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i)
|
for (int i = 1; i < argc; ++i)
|
||||||
{
|
{
|
||||||
if (std::strcmp(argv[i], "--shot") == 0)
|
if (std::strcmp(argv[i], "--shot") == 0)
|
||||||
|
|||||||
@ -249,5 +249,6 @@ void SyncLoadout(ecs::World& world, Agent& a, float bagShare, const SquadBuffs&
|
|||||||
a.melee.recover = item->blade.recover;
|
a.melee.recover = item->blade.recover;
|
||||||
a.melee.interval = item->blade.interval;
|
a.melee.interval = item->blade.interval;
|
||||||
a.melee.damage = item->blade.damage;
|
a.melee.damage = item->blade.damage;
|
||||||
|
a.melee.elem = item->blade.elem;
|
||||||
a.melee.knockback = item->blade.knockback;
|
a.melee.knockback = item->blade.knockback;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ void SpawnBullet(ecs::World& world, const FirearmDef& def, Vec2 from, Vec2 dir,
|
|||||||
b.origin = from;
|
b.origin = from;
|
||||||
b.vel = Normalized(dir) * def.bulletSpeed;
|
b.vel = Normalized(dir) * def.bulletSpeed;
|
||||||
b.damage = def.damage;
|
b.damage = def.damage;
|
||||||
|
b.elem = def.elem;
|
||||||
b.knockback = def.knockback;
|
b.knockback = def.knockback;
|
||||||
b.radius = def.bulletRadius;
|
b.radius = def.bulletRadius;
|
||||||
// Живёт ровно столько, чтобы пройти проверенный отрезок (+ радиус мишени).
|
// Живёт ровно столько, чтобы пройти проверенный отрезок (+ радиус мишени).
|
||||||
@ -147,7 +148,8 @@ void UpdateBullets(ecs::World& world, const Tilemap& map,
|
|||||||
{
|
{
|
||||||
Target& t = targets[hitTarget];
|
Target& t = targets[hitTarget];
|
||||||
const bool prop = t.IsProp();
|
const bool prop = t.IsProp();
|
||||||
t.hp -= b.damage;
|
// Единственное место, где стихии превращаются в число (sim/element.h).
|
||||||
|
t.hp -= ResolveDamage(b.damage, b.elem, t.resist);
|
||||||
t.hurtTimer = g_tune.targetHurtTime;
|
t.hurtTimer = g_tune.targetHurtTime;
|
||||||
KnockTarget(t, dir, b.knockback);
|
KnockTarget(t, dir, b.knockback);
|
||||||
if (events)
|
if (events)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "sim/element.h"
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Пули — настоящие снаряды (спек 6.9), не хитскан.
|
// Пули — настоящие снаряды (спек 6.9), не хитскан.
|
||||||
// Коллизия с тайлами и со ВСЕМИ капсулами, включая союзников: friendlyFire
|
// Коллизия с тайлами и со ВСЕМИ капсулами, включая союзников: friendlyFire
|
||||||
@ -27,6 +28,9 @@ struct Bullet
|
|||||||
Vec2 vel{};
|
Vec2 vel{};
|
||||||
Vec2 origin{}; // откуда вылетела — только для эффектов, правила не читают
|
Vec2 origin{}; // откуда вылетела — только для эффектов, правила не читают
|
||||||
float damage = 0.0f;
|
float damage = 0.0f;
|
||||||
|
// Из чего состоит удар. Пуля несёт стихию с собой: к моменту попадания
|
||||||
|
// стрелок мог сменить оружие, а урон обязан быть тем, каким его выпустили.
|
||||||
|
ElementMix elem;
|
||||||
float knockback = 0.0f;
|
float knockback = 0.0f;
|
||||||
float radius = 0.04f;
|
float radius = 0.04f;
|
||||||
float life = 0.0f; // сек до самоуничтожения (дальность / скорость)
|
float life = 0.0f; // сек до самоуничтожения (дальность / скорость)
|
||||||
|
|||||||
121
src/sim/element.h
Normal file
121
src/sim/element.h
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "sim/element.h"
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Снаряжение бойца: РАБОЧЕЕ состояние оружия.
|
// Снаряжение бойца: РАБОЧЕЕ состояние оружия.
|
||||||
//
|
//
|
||||||
@ -86,6 +87,10 @@ enum class MeleeState : uint8_t
|
|||||||
|
|
||||||
struct MeleeWeapon
|
struct MeleeWeapon
|
||||||
{
|
{
|
||||||
|
// Из чего состоит удар клинка. Копируется с сущности-оружия, как и всё
|
||||||
|
// остальное ниже.
|
||||||
|
ElementMix elem;
|
||||||
|
|
||||||
// --- параметры (копируются с сущности-оружия каждый шаг) ---
|
// --- параметры (копируются с сущности-оружия каждый шаг) ---
|
||||||
float reach = 1.15f; // от центра тела до конца замаха
|
float reach = 1.15f; // от центра тела до конца замаха
|
||||||
float arcDeg = 100.0f; // ширина сектора
|
float arcDeg = 100.0f; // ширина сектора
|
||||||
|
|||||||
@ -101,6 +101,8 @@ EnemyDef EnemyDefOf(EnemyKind kind)
|
|||||||
d.attack = AttackKind::SWIPE; d.swipeArcDeg = 100.0f;
|
d.attack = AttackKind::SWIPE; d.swipeArcDeg = 100.0f;
|
||||||
d.lightPower = 0.05f;
|
d.lightPower = 0.05f;
|
||||||
d.r = 116; d.g = 52; d.b = 52;
|
d.r = 116; d.g = 52; d.b = 52;
|
||||||
|
// мясо: горит как солома, дугу проводит плохо, радиация его и подняла
|
||||||
|
d.resist = Resists(0.0f, -0.60f, 0.0f, 0.0f, 0.60f, 0.30f, 0.0f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Бегун: тонкий и быстрый. Успевает добежать до перезарядки — и тогда
|
// Бегун: тонкий и быстрый. Успевает добежать до перезарядки — и тогда
|
||||||
@ -116,6 +118,8 @@ EnemyDef EnemyDefOf(EnemyKind kind)
|
|||||||
d.attack = AttackKind::LUNGE; d.lungeDist = 3.4f; d.lungeTime = 0.20f;
|
d.attack = AttackKind::LUNGE; d.lungeDist = 3.4f; d.lungeTime = 0.20f;
|
||||||
d.lightPower = 0.07f;
|
d.lightPower = 0.07f;
|
||||||
d.r = 158; d.g = 74; d.b = 44;
|
d.r = 158; d.g = 74; d.b = 44;
|
||||||
|
// быстрый и тонкий: горит и стынет, к кислоте привычен
|
||||||
|
d.resist = Resists(0.0f, -0.50f, -0.40f, 0.55f, 0.0f, 0.0f, 0.0f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Плевок: единственный, кто достаёт издалека. Держит дистанцию и бьёт
|
// Плевок: единственный, кто достаёт издалека. Держит дистанцию и бьёт
|
||||||
@ -131,6 +135,8 @@ EnemyDef EnemyDefOf(EnemyKind kind)
|
|||||||
d.attack = AttackKind::SPIT; d.spitRange = 8.4f;
|
d.attack = AttackKind::SPIT; d.spitRange = 8.4f;
|
||||||
d.lightPower = 0.10f;
|
d.lightPower = 0.10f;
|
||||||
d.r = 96; d.g = 132; d.b = 72;
|
d.r = 96; d.g = 132; d.b = 72;
|
||||||
|
// сам кислотный: TOX по нему бесполезен, дуга — наоборот
|
||||||
|
d.resist = Resists(0.10f, 0.0f, 0.0f, 0.70f, -0.45f, 0.20f, 0.0f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Громила: медленный, толстый, с одного удара снимает половину бойца.
|
// Громила: медленный, толстый, с одного удара снимает половину бойца.
|
||||||
@ -145,6 +151,8 @@ EnemyDef EnemyDefOf(EnemyKind kind)
|
|||||||
d.attack = AttackKind::SLAM; d.slamRadius = 2.15f;
|
d.attack = AttackKind::SLAM; d.slamRadius = 2.15f;
|
||||||
d.lightPower = 0.12f;
|
d.lightPower = 0.12f;
|
||||||
d.r = 142; d.g = 60; d.b = 78;
|
d.r = 142; d.g = 60; d.b = 78;
|
||||||
|
// танк: пуля вязнет в туше, кислота разъедает
|
||||||
|
d.resist = Resists(0.60f, 0.20f, 0.30f, -0.50f, 0.0f, 0.10f, 0.0f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Хват: сам по себе слаб, но держит бойца на месте, пока его не убьют.
|
// Хват: сам по себе слаб, но держит бойца на месте, пока его не убьют.
|
||||||
@ -160,6 +168,8 @@ EnemyDef EnemyDefOf(EnemyKind kind)
|
|||||||
d.attack = AttackKind::GRAB; d.grabTime = 2.60f; d.grabTick = 0.45f;
|
d.attack = AttackKind::GRAB; d.grabTime = 2.60f; d.grabTick = 0.45f;
|
||||||
d.lightPower = 0.09f;
|
d.lightPower = 0.09f;
|
||||||
d.r = 88; d.g = 62; d.b = 118;
|
d.r = 88; d.g = 62; d.b = 118;
|
||||||
|
// тварь тени: жгут, а не расстреливают; пустота ей почти родня
|
||||||
|
d.resist = Resists(0.30f, -0.60f, 0.10f, 0.0f, 0.0f, 0.0f, 0.60f);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// --- разрушаемая обстановка -------------------------------------------
|
// --- разрушаемая обстановка -------------------------------------------
|
||||||
@ -218,6 +228,7 @@ void MakeTarget(ecs::World& world, EnemyKind kind, Vec2 pos, float knockResist,
|
|||||||
t.hp = def.hp;
|
t.hp = def.hp;
|
||||||
t.maxHp = def.hp;
|
t.maxHp = def.hp;
|
||||||
t.radius = def.radius;
|
t.radius = def.radius;
|
||||||
|
t.resist = def.resist;
|
||||||
t.respawnTimer = 0.0f;
|
t.respawnTimer = 0.0f;
|
||||||
t.alive = true;
|
t.alive = true;
|
||||||
// У мишени приёмки сопротивление отбросу расписано по слотам, у твари оно —
|
// У мишени приёмки сопротивление отбросу расписано по слотам, у твари оно —
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "sim/element.h"
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Противник. Одна сущность на две роли, и это сделано намеренно.
|
// Противник. Одна сущность на две роли, и это сделано намеренно.
|
||||||
//
|
//
|
||||||
@ -96,6 +97,11 @@ struct EnemyDef
|
|||||||
float grabTime = 0.0f; // GRAB: сколько держит
|
float grabTime = 0.0f; // GRAB: сколько держит
|
||||||
float grabTick = 0.5f; // GRAB: как часто тикает урон, пока держит
|
float grabTick = 0.5f; // GRAB: как часто тикает урон, пока держит
|
||||||
|
|
||||||
|
// Профиль сопротивлений. Свойство ВИДА, а не глубины: глубже выходят другие
|
||||||
|
// твари, а не те же с другими числами (docs/16-descent.md). Он же —
|
||||||
|
// единственная подсказка, которую игра даёт про правильное оружие.
|
||||||
|
Resists resist;
|
||||||
|
|
||||||
float lightPower = 0.0f; // светятся ли глаза (render-only)
|
float lightPower = 0.0f; // светятся ли глаза (render-only)
|
||||||
|
|
||||||
// Цвет тела. Нейтральные байты: типов рендера в sim/ нет.
|
// Цвет тела. Нейтральные байты: типов рендера в sim/ нет.
|
||||||
@ -126,6 +132,9 @@ struct Target
|
|||||||
// 1 — не сдвигается вообще. Тяжёлая цель гасит импульс клинка, и это
|
// 1 — не сдвигается вообще. Тяжёлая цель гасит импульс клинка, и это
|
||||||
// единственное, чем она отличается от лёгкой при равном HP.
|
// единственное, чем она отличается от лёгкой при равном HP.
|
||||||
float knockResist = 0.0f;
|
float knockResist = 0.0f;
|
||||||
|
// Копия профиля вида: правила урона не лезут в каталог за каждой пулей, а
|
||||||
|
// мутации однажды будут править сопротивления ЭКЗЕМПЛЯРА, а не вида.
|
||||||
|
Resists resist;
|
||||||
|
|
||||||
// Во сколько раз ослабляется импульс с учётом общего множителя из tuning.
|
// Во сколько раз ослабляется импульс с учётом общего множителя из tuning.
|
||||||
float KnockScale() const
|
float KnockScale() const
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "sim/element.h"
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Каталог оружия: ЧТО такое ствол или клинок, в отрыве от того, кто его держит.
|
// Каталог оружия: ЧТО такое ствол или клинок, в отрыве от того, кто его держит.
|
||||||
//
|
//
|
||||||
@ -60,6 +61,10 @@ struct FirearmDef
|
|||||||
{
|
{
|
||||||
const char* name = "rifle";
|
const char* name = "rifle";
|
||||||
|
|
||||||
|
// Из чего состоит удар. По умолчанию чистая кинетика: карабин, на котором
|
||||||
|
// меряется приёмка, обязан остаться ровно таким, каким был.
|
||||||
|
ElementMix elem;
|
||||||
|
|
||||||
// баллистика и урон
|
// баллистика и урон
|
||||||
float range = 7.2f;
|
float range = 7.2f;
|
||||||
float fireInterval = 0.35f;
|
float fireInterval = 0.35f;
|
||||||
@ -103,6 +108,8 @@ struct BladeDef
|
|||||||
{
|
{
|
||||||
const char* name = "blade";
|
const char* name = "blade";
|
||||||
|
|
||||||
|
ElementMix elem;
|
||||||
|
|
||||||
float reach = 1.40f;
|
float reach = 1.40f;
|
||||||
float arcDeg = 100.0f;
|
float arcDeg = 100.0f;
|
||||||
float windup = 0.17f;
|
float windup = 0.17f;
|
||||||
@ -233,6 +240,10 @@ inline FirearmDef ShotgunDef()
|
|||||||
d.shakePerShot = 1.55f;
|
d.shakePerShot = 1.55f;
|
||||||
d.tracerR = 255; d.tracerG = 190; d.tracerB = 130;
|
d.tracerR = 255; d.tracerG = 190; d.tracerB = 130;
|
||||||
d.muzzleR = 255; d.muzzleG = 196; d.muzzleB = 110;
|
d.muzzleR = 255; d.muzzleG = 196; d.muzzleB = 110;
|
||||||
|
|
||||||
|
// раскалённая дробь: стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и
|
||||||
|
// сабля остаются чистой кинетикой — на них меряется приёмка.
|
||||||
|
d.elem = ElementMix::Of(Element::INC, 0.55f);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,6 +280,10 @@ inline FirearmDef SmgDef()
|
|||||||
d.shakePerShot = 0.22f;
|
d.shakePerShot = 0.22f;
|
||||||
d.tracerR = 220; d.tracerG = 240; d.tracerB = 255;
|
d.tracerR = 220; d.tracerG = 240; d.tracerB = 255;
|
||||||
d.muzzleR = 210; d.muzzleG = 230; d.muzzleB = 255;
|
d.muzzleR = 210; d.muzzleG = 230; d.muzzleB = 255;
|
||||||
|
|
||||||
|
// электризованный боеприпас: стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и
|
||||||
|
// сабля остаются чистой кинетикой — на них меряется приёмка.
|
||||||
|
d.elem = ElementMix::Of(Element::ARC, 0.50f);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,6 +320,10 @@ inline FirearmDef MagnumDef()
|
|||||||
d.shakePerShot = 1.90f;
|
d.shakePerShot = 1.90f;
|
||||||
d.tracerR = 255; d.tracerG = 246; d.tracerB = 226;
|
d.tracerR = 255; d.tracerG = 246; d.tracerB = 226;
|
||||||
d.muzzleR = 255; d.muzzleG = 232; d.muzzleB = 176;
|
d.muzzleR = 255; d.muzzleG = 232; d.muzzleB = 176;
|
||||||
|
|
||||||
|
// пробивает то, что защищено: стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и
|
||||||
|
// сабля остаются чистой кинетикой — на них меряется приёмка.
|
||||||
|
d.elem = ElementMix::Of(Element::VOID, 0.45f);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +355,10 @@ inline FirearmDef BowDef()
|
|||||||
d.shakePerShot = 0.18f;
|
d.shakePerShot = 0.18f;
|
||||||
d.tracerR = 178; d.tracerG = 205; d.tracerB = 180;
|
d.tracerR = 178; d.tracerG = 205; d.tracerB = 180;
|
||||||
d.muzzleR = 0; d.muzzleG = 0; d.muzzleB = 0;
|
d.muzzleR = 0; d.muzzleG = 0; d.muzzleB = 0;
|
||||||
|
|
||||||
|
// отравленный болт: стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и
|
||||||
|
// сабля остаются чистой кинетикой — на них меряется приёмка.
|
||||||
|
d.elem = ElementMix::Of(Element::TOX, 0.75f);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,6 +437,10 @@ inline BladeDef CleaverDef()
|
|||||||
d.damage = 240.0f;
|
d.damage = 240.0f;
|
||||||
d.knockback = 4.6f;
|
d.knockback = 4.6f;
|
||||||
d.lungeSpeed = 2.8f;
|
d.lungeSpeed = 2.8f;
|
||||||
|
|
||||||
|
// холодная сталь тесака: стихии есть только у НАХОДИМОГО оружия. Карабин, пистолет и
|
||||||
|
// сабля остаются чистой кинетикой — на них меряется приёмка.
|
||||||
|
d.elem = ElementMix::Of(Element::CRY, 0.55f);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user