#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}, {"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}, {"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& 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& 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; }