10#include "absl/strings/str_format.h"
22namespace fs = std::filesystem;
24absl::StatusOr<std::optional<std::string>>
ReadSource(
const fs::path& path) {
26 if (!fs::exists(path, ec) && !ec)
28 if (ec || !fs::is_regular_file(path, ec) || ec)
29 return absl::FailedPreconditionError(
30 "Terrain source is not a regular file");
31 if (fs::file_size(path, ec) > 4 * 1024 * 1024 || ec)
32 return absl::InvalidArgumentError(
"Terrain source exceeds 4 MiB");
33 std::ifstream file(path, std::ios::binary);
35 return absl::PermissionDeniedError(
"Cannot read terrain source");
36 std::string text{std::istreambuf_iterator<char>(file), {}};
38 return absl::DataLossError(
"Could not finish reading terrain source");
39 return std::optional<std::string>(std::move(text));
44 if (path.extension() !=
".json")
45 return absl::InvalidArgumentError(
46 "Lyra terrain must use a separate .json file");
48 const auto canonical_target = fs::weakly_canonical(path, ec);
50 return absl::FailedPreconditionError(
"Cannot resolve terrain source path");
51 const bool target_exists = fs::exists(path, ec);
53 return absl::FailedPreconditionError(
"Cannot inspect terrain source path");
54 for (
const auto& protected_path :
56 if (protected_path.empty())
58 const auto canonical_protected = fs::weakly_canonical(protected_path, ec);
60 return absl::FailedPreconditionError(
61 "Cannot resolve project or ROM path");
62 if (canonical_target == canonical_protected)
63 return absl::FailedPreconditionError(
64 "Terrain source aliases the project or ROM");
65 const bool protected_exists = fs::exists(protected_path, ec);
67 return absl::FailedPreconditionError(
68 "Cannot inspect project or ROM path");
69 if (!target_exists || !protected_exists)
71 const bool aliases = fs::equivalent(path, protected_path, ec);
73 return absl::FailedPreconditionError(
74 "Cannot compare terrain source identity");
76 return absl::FailedPreconditionError(
77 "Terrain source aliases the project or ROM");
79 return absl::OkStatus();
82void AddWord(std::string& bytes, uint16_t word) {
83 bytes.push_back(
static_cast<char>(word));
84 bytes.push_back(
static_cast<char>(word >> 8));
91 : document_(std::move(document)), stroke_(std::move(stroke)) {}
92 absl::Status
Undo()
override {
return document_->ApplyStroke(stroke_,
true); }
93 absl::Status
Redo()
override {
94 return document_->ApplyStroke(stroke_,
false);
96 std::string
Description()
const override {
return "Paint Lyra terrain"; }
98 return sizeof(*this) +
111 : document_(std::move(document)),
112 before_(std::move(before)),
113 after_(std::move(after)) {}
115 return document_->SetAuthoring(after_, before_);
118 return document_->SetAuthoring(before_, after_);
121 return "Edit Lyra map properties, areas, entities or connections";
134 const std::optional<std::string>& source_bytes,
137 return absl::FailedPreconditionError(
138 "Lyra project source changed; draft retained");
141 {fs::absolute(project.
filepath), source_path},
142 {
"Lyra terrain",
"Lyra terrain file"}));
144 if (current != source_bytes)
145 return absl::FailedPreconditionError(
146 "Lyra terrain changed on disk; refusing stale write");
149 return absl::FailedPreconditionError(
150 "Save the project file before publishing Lyra terrain");
151 const std::string after = document.
Serialize();
156 {{fs::absolute(project.
filepath), project_bytes, *project_bytes},
157 {source_path, source_bytes, after}},
161 return absl::DataLossError(
"Lyra terrain readback is missing");
162 return Document::Parse(*saved, document.
profile()).status();
178 for (
auto& [
id, bitmap] :
sprites)
184absl::StatusOr<std::unique_ptr<LyraTerrainCanvas::MapAssets>>
190 auto& source = native.source;
191 const auto& pixels = source.current_graphics();
192 auto result = std::make_unique<MapAssets>();
193 result->binding = std::move(native.binding);
197 const int height = ((overworld.
tiles16().size() + 7) / 8) * 16;
198 const auto& blockset = source.current_tile16_blockset();
199 const size_t bytes = 128 * height;
200 if (blockset.size() < bytes)
201 return absl::FailedPreconditionError(
"Lyra Tile16 pixels are incomplete");
202 result->atlas.Create(
204 std::vector<uint8_t>(blockset.begin(), blockset.begin() + bytes));
205 result->atlas.SetPalette(source.current_palette());
206 result->graphics.Create(128, 512, 8, pixels);
207 result->graphics.SetPalette(source.current_palette());
216absl::StatusOr<core::LyraMapProperties>
221 return absl::FailedPreconditionError(
222 "Open the main project ROM to copy native graphics settings");
223 if (source_map < 0 || source_map >= 0xA0)
224 return absl::InvalidArgumentError(
"Choose a native source map $00-$9F");
227 return absl::FailedPreconditionError(
228 absl::StrFormat(
"Native map $%02X is unavailable", source_map));
229 const int parent_id = selected->parent();
230 const auto* parent = parent_id >= 0 && parent_id < 0xA0
233 if (!parent || parent->parent() != parent_id)
234 return absl::FailedPreconditionError(absl::StrFormat(
235 "Native map $%02X has no valid native area parent", source_map));
238 return absl::FailedPreconditionError(
"Loom map $3A is unavailable");
239 if (selected->HasGraphicsPreview() || parent->HasGraphicsPreview() ||
240 donor->HasGraphicsPreview())
241 return absl::FailedPreconditionError(
242 "Graphics preview overrides cannot be copied as native settings");
243 const auto* data = parent->game_data();
244 if (!data || data != donor->game_data() || !data->gfx_groups_loaded)
245 return absl::FailedPreconditionError(
246 "Load native graphics before copying graphics settings");
248 const auto tables = zelda3::kVersionConstantsMap.find(data->version);
249 if (tables == zelda3::kVersionConstantsMap.end() ||
250 tables->second.kOverworldGfxGroups1 == 0 ||
251 tables->second.kOverworldGfxGroups2 == 0 ||
252 tables->second.kOverworldMapPaletteGroup == 0 ||
253 tables->second.kSpriteBlocksetPointer == 0)
254 return absl::FailedPreconditionError(
255 "Native graphics tables are unknown for this ROM version");
256 if (donor->roomset_source() !=
258 return absl::FailedPreconditionError(
259 "Loom map $3A must use custom tile groups to copy graphics settings");
261 auto source = *parent;
262 source.ClearInheritedAreaProperties();
263 source.set_game_state(0);
264 source.LoadAreaGraphics();
265 for (
int i = 0; i < 8; ++i) {
266 const uint8_t sheet = source.static_graphics(i);
267 if (sheet == 0x00 || sheet == 0xFF)
268 return absl::FailedPreconditionError(
269 absl::StrFormat(
"Native map $%02X resolves graphics slot %d to $%02X",
270 parent_id, i, sheet));
275 return absl::FailedPreconditionError(
276 absl::StrFormat(
"Native map $%02X resolves animation to $%02X",
281 candidate.
main_palette >= data->palette_groups.overworld_main.size())
282 return absl::OutOfRangeError(absl::StrFormat(
283 "Native main palette $%02X is unavailable", candidate.
main_palette));
285 return absl::OutOfRangeError(absl::StrFormat(
286 "Native area palette $%02X is unavailable", candidate.
palette_set));
290 const bool same_palette_context = parent_id == 0x3A;
291 const auto& rom = overworld.
rom()->vector();
292 const size_t area_entry = tables->second.kOverworldMapPaletteGroup +
294 if (area_entry + 2 >= rom.
size())
295 return absl::OutOfRangeError(
"Native area palette table is incomplete");
296 for (
int i = 0; i < 3; ++i) {
297 if (!same_palette_context && rom[area_entry + i] == 0xFF)
298 return absl::FailedPreconditionError(absl::StrFormat(
299 "Native area palette $%02X uses a previous-map fallback",
302 for (
int state = 0; state < 3; ++state) {
303 const uint8_t sprite_palette = source.sprite_palette(state);
305 static_cast<size_t>(sprite_palette) * 2;
306 if (sprite_entry + 1 >= rom.
size())
307 return absl::OutOfRangeError(
"Native sprite palette table is incomplete");
308 if (!same_palette_context &&
309 (rom[sprite_entry] == 0xFF || rom[sprite_entry + 1] == 0xFF))
310 return absl::FailedPreconditionError(absl::StrFormat(
311 "Native sprite palette $%02X (State %d) uses a previous-map fallback",
312 sprite_palette, state));
316 for (
int state = 0; state < 3; ++state)
324 std::unordered_map<std::string, std::string> bindings;
325 for (
const auto& map : authoring.
maps) {
326 for (
int state = 0; state < 3; ++state) {
328 if (!bindings.contains(key)) {
332 bindings.emplace(key, assets->binding);
334 if (!map.asset_bindings[state].empty() &&
335 map.asset_bindings[state] != bindings.at(key))
336 return absl::FailedPreconditionError(
337 "Lyra map graphics or palette assets changed; draft retained");
340 return absl::OkStatus();
346 if (!
document_ || !donor || !donor->game_data() || !overworld.
rom())
347 return absl::FailedPreconditionError(
"Lyra source is unavailable");
348 const auto& data = *donor->game_data();
349 std::string metadata;
350 for (
const auto& tile : overworld.
tiles16())
351 for (
const auto& quadrant : tile.tiles_info)
353 for (
const auto* group :
354 {&data.palette_groups.overworld_main, &data.palette_groups.overworld_aux,
355 &data.palette_groups.overworld_animated, &data.palette_groups.hud,
356 &data.palette_groups.global_sprites, &data.palette_groups.armors,
357 &data.palette_groups.sprites_aux1, &data.palette_groups.sprites_aux2,
358 &data.palette_groups.sprites_aux3, &data.palette_groups.grass}) {
359 AddWord(metadata, group->size());
360 for (
size_t i = 0; i < group->size(); ++i) {
361 AddWord(metadata, (*group)[i].size());
362 for (
const auto& color : (*group)[i])
363 AddWord(metadata, color.snes());
366 for (
const auto& group : data.main_blockset_ids)
367 for (
auto id : group)
368 metadata.push_back(
id);
369 for (
const auto& group : data.room_blockset_ids)
370 for (
auto id : group)
371 metadata.push_back(
id);
372 for (
const auto& group : data.spriteset_ids)
373 for (
auto id : group)
374 metadata.push_back(
id);
375 for (
int state = 0; state < 3; ++state) {
376 metadata.push_back(donor->sprite_graphics(state));
377 metadata.push_back(donor->sprite_palette(state));
397 "Lyra bound native assets changed; reload matching source before "
407 return absl::OkStatus();
418 std::set<std::string> active_keys;
419 for (
int i = 0; i < 32; ++i) {
420 const auto& map =
document_->authoring().maps[i];
422 active_keys.insert(key);
428 return absl::FailedPreconditionError(
"Lyra map asset binding changed");
433 return absl::FailedPreconditionError(
"Lyra cached map binding changed");
438 return !active_keys.contains(item.first);
440 return absl::OkStatus();
462 if (map_id < 0xA0 || map_id >= 0xC0 || !canvas.
draw_list)
467 if (!assets->sprites.contains(sprite.
sprite_id)) {
473 auto bitmap = std::make_unique<gfx::Bitmap>();
475 bitmap->SetPalette(assets->graphics.palette());
478 assets->sprites.emplace(sprite.
sprite_id, std::move(bitmap));
479 assets->sprite_bounds.emplace(sprite.
sprite_id, bounds);
481 const auto& bitmap = *assets->sprites.at(sprite.
sprite_id);
482 if (!bitmap.texture())
484 const auto bounds = assets->sprite_bounds.at(sprite.
sprite_id);
487 (map_id % 8 * 512 + sprite.
x * 16 + bounds.x) * canvas.
scale,
489 (map_id % 64 / 8 * 512 + sprite.
y * 16 + bounds.y) * canvas.
scale);
491 (ImTextureID)(intptr_t)bitmap.texture(), min,
492 ImVec2(min.x + bounds.w * canvas.
scale, min.y + bounds.h * canvas.
scale));
503 int map_id, std::optional<core::LyraGameplayAreaSize> size,
506 return absl::FailedPreconditionError(
"Lyra project is unavailable");
508 if (map_id < Document::kFirstMap ||
509 map_id >= Document::kFirstMap + Document::kMapCount) {
510 return absl::OutOfRangeError(
"Gameplay area page must be in A0-BF");
512 const auto* current =
document_->GameplayAreaForMap(map_id);
513 if (size && current && current->parent_map_id != map_id) {
514 return absl::FailedPreconditionError(absl::StrFormat(
515 "Select area parent $%02X to resize, or unassign the whole area first",
516 current->parent_map_id));
519 auto& areas = after.gameplay_areas;
520 const auto existing = std::find_if(
522 return current && area.parent_map_id == current->parent_map_id;
525 if (existing == areas.end()) {
526 return absl::OkStatus();
528 areas.erase(existing);
532 if (existing == areas.end()) {
533 areas.push_back(std::move(area));
535 *existing = std::move(area);
545 return absl::FailedPreconditionError(
"Lyra project is unavailable");
548 const auto before =
document_->authoring();
550 return absl::OkStatus();
551 for (
int i = 0; i < 32; ++i) {
552 if (before.maps[i].properties == after.
maps[i].properties)
554 for (
int state = 0; state < 3; ++state) {
558 after.
maps[i].asset_bindings[state] = assets->binding;
563 std::make_unique<AuthoringAction>(
document_, before, std::move(after)));
564 return absl::OkStatus();
570 return absl::FailedPreconditionError(
571 "Save the current Lyra terrain draft before replacing it");
574 return absl::OkStatus();
579 return absl::OkStatus();
583 return Create(project, overworld);
591 return absl::NotFoundError(
"The project's Lyra terrain file is missing");
599 std::move(
atlas), path, std::move(bytes),
false);
602 return absl::OkStatus();
606 const std::string& path,
609 return absl::FailedPreconditionError(
610 "Save a Yaze project before opening Lyra terrain");
612 return absl::InvalidArgumentError(
"Select a Lyra terrain .json file");
613 auto candidate = *project;
616 const bool reference_changed =
618 candidate.lyra_terrain_file;
621 return absl::OkStatus();
627 return absl::FailedPreconditionError(
628 "Save a Yaze project before creating Lyra terrain");
631 return absl::OkStatus();
633 return Load(project, overworld);
635 return absl::FailedPreconditionError(
"Lyra terrain is already loaded");
639 path.replace_extension(
".lyra-terrain.json");
643 auto candidate = *project;
645 fs::absolute(path).lexically_normal().string();
649 return absl::OkStatus();
656 std::make_shared<Document>(std::move(
document)), std::move(
atlas),
657 fs::absolute(path).lexically_normal().
string(), std::nullopt,
true);
659 return absl::OkStatus();
664 std::string source_path,
665 std::optional<std::string> source_bytes,
683 const auto& live =
document_->authoring().maps[map_index].properties;
685 if (!entry || entry->draft == entry->baseline || entry->draft == live)
692 return document_ && entry && entry->draft != entry->baseline &&
693 entry->draft !=
document_->authoring().maps[map_index].properties;
699 document_->authoring().maps[map_index].properties;
703 std::vector<int> map_ids;
704 for (
int i = 0; i < Document::kMapCount; ++i) {
706 map_ids.push_back(Document::kFirstMap + i);
713 for (
const int map_id : map_ids)
714 absl::StrAppendFormat(&text,
"%s$%02X", text.empty() ?
"" :
", ", map_id);
722 return absl::FailedPreconditionError(
"Lyra project is unavailable");
725 return absl::FailedPreconditionError(absl::StrFormat(
726 "Map $%02X properties changed after these edits began; Revert "
727 "Properties to load the current values, then edit again. Edits "
729 Document::kFirstMap + map_index));
734 return absl::OkStatus();
738 const auto& live =
document_->authoring().maps[map_index].properties;
745 return absl::FailedPreconditionError(absl::StrFormat(
746 "Apply or Revert Lyra properties for %s %s before saving the project.",
747 pending.size() == 1 ?
"map" :
"maps",
FormatMapIds(pending)));
750 return absl::FailedPreconditionError(
751 "Lyra project source changed; draft retained");
756 return absl::FailedPreconditionError(
757 "Lyra tile definitions or graphics profile changed; draft retained");
764 return absl::OkStatus();
773 return absl::OkStatus();
777 const std::string& project_path,
788 return absl::OkStatus();
801 if (!stroke->empty())
803 std::make_unique<TerrainStrokeAction>(
document_, std::move(*stroke)));
807 int y,
int width,
int height,
811 return absl::FailedPreconditionError(
812 "Lyra map assets are unavailable; terrain retained");
814 return absl::FailedPreconditionError(
"Lyra terrain is unavailable");
815 if (!brush.
valid() || width <= 0 || width > Document::kWidth || height <= 0 ||
816 height > 256 || x < 0 || x >= Document::kWidth || y < 0 ||
817 y >= Document::kHeight)
818 return absl::InvalidArgumentError(
"Invalid Lyra terrain brush footprint");
819 for (
const int tile : brush.
tile_ids) {
820 if (tile >=
document_->profile().tile_count)
821 return absl::InvalidArgumentError(
822 "Brush contains a Tile16 outside the Lyra graphics profile");
852 for (
int dy = 0; dy <
pattern_height_ && y + dy < Document::kHeight; ++dy) {
853 for (
int dx = 0; dx <
pattern_width_ && x + dx < Document::kWidth; ++dx) {
865 const int dx = std::abs(x - x0), sx = x0 < x ? 1 : -1;
866 const int dy = -std::abs(y - y0), sy = y0 < y ? 1 : -1;
870 if (!
status_.ok() || (x0 == x && y0 == y))
872 const int twice = 2 * error;
886 int tile, ImVec2 min,
float size)
const {
888 const ImVec2 uv0((tile % 8) * 16.0f / 128.0f, (tile / 8) * 16.0f / height);
889 const ImVec2 uv1(uv0.x + 16.0f / 128.0f, uv0.y + 16.0f / height);
890 draw->AddImage((ImTextureID)(intptr_t)
atlas.
texture(), min,
891 ImVec2(min.x + size, min.y + size), uv0, uv1);
896 !std::isfinite(canvas.
scale) || canvas.
scale <= 0) {
902 constexpr float kWorldYOffset =
903 4 * Document::kMapSize * Document::kTilePixels;
904 const float cell = Document::kTilePixels * canvas.
scale;
908 if (!std::isfinite(start.x) || !std::isfinite(start.y)) {
912 const ImVec2 clip_min = draw->GetClipRectMin();
913 const ImVec2 clip_max = draw->GetClipRectMax();
916 const auto first_cell = [cell](
float clip,
float origin,
int count) {
917 return static_cast<int>(std::clamp(std::floor((clip - origin) / cell), 0.0f,
918 static_cast<float>(count)));
920 const auto last_cell = [cell](
float clip,
float origin,
int count) {
921 return static_cast<int>(std::clamp(std::ceil((clip - origin) / cell), 0.0f,
922 static_cast<float>(count)));
924 const int x0 = first_cell(clip_min.x, start.x, Document::kWidth);
925 const int y0 = first_cell(clip_min.y, start.y, Document::kHeight);
926 const int x1 = last_cell(clip_max.x, start.x, Document::kWidth);
927 const int y1 = last_cell(clip_max.y, start.y, Document::kHeight);
928 for (
int y = y0; y < y1; ++y) {
929 for (
int x = x0; x < x1; ++x) {
934 ImVec2(start.x + x * cell, start.y + y * cell), cell);
939 for (
const auto& connection :
document_->authoring().edge_connections) {
940 if (!connection.passage)
942 const int col = connection.map_b % Document::kColumns;
943 const int row = connection.map_b / Document::kColumns -
944 Document::kFirstMap / Document::kColumns;
945 const float first = connection.passage->first_tile;
946 const float last = connection.passage->last_tile + 1;
947 const bool vertical =
948 connection.map_b - connection.map_a == Document::kColumns;
949 const float edge = (vertical ? row : col) * Document::kMapSize;
950 const float along = (vertical ? col : row) * Document::kMapSize;
951 const ImVec2 min_tiles = vertical ? ImVec2(along + first, edge - 1)
952 : ImVec2(edge - 1, along + first);
953 const ImVec2 max_tiles = vertical ? ImVec2(along + last, edge + 1)
954 : ImVec2(edge + 1, along + last);
955 const ImVec2 p_min(start.x + min_tiles.x * cell,
956 start.y + min_tiles.y * cell);
957 const ImVec2 p_max(start.x + max_tiles.x * cell,
958 start.y + max_tiles.y * cell);
959 draw->AddRectFilled(p_min, p_max, IM_COL32(255, 140, 40, 110));
960 draw->AddRect(p_min, p_max, IM_COL32(255, 200, 120, 230));
std::string Serialize() const
const LyraAuthoringState & authoring() const
const LyraTerrainProfile & profile() const
std::vector< uint8_t > source_rom_
bool PropertyConflict(int map_index) const
absl::Status PaintPattern(const TileBrush &brush, int x, int y, int width, int height, bool finalize, UndoManager &history)
bool PropertyPending(int map_index) const
absl::Status PrepareMapAssets(zelda3::Overworld &overworld, int state)
absl::Status SaveAs(project::YazeProject *project, const std::string &project_path, zelda3::Overworld &overworld)
std::optional< std::string > source_bytes_
absl::Status SetGameplayArea(int map_id, std::optional< core::LyraGameplayAreaSize > size, zelda3::Overworld &overworld, UndoManager &history)
void DrawSpecialWorld(const gui::CanvasRuntime &canvas)
std::shared_ptr< core::LyraTerrainDocument > document_
std::optional< core::LyraAuthoringState > validated_authoring_
std::vector< int > PendingPropertyMaps() const
absl::Status ValidateMapAssets(zelda3::Overworld &overworld, const core::LyraAuthoringState &authoring)
gfx::Bitmap & MapAtlas(int map_id)
absl::Status Create(project::YazeProject *project, zelda3::Overworld &overworld)
std::array< std::optional< PropertyDraft >, core::LyraTerrainDocument::kMapCount > property_drafts_
absl::StatusOr< std::unique_ptr< MapAssets > > ResolveMapAssets(zelda3::Overworld &overworld, const core::LyraMapProperties &properties, int state, bool textures)
void InstallDocument(std::shared_ptr< core::LyraTerrainDocument > document, gfx::Bitmap atlas, std::string source_path, std::optional< std::string > source_bytes, bool unpublished)
std::optional< ImVec2 > last_cell_
absl::Status EditAuthoring(core::LyraAuthoringState after, zelda3::Overworld &overworld, UndoManager &history)
const core::LyraTerrainDocument * document() const
void RevertPropertyDraft(int map_index)
const gfx::Bitmap & atlas() const
absl::Status authoring_assets_status_
static std::string FormatMapIds(const std::vector< int > &map_ids)
void PaintStamp(int x, int y)
std::array< MapAssets *, 32 > displayed_assets_
std::array< char, 64 > sprite_filter_
std::vector< uint8_t > source_graphics_
gfx::Bitmap * MapGraphics(int map_id)
std::unordered_map< std::string, std::unique_ptr< MapAssets > > map_assets_
void DrawSpritePreview(const gui::CanvasRuntime &canvas, int map_id, const core::LyraSpritePlacement &sprite)
void DrawTile(ImDrawList *draw, const gfx::Bitmap &atlas, int tile, ImVec2 min, float size) const
std::optional< core::LyraAuthoringState > drag_before_
absl::Status BindSource(zelda3::Overworld &overworld, core::LyraTerrainProfile *profile, gfx::Bitmap *atlas=nullptr)
absl::StatusOr< core::LyraMapProperties > CopyNativeGraphicsSettings(zelda3::Overworld &overworld, int source_map, core::LyraMapProperties candidate)
bool pending_project_commit_
absl::Status CheckAssetSources(zelda3::Overworld &overworld)
absl::Status ApplyPropertyDraft(int map_index, zelda3::Overworld &overworld, UndoManager &history)
gfx::Bitmap unavailable_atlas_
absl::Status Load(project::YazeProject *project, zelda3::Overworld &overworld)
uint32_t selected_entity_
absl::Status Save(project::YazeProject *project, zelda3::Overworld &overworld)
void FinishStroke(UndoManager &history)
absl::Status ApplyAuthoring(core::LyraAuthoringState after, zelda3::Overworld &overworld, UndoManager &history)
std::string copy_message_
void PaintLine(int x, int y)
core::LyraMapProperties & PropertyDraftFor(int map_index)
absl::Status ValidateSave(project::YazeProject *project, zelda3::Overworld &overworld)
std::string source_metadata_
absl::Status source_status_
absl::Status Open(project::YazeProject *project, const std::string &path, zelda3::Overworld &overworld)
Abstract base for all undoable actions (Command pattern)
Manages undo/redo stacks for a single editor.
void Push(std::unique_ptr< UndoAction > action)
std::shared_ptr< Document > document_
absl::Status Undo() override
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
core::LyraAuthoringState after_
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
AuthoringAction(std::shared_ptr< Document > document, core::LyraAuthoringState before, core::LyraAuthoringState after)
absl::Status Redo() override
TerrainStrokeAction(std::shared_ptr< Document > document, core::LyraTerrainStroke stroke)
core::LyraTerrainStroke stroke_
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
absl::Status Redo() override
absl::Status Undo() override
std::shared_ptr< Document > document_
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void RetireBitmap(Bitmap &bitmap)
Explicitly retire resources owned by a bitmap that is being erased.
Represents a bitmap image optimized for SNES ROM hacking.
TextureHandle texture() const
const std::vector< gfx::Tile16 > & tiles16() const
bool is_lyra_preview() const
auto overworld_map(int i) const
A class for managing sprites in the overworld and underworld.
void RenderPreviewGraphics(std::span< const uint8_t > graphics, const SpriteOamLayout *layout_override=nullptr, std::span< const uint8_t > graphics_resource={})
auto preview_graphics() const
SDL_Rect preview_bounds() const
#define ASSIGN_OR_RETURN(type_variable_name, expression)
absl::Status PublishSourceArtifacts(const SourceArtifactPublicationLock &lock, std::vector< SourceArtifactUpdate > updates, std::string_view expected_primary_sha256, SourceArtifactReadbackValidator readback_validator)
absl::StatusOr< std::vector< int > > LyraGameplayAreaFootprint(int parent_map_id, LyraGameplayAreaSize size)
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::StatusOr< std::unique_ptr< SourceArtifactPublicationLock > > AcquireSourceArtifactPublicationLock(const std::vector< fs::path > &targets, const SourceArtifactPublisherLabels &labels, const std::vector< fs::path > &read_only_targets)
void AddWord(std::string &bytes, uint16_t word)
absl::Status ValidateTarget(const fs::path &path, const project::YazeProject &project)
absl::StatusOr< std::optional< std::string > > ReadSource(const fs::path &path)
Editors are the view controllers for the application.
absl::StatusOr< std::string > PublishLyraTerrainSource(const project::YazeProject &project, const std::string &source_path, const std::optional< std::string > &source_bytes, const core::LyraTerrainDocument &document)
uint16_t TileInfoToWord(TileInfo tile_info)
constexpr int kOverworldSpritePaletteGroup
std::string LyraMapAssetKey(const core::LyraMapProperties &p, int state)
absl::StatusOr< LyraNativeMapAssets > ResolveLyraMapAssets(Overworld &overworld, const core::LyraMapProperties &p, int state)
absl::Status BindLyraTerrainSource(Overworld &overworld, core::LyraTerrainProfile *profile, gfx::Bitmap *atlas)
#define RETURN_IF_ERROR(expr)
std::array< LyraMapAuthoring, 32 > maps
std::array< uint8_t, 8 > graphics_sheets
std::array< int, 3 > sprite_palettes
std::array< int, 3 > sprite_graphics
std::unordered_map< int, std::unique_ptr< gfx::Bitmap > > sprites
core::LyraMapProperties draft
int at(int x, int y) const
std::vector< int > tile_ids
Modern project structure with comprehensive settings consolidation.
bool project_opened() const
std::string lyra_terrain_file
bool IsOracleCompatible() const
absl::Status SaveAsWithLyraTerrain(const std::string &new_path, const core::LyraTerrainDocument &terrain, const std::optional< std::string > &expected_source_bytes)
std::string GetAbsolutePath(const std::string &relative_path) const