squad-proto/engine/tileset.h

70 lines
4.2 KiB
C
Raw Normal View History

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-14 01:07:19 +07:00
#pragma once
// -----------------------------------------------------------------------------
// Тайлы пола и стен — по набору на каждую тему отделки.
//
// Живёт в движке, а не в render/sprites.h, ровно по одной причине: этим
// набором рисует и игра, и редактор комнат. Редактор линкуется только с
// движком и про бойцов, тварей и предметы не знает, поэтому тащить ради двух
// спрайтов весь SpriteSet он не может и не должен.
//
// Выбор варианта плиты — чистая функция от координат тайла, без состояния и без
// генератора: пол обязан быть одинаковым в любом прогоне, иначе запечённая
// кровь легла бы после перезапуска на другую плиту (render/blood.h).
// -----------------------------------------------------------------------------
#include <cstdint>
#include "engine/hash.h"
#include "engine/theme.h"
#include "engine/framebuffer.h"
#include "engine/config.h"
class SpriteLibrary;
// Сколько разных «рисунков» может лежать на плите пола: ровная, треснувшая,
// решётка, засыпанная трухой. Шахматка при этом остаётся — вариант ложится
// ПОВЕРХ неё, а не вместо: без чередования двух тонов изометрическая сетка
// перестаёт читаться, и пол превращается в кашу.
constexpr int FLOOR_VARIANTS = 4;
struct TileSet
{
// [тема][чётность шахматки][вариант рисунка].
Sprite floor[THEME_COUNT][2][FLOOR_VARIANTS];
Sprite wall[THEME_COUNT][2]; // industrial / biomass-infested, 32x32
// Процедурный набор. Не зависит ни от одного файла на диске: игра и
// редактор обязаны запускаться на голом репозитории.
void Generate();
// Заменить базовую тему на тайлы из библиотеки SpriteForge и пересобрать
// по ней остальные. Всё или ничего: при любой осечке набор остаётся тем,
// что дал Generate(), и возвращается false. Вызывать только после Generate().
bool LoadFromForge(const SpriteLibrary& lib);
const Sprite& Floor(int tx, int ty, uint8_t theme) const
{
// Раскладка намеренно неравномерная: ровная плита занимает больше
// половины карты. Равные доли превращают пол в пёстрый ковёр, на
// котором не видно ни крови, ни тварей.
static const int kPick[8] = {0, 0, 0, 0, 0, 1, 2, 3};
const uint32_t h = Hash2(uint32_t(tx) * 2654435761u, uint32_t(ty));
return floor[Clamp(theme)][(tx + ty) & 1][kPick[h & 7u]];
}
const Sprite& Wall(int tx, int ty, uint8_t theme) const
{
// Редкие заражённые блоки, детерминированно по координате.
const uint32_t h = Hash2(uint32_t(tx) * 2246822519u, uint32_t(ty));
return wall[Clamp(theme)][(h & 7u) == 0u ? 1 : 0];
}
private:
// Комната может сослаться на тему, которой в этой сборке движка ещё нет.
// Падать из-за этого нельзя: неизвестная тема рисуется базовой.
static int Clamp(uint8_t theme) { return (theme < THEME_COUNT) ? int(theme) : 0; }
// Темы 1..N-1 — перекраска темы 0. Вызывается и после Generate(), и после
// LoadFromForge(): иначе привезённые тайлы остались бы только в базовой.
void BuildThemes();
};