squad-proto/editor/editor.h
z.kirill 7d6266aaf3 Tile2D: движок отдельным продуктом, игра — проектом в нём (M11-M20)
Десять вех одним состоянием: разделять их пофайлово нечем, потому что
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>
2026-08-13 21:07:19 +03:00

176 lines
8.7 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.

#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; // кеш, по индексу каталога
};