#pragma once // ----------------------------------------------------------------------------- // Редактор комнат — ОТДЕЛЬНОЕ приложение поверх движка. // // Он линкуется только с tile2d и не видит ни одного символа из sim/ и ai/. // Поэтому «редактор знает про правила боя» — это ошибка линковки, а не то, что // надо ловить на ревью. // // Ни одной игры по имени он тоже не знает: где содержимое, говорит проект // (engine/project.h), а что можно ставить в комнату — его каталог сущностей. // // Рисует он ровно тем же софтверным фреймбуфером и той же ПРОЕКЦИЕЙ, что и // игра: комната в редакторе выглядит так же, как в бою, потому что это один и // тот же код, а не похожий. В проекте с видом сверху редактор тоже показывает // вид сверху — иначе он планировал бы не то, что получится. // // Раскладка кадра 480x270: // y 0..11 верхняя панель: файл, операции над комнатами // x 0..379 холст: изометрия комнаты (или карта предпросмотра уровня) // x 380..479 правая панель: кисти, свойства комнаты, палитра // y 232..269 нижняя полоса поверх холста: таблица выбранной точки спавна // ----------------------------------------------------------------------------- #include #include #include "engine/math.h" #include "engine/catalog.h" #include "engine/level_gen.h" #include "engine/project.h" #include "engine/room.h" #include "engine/tileset.h" #include "engine/framebuffer.h" #include "engine/sfa_library.h" // Кисть. Пол, стена и дверь пишут в сетку; спавн ставит точку; ластик снимает // и то и другое — иначе «убрать» требовало бы помнить, чем это поставлено. enum class Tool : uint8_t { FLOOR = 0, WALL, DOOR, SPAWN, ERASE, COUNT, }; // Что делает элемент интерфейса. Действие, а не «номер кнопки»: раскладка // панелей меняется чаще, чем набор операций. enum class Act : uint8_t { NONE = 0, TOOL, PALETTE, ROOM_PREV, ROOM_NEXT, ROOM_NEW, ROOM_SAVE, ROOM_RELOAD, W_DEC, W_INC, H_DEC, H_INC, WEIGHT_DEC, WEIGHT_INC, THEME_CYCLE, START_TOGGLE, DMIN_DEC, DMIN_INC, DMAX_DEC, DMAX_INC, PALETTE_TAB, CHANCE_DEC, CHANCE_INC, OPT_DEC, OPT_INC, PREVIEW_TOGGLE, PREVIEW_RESEED, PREVIEW_DEEPER, PREVIEW_SHALLOWER, PLAYTEST, }; struct EdRect { int x = 0, y = 0, w = 0, h = 0; bool Has(float fx, float fy) const { return fx >= float(x) && fy >= float(y) && fx < float(x + w) && fy < float(y + h); } }; struct EdWidget { Act act = Act::NONE; int arg = 0; // номер кисти, индекс каталога, ... EdRect rect; std::string label; bool active = false; // включённое состояние (нажатая кисть) bool enabled = true; }; struct Editor { // --- данные --------------------------------------------------------------- // Проект — первое, что поднимается: из него берутся пути к содержимому, // вид отображения и кривая глубины. Редактор не знает ни одной игры по // имени и открывает тот проект, который ему назвали. Project project; EntityCatalog catalog; RoomLibrary lib; TileSet tiles; SpriteLibrary forge; bool forgeOpen = false; // Рабочая копия правится, а lib.rooms остаётся тем, что лежит на диске: // без этого «не сохранять» было бы нечем осуществить. Room room; int roomIndex = -1; bool dirty = false; // --- инструменты ---------------------------------------------------------- Tool tool = Tool::WALL; int paletteIndex = 0; // Каталог вырос до трёх десятков записей и в столбик больше не помещается. // Вкладки по классу — не украшение: твари, обстановка и лут ставятся в // разные моменты работы, и мешать их в одном списке значит листать чужое. EntityClass paletteTab = EntityClass::MONSTER; int paletteScroll = 0; int selX = -1, selY = -1; // выбранная точка спавна; -1 — не выбрана // --- вид ------------------------------------------------------------------ int panX = 0, panY = 0; // сдвиг холста в пикселях int hoverX = -1, hoverY = -1; bool painting = false; // ЛКМ зажата: кисть тянется bool erasing = false; // --- предпросмотр этажа --------------------------------------------------- bool preview = false; uint32_t seed = 1; // Какой этаж собирается в предпросмотре. Отдельно от диапазона комнаты: // проверять надо не только «попадает ли комната», но и «во что превращается // весь набор» на этой глубине. int previewDepth = 1; Tilemap previewMap; GeneratedLevel previewLevel; std::string previewError; // --- строка состояния ----------------------------------------------------- std::string status; float statusTimer = 0.0f; std::vector widgets; // Поднять проект, каталог, набор комнат и тайлы. false — работать не с чем // (нет проекта или каталога сущностей), причина уходит в status. // projectName — имя или путь; пусто — единственный найденный проект. bool Init(const std::string& projectName = {}); // Ввод и действия. fit — куда лёг фреймбуфер в окне. void Update(const ScreenRect& fit, float dt); // Комната (или карта предпросмотра) в софтверный буфер. void DrawWorld(Framebuffer& fb); // Панели поверх готового кадра: примитивы raylib, потому что здесь нужны // слова, и много. void DrawUi(const ScreenRect& fit) const; // Выполнить действие интерфейса. Публично, потому что через него же // работают ключи командной строки: «показать сборку уровня» с клавиши и // из аргумента обязаны делать буквально одно и то же. void Apply(Act act, int arg); private: // u — целый масштаб кадра в окне. Нужен, чтобы мерить подписи ТЕМ ЖЕ // шрифтом и кеглем, каким они будут нарисованы: у встроенного шрифта raylib // межбуквенный интервал зависит от размера, и посчитанная «по пять пикселей // на букву» ширина кнопки разъезжается с настоящей. void BuildWidgets(int u); void LoadRoomAt(int index); void SaveCurrent(); void ReloadLibrary(); void NewRoom(); void PaintAt(int x, int y, bool erase); void Regenerate(); void Say(const std::string& s); // Спрайт-маркер сущности каталога: кадр из SpriteForge, если он собран, // иначе капсула по цвету и радиусу. Пустая палитра не должна мешать // планировать уровень. const Sprite* MarkerSprite(int catalogIndex) const; mutable std::vector markers; // кеш, по индексу каталога };