squad-proto/src/sim/spawn_catalog.cpp
z.kirill e3294cffd4 Личинки, парирование и инструмент --wall (M30)
--WALL. Новый прогон отвечает на вопрос, появившийся вместе с чекпоинтами
через пятьдесят этажей: растёт ли достижимая глубина от вылазки к вылазке.
Отряд из одного, трёх и пяти, одетый по глубине, ставится в скопление
тварей и живёт полторы минуты без управления. Первая стена: одиночка D7,
трое D30, пятеро D30 - петля работает.

И он же нашёл настоящую ошибку: PlaceSquad воскрешал ВСЕ пять слотов, в
том числе незанятые. Строка писалась, когда отряд всегда был впятером;
после появления ростера одиночка выходил на этаж с четырьмя невидимыми
телохранителями. Из-за них прежний замер "одиночка выжил на первом этаже,
100 -> 41" был боем впятером.

ЛИЧИНКИ. С исправленным замером выяснилось, что одиночка с ножом
проигрывает двум бредущим: 210 здоровья против ножа это девять секунд
работы под ответным уроном. Первые этажи населены личинками (55 здоровья,
7 урона, медленные), бредущий уехал на третий этаж, бегун на четвёртый.
Новый ВИД, а не ослабленный бредущий с множителем: глубина в этой игре
меняет, кто выходит, а не числа у одного и того же.

ПАРИРОВАНИЕ. Отдельная характеристика клинка, и чем оружие легче, тем она
выше: нож 34%, сабля 20%, тесак 8%. Не выведена из веса намеренно - её
двигает аффикс PARRY, и лёгкий тесак обязан быть возможен как находка.

Отбить можно, только если удары СОПРИКОСНУЛИСЬ: боец сам должен быть в
замахе или в ударе. Иначе клинок стал бы щитом, работающим, пока стоишь.

Удавшийся отбив снимает урон, бьёт тварь ударом бойца целиком и запускает
повторную атаку с начала фазы, НЕ сбрасывая откат. Последнее и есть суть
размена: сбрасывай отбив откат, выгоднее было бы подставляться под удары.

Плевок отбивается тоже: летит назад и несёт урон бойца, а не твари. Это
его удар, просто по летящей цели.

Проверка меряет размен целиком, включая обратную половину правила:
"стоя столбом не отбивает". Без неё условие про соприкосновение
существовало бы только в комментарии.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 05:04:35 +03:00

147 lines
5.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "sim/spawn_catalog.h"
#include "sim/affix.h"
#include "sim/loot.h"
namespace
{
// Порядок здесь не важен, важна полнота: каждый вид, который может встретиться
// в комнате, обязан иметь id. Мишень приёмки (DUMMY) в каталоге движка есть
// намеренно — ею удобно ставить неподвижную цель, не заводя тварь.
struct Row
{
const char* id;
EnemyKind kind;
};
const Row ROWS[] = {
{"dummy", EnemyKind::DUMMY},
{"larva", EnemyKind::LARVA},
{"shambler", EnemyKind::SHAMBLER},
{"rusher", EnemyKind::RUSHER},
{"spitter", EnemyKind::SPITTER},
{"brute", EnemyKind::BRUTE},
{"lurker", EnemyKind::LURKER},
{"crate", EnemyKind::CRATE},
{"barrel", EnemyKind::BARREL},
{"locker", EnemyKind::LOCKER},
};
// Вторая половина того же моста: id каталога -> предмет игры. Отдельная
// таблица, а не поле в первой: тварь и предмет — разные пулы и разные
// перечисления, и объединяющий их «универсальный id» пришлось бы разбирать
// в каждом месте использования.
struct LootRow
{
const char* id;
ItemId item;
};
const LootRow LOOT_ROWS[] = {
{"rifle", ItemId::RIFLE}, {"pistol", ItemId::PISTOL},
{"shotgun", ItemId::SHOTGUN}, {"smg", ItemId::SMG},
{"magnum", ItemId::MAGNUM}, {"bow", ItemId::BOW},
{"saber", ItemId::SABER}, {"knife", ItemId::KNIFE},
{"cleaver", ItemId::CLEAVER},
{"jacket", ItemId::JACKET}, {"vest", ItemId::VEST},
{"plate", ItemId::PLATE}, {"shroud", ItemId::SHROUD},
{"lantern", ItemId::LANTERN}, {"charm", ItemId::CHARM},
{"spurs", ItemId::SPURS},
{"ammo", ItemId::AMMO}, {"battery", ItemId::BATTERY},
{"capsule", ItemId::CAPSULE},
{"bandage", ItemId::BANDAGE}, {"stimpack", ItemId::STIMPACK},
};
} // namespace
bool EnemyKindOfId(const std::string& id, EnemyKind& out)
{
for (const Row& r : ROWS)
{
if (id != r.id) continue;
out = r.kind;
return true;
}
return false;
}
bool ItemIdOfId(const std::string& id, ItemId& out)
{
for (const LootRow& r : LOOT_ROWS)
{
if (id != r.id) continue;
out = r.item;
return true;
}
return false;
}
const char* IdOfEnemyKind(EnemyKind kind)
{
for (const Row& r : ROWS)
if (r.kind == kind) return r.id;
return "?";
}
const char* IdOfItem(ItemId item)
{
for (const LootRow& r : LOOT_ROWS)
if (r.item == item) return r.id;
return "-";
}
int SpawnFromLevel(ecs::World& world, const std::vector<LevelSpawn>& list, const Tilemap& map,
int* unknown)
{
int placed = 0;
int skipped = 0;
for (const LevelSpawn& s : list)
{
EnemyKind kind = EnemyKind::DUMMY;
if (!EnemyKindOfId(s.id, kind)) { ++skipped; continue; }
// Потолок тот же, что у захардкоженной расстановки: маска задетых целей
// в клинке 32-битная, и тридцать третья цель молча получала бы удары
// дважды за взмах (tuning.h).
if (placed >= MAX_TARGETS) { ++skipped; continue; }
// Сопротивление отбросу — свойство вида: расписывать его по слотам,
// как у мишеней приёмки, здесь не по чему, слотов нет.
MakeTarget(world, kind, s.pos, -1.0f, &map);
++placed;
}
if (unknown) *unknown = skipped;
return placed;
}
int SpawnLootFromLevel(ecs::World& world, const std::vector<LevelSpawn>& list, int depth,
uint32_t seed, int* unknown)
{
int placed = 0;
int skipped = 0;
// Сид броска — от сида уровня и глубины: тот же уровень обязан дать те же
// находки. Иначе «переиграть с начала» (R) выдавало бы другой лут на той же
// карте, и сравнить два прохода стало бы нечем.
uint32_t rng = seed * 2654435761u + uint32_t(depth) * 40503u + 12345u;
for (const LevelSpawn& s : list)
{
ItemId item = ItemId::NONE;
if (!ItemIdOfId(s.id, item)) { ++skipped; continue; }
// Из стены предмет не выталкивается, в отличие от твари: генератор
// кладёт лут только на достижимый пол, и «поправить» его здесь значило
// бы прятать ошибку раскладки вместо того, чтобы её увидеть.
SpawnFloorItem(world, RollItem(item, depth, rng), s.pos);
++placed;
}
if (unknown) *unknown = skipped;
return placed;
}