Десять вех одним состоянием: разделять их пофайлово нечем, потому что
src/game.cpp менялся почти в каждой. Собирается начисто без предупреждений,
все проверки проходят.
ДВИЖОК (engine/, цель tile2d)
Каталог свой, и решение «это движок» принимается тем, куда положен файл.
Компилятор нашёл три зависимости движка от игры, и все сняты: размеры кадра
и карты уехали в engine/config.h, потолок населения этажа стал своим
(MAX_LEVEL_SPAWNS против игровой MAX_TARGETS), запасная точка старта стала
центром карты вместо позиции отряда.
engine/projection.* единственное место, где движок знает про изометрию;
видов сразу два — абстракция с одной реализацией ничем
не проверена и обычно оказывается неправильной
engine/project.* манифест проекта: пути, вид, кривая глубины
engine/app.* окно, кадр, fixed timestep — вместо двух копий цикла
engine/tilemap_draw движок научился показывать свою карту
РЕДАКТОР (editor/, цель tile2d_editor)
Открывает любой проект и ни одной игры по имени не знает.
--tool одиннадцать безоконных команд: комнаты, каталог, кривая, сборка
этажа, ASCII-карта, проверка набора. Правку делают файлами,
последствия читают командой (docs/19-agent.md)
терминал снизу: те же инструменты, git, сборка и запросы к claude/codex,
открытый проект уходит потомкам через TILE2D_PROJECT
ПРИМЕР (samples/walk, цель tile2d_walk)
Второй потребитель движка. Игра и редактор самостоятельность движка
проверить не могут: редактор мира не рисует, а игра — то место, куда общий
код и уезжает незаметно.
ИГРА (src/)
Экстракшен: вес и два порога переноски, конечный боезапас, расходники
применяются сами, общая сумка отряда, трата опыта на улучшения.
Каждое из правил проверяется прогоном, а не глазами, и проверки поймали:
вес дважды не связывал ничего (мерил по бинтам прогона, а не по добыче);
боец с клинком не тратил найденные патроны при запасном стволе в сумке;
массив виджетов экрана переполнился и молча съел хвост схрона; проверка
улучшений сама ломала забег, вызывая NewRun посреди спуска.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
176 lines
8.7 KiB
C++
176 lines
8.7 KiB
C++
#pragma once
|
||
// -----------------------------------------------------------------------------
|
||
// Редактор комнат — ОТДЕЛЬНОЕ приложение поверх движка.
|
||
//
|
||
// Он линкуется только с tile2d и не видит ни одного символа из sim/ и ai/.
|
||
// Поэтому «редактор знает про правила боя» — это ошибка линковки, а не то, что
|
||
// надо ловить на ревью.
|
||
//
|
||
// Ни одной игры по имени он тоже не знает: где содержимое, говорит проект
|
||
// (engine/project.h), а что можно ставить в комнату — его каталог сущностей.
|
||
//
|
||
// Рисует он ровно тем же софтверным фреймбуфером и той же ПРОЕКЦИЕЙ, что и
|
||
// игра: комната в редакторе выглядит так же, как в бою, потому что это один и
|
||
// тот же код, а не похожий. В проекте с видом сверху редактор тоже показывает
|
||
// вид сверху — иначе он планировал бы не то, что получится.
|
||
//
|
||
// Раскладка кадра 480x270:
|
||
// y 0..11 верхняя панель: файл, операции над комнатами
|
||
// x 0..379 холст: изометрия комнаты (или карта предпросмотра уровня)
|
||
// x 380..479 правая панель: кисти, свойства комнаты, палитра
|
||
// y 232..269 нижняя полоса поверх холста: таблица выбранной точки спавна
|
||
// -----------------------------------------------------------------------------
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#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<EdWidget> 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<Sprite> markers; // кеш, по индексу каталога
|
||
};
|