112 lines
7.2 KiB
C
112 lines
7.2 KiB
C
|
|
#pragma once
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
// Каталог размещаемых сущностей: ЧТО вообще можно поставить в комнату и на
|
|||
|
|
// какой глубине это встречается.
|
|||
|
|
//
|
|||
|
|
// Это граница между движком и игрой. Редактор про EnemyKind и ItemId не знает
|
|||
|
|
// и знать не может — он линкуется только с движком. Всё, что он видит, —
|
|||
|
|
// строковые id из этого каталога: `shambler`, `crate`, `plate`. Игра переводит
|
|||
|
|
// id в свои перечисления одной таблицей (sim/spawn_catalog.h), и это
|
|||
|
|
// ЕДИНСТВЕННОЕ место перевода.
|
|||
|
|
//
|
|||
|
|
// Почему не общий enum на движок и игру: тогда добавление твари правило бы
|
|||
|
|
// заголовок движка, редактор пришлось бы пересобирать вместе с игрой, а
|
|||
|
|
// «движок» превратился бы в подкаталог игры. Здесь же новая тварь — это строка
|
|||
|
|
// в текстовом файле плюс одна строка в таблице перевода.
|
|||
|
|
//
|
|||
|
|
// --- почему формат key=value -------------------------------------------------
|
|||
|
|
// Первая версия каталога была позиционной: цвет, радиус и спрайт стояли по
|
|||
|
|
// местам. Игра про бесконечный спуск потребовала добавить сущности глубину,
|
|||
|
|
// опасность и качество, и позиционный формат на этом кончился: каждое новое
|
|||
|
|
// свойство ломало бы все уже написанные строки. Ключи же дописываются с краю
|
|||
|
|
// и не трогают ничего.
|
|||
|
|
// -----------------------------------------------------------------------------
|
|||
|
|
#include <cstdint>
|
|||
|
|
#include <string>
|
|||
|
|
#include <vector>
|
|||
|
|
|
|||
|
|
// Роль сущности. Редактору важны эти три случая: они по-разному ставятся и
|
|||
|
|
// по-разному попадают в уровень.
|
|||
|
|
enum class EntityClass : uint8_t
|
|||
|
|
{
|
|||
|
|
MONSTER = 0, // тварь: ходит, бьёт, участвует в зачистке и тратит бюджет опасности
|
|||
|
|
PROP, // обстановка: ящик, бочка, шкафчик. Опасности не несёт
|
|||
|
|
LOOT, // предмет: лежит на полу, подбирается на ходу
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const char* EntityClassName(EntityClass cls);
|
|||
|
|
|
|||
|
|
struct EntityDesc
|
|||
|
|
{
|
|||
|
|
std::string id; // то, что попадает в файл комнаты
|
|||
|
|
std::string name; // подпись в палитре редактора
|
|||
|
|
EntityClass cls = EntityClass::MONSTER;
|
|||
|
|
|
|||
|
|
uint8_t r = 180, g = 180, b = 180;
|
|||
|
|
float radius = 0.34f;
|
|||
|
|
|
|||
|
|
// Имя ассета SpriteForge (`sf list`), пусто — рисовать капсулу по цвету.
|
|||
|
|
// Имя, а не числовой id: каталог читают данными, а generated-заголовок
|
|||
|
|
// с константами SF_ASSET_* принадлежит игре, а не движку.
|
|||
|
|
std::string sprite;
|
|||
|
|
|
|||
|
|
// --- глубина --------------------------------------------------------------
|
|||
|
|
// С какого этажа сущность вообще может появиться. Это главный рычаг всей
|
|||
|
|
// игры: «чем глубже, тем страшнее» — это в первую очередь ЗДЕСЬ, а не в
|
|||
|
|
// множителях урона. Громила на первом этаже ломает не баланс, а обещание.
|
|||
|
|
int minDepth = 1;
|
|||
|
|
// По какой этаж включительно; 0 — до бесконечности. Нужен, чтобы мелочь
|
|||
|
|
// первых этажей перестала занимать место на глубине.
|
|||
|
|
int maxDepth = 0;
|
|||
|
|
|
|||
|
|
// Цена в бюджете опасности этажа (engine/progression.h). Не урон и не HP,
|
|||
|
|
// а «сколько места в голове игрока эта тварь занимает»: бюджет — это то,
|
|||
|
|
// чем этаж отличается по СЛОЖНОСТИ, а не по числу тел.
|
|||
|
|
float danger = 1.0f;
|
|||
|
|
|
|||
|
|
// Качество предмета для class=loot. Этаж выдаёт лут своего тира и ниже,
|
|||
|
|
// поэтому карабин не исчезает на глубине — он просто перестаёт быть
|
|||
|
|
// находкой.
|
|||
|
|
int tier = 0;
|
|||
|
|
|
|||
|
|
bool InDepth(int depth) const
|
|||
|
|
{
|
|||
|
|
return depth >= minDepth && (maxDepth <= 0 || depth <= maxDepth);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Порядок записей — порядок палитры в редакторе. Он же порядок в файле:
|
|||
|
|
// каталог правится руками, и перетасовывать его при загрузке нельзя.
|
|||
|
|
struct EntityCatalog
|
|||
|
|
{
|
|||
|
|
std::vector<EntityDesc> entries;
|
|||
|
|
|
|||
|
|
// Разобрать текстовый каталог. false — файла нет или он битый; err (если
|
|||
|
|
// передан) получает первую же осечку с номером строки.
|
|||
|
|
bool Load(const std::string& path, std::string* err = nullptr);
|
|||
|
|
|
|||
|
|
int Count() const { return int(entries.size()); }
|
|||
|
|
const EntityDesc& At(int i) const { return entries[size_t(i)]; }
|
|||
|
|
|
|||
|
|
// -1 — такого id в каталоге нет. Комната, ссылающаяся на выпавший id,
|
|||
|
|
// не должна ронять ни редактор, ни игру: точка просто не сработает.
|
|||
|
|
int IndexOf(const std::string& id) const;
|
|||
|
|
const EntityDesc* Find(const std::string& id) const;
|
|||
|
|
|
|||
|
|
// Сколько записей данного класса. Палитра редактора разбита на вкладки.
|
|||
|
|
int CountOf(EntityClass cls) const;
|
|||
|
|
|
|||
|
|
// Есть ли на этой глубине хоть одна тварь и хоть один предмет. Отвечает на
|
|||
|
|
// единственный вопрос, который может убить забег: «а есть ли чем населить
|
|||
|
|
// сотый этаж».
|
|||
|
|
bool HasAnyAt(EntityClass cls, int depth) const;
|
|||
|
|
|
|||
|
|
// Самая глубокая точка, дальше которой каталог перестаёт что-либо
|
|||
|
|
// добавлять: максимум minDepth по всем записям. Дальше неё этаж собирается
|
|||
|
|
// из того же набора, и это нормально — бесконечность не требует
|
|||
|
|
// бесконечного каталога, она требует, чтобы он не кончался.
|
|||
|
|
int DeepestIntroduction() const;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Где лежит каталог — знает проект (engine/project.h). Движок сам его не ищет.
|