yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_editor.cc
Go to the documentation of this file.
1// Related header
3
4#ifndef IM_PI
5#define IM_PI 3.14159265358979323846f
6#endif
7
8// C system headers
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12
13// C++ standard library headers
14#include <algorithm>
15#include <exception>
16#include <filesystem>
17#include <iostream>
18#include <memory>
19#include <new>
20#include <ostream>
21#include <string>
22#include <unordered_map>
23#include <utility>
24#include <vector>
25
26// Third-party library headers
27#include "absl/status/status.h"
28#include "absl/strings/str_format.h"
29#include "imgui/imgui.h"
30
31// Project headers
43// Note: All overworld panels now self-register via REGISTER_PANEL macro:
44// AreaGraphicsPanel, DebugWindowPanel, GfxGroupsPanel, MapPropertiesPanel,
45// OverworldCanvasPanel, ScratchSpacePanel, Tile16EditorPanel, Tile16SelectorPanel,
46// Tile8SelectorPanel, UsageStatisticsPanel, V3SettingsPanel
51#include "app/gfx/core/bitmap.h"
61#include "app/gui/core/icons.h"
63#include "app/gui/core/style.h"
68#include "core/asar_wrapper.h"
69#include "core/features.h"
70#include "core/project.h"
71#include "rom/rom.h"
72#include "util/file_util.h"
73#include "util/hex.h"
74#include "util/log.h"
75#include "util/macro.h"
76#include "zelda3/common.h"
84
85namespace yaze::editor {
86
88 // Register panels with PanelManager (dependency injection)
90 return;
91 }
92 auto* panel_manager = dependencies_.panel_manager;
93
94 // Initialize renderer from dependencies
96
97 // Initialize MapRefreshCoordinator (must be before callbacks that use it)
99
100 // Register Overworld Canvas (main canvas panel with toolset)
101
102 // Note: All EditorPanel instances now self-register via REGISTER_PANEL macro
103 // and use ContentRegistry::Context to access the current editor.
104 // See comment at include section for full list of panels.
105
106 // Original initialization code below:
107 // Initialize MapPropertiesSystem with canvas and bitmap data
108 // Initialize cards
109 usage_stats_card_ = std::make_unique<UsageStatisticsCard>(&overworld_);
110 debug_window_card_ = std::make_unique<DebugWindowCard>();
111
112 map_properties_system_ = std::make_unique<MapPropertiesSystem>(
114
115 // Set up refresh callbacks for MapPropertiesSystem
116 map_properties_system_->SetRefreshCallbacks(
117 [this]() { this->RefreshMapProperties(); },
118 [this]() { this->RefreshOverworldMap(); },
119 [this]() -> absl::Status { return this->RefreshMapPalette(); },
120 [this]() -> absl::Status { return this->RefreshTile16Blockset(); },
121 [this](int map_index) { this->ForceRefreshGraphics(map_index); });
122
123 // Initialize OverworldSidebar
124 sidebar_ = std::make_unique<OverworldSidebar>(&overworld_, rom_,
126
127 // Initialize OverworldEntityRenderer for entity visualization
128 entity_renderer_ = std::make_unique<OverworldEntityRenderer>(
130
131 // Initialize Toolbar
132 toolbar_ = std::make_unique<OverworldToolbar>();
133 toolbar_->on_refresh_graphics = [this]() {
134 // Invalidate cached graphics for the current map area to force re-render
135 // with potentially new palette/graphics settings
138 };
139 toolbar_->on_refresh_map = [this]() {
141 };
142
143 toolbar_->on_save_to_scratch = [this]() {
145 };
146 toolbar_->on_load_from_scratch = [this]() {
148 };
149
150 // Initialize OverworldCanvasRenderer for canvas and panel drawing
151 canvas_renderer_ = std::make_unique<OverworldCanvasRenderer>(this);
152
156}
157
161 deps.overworld = &overworld_;
162 deps.maps_bmp = &maps_bmp_;
170 deps.game_state = &game_state_;
171 deps.rom = rom_;
173
174 TilePaintingCallbacks callbacks;
175 callbacks.create_undo_point = [this](int map_id, int world, int x, int y,
176 int old_tile_id) {
177 this->CreateUndoPoint(map_id, world, x, y, old_tile_id);
178 };
179 callbacks.finalize_paint_operation = [this]() {
181 };
182 callbacks.refresh_overworld_map = [this]() {
183 this->RefreshOverworldMap();
184 };
185 callbacks.refresh_overworld_map_on_demand = [this](int map_index) {
186 this->RefreshOverworldMapOnDemand(map_index);
187 };
188 callbacks.scroll_blockset_to_current_tile = [this]() {
190 };
191
192 tile_painting_ = std::make_unique<TilePaintingManager>(deps, callbacks);
193}
194
198 ctx.overworld = &overworld_;
199 ctx.rom = rom_;
208 ctx.maps_bmp = &maps_bmp_;
211
213 callbacks.refresh_overworld_map = [this]() {
214 this->RefreshOverworldMap();
215 };
216 callbacks.refresh_tile16_blockset = [this]() -> absl::Status {
217 return this->RefreshTile16Blockset();
218 };
219 callbacks.ensure_map_texture = [this](int map_index) {
220 this->EnsureMapTexture(map_index);
221 };
222 callbacks.pick_tile16_from_hovered_canvas = [this]() -> bool {
223 return this->PickTile16FromHoveredCanvas();
224 };
225 callbacks.is_entity_hovered = [this]() -> bool {
226 return entity_renderer_ && entity_renderer_->hovered_entity() != nullptr;
227 };
228
229 canvas_nav_ = std::make_unique<CanvasNavigationManager>();
230 canvas_nav_->Initialize(ctx, callbacks);
231}
232
233absl::Status OverworldEditor::Load() {
234 gfx::ScopedTimer timer("OverworldEditor::Load");
235
236 LOG_DEBUG("OverworldEditor", "Loading overworld.");
237 if (!rom_ || !rom_->is_loaded()) {
238 return absl::FailedPreconditionError("ROM not loaded");
239 }
240
241 // Clear undo/redo state when loading new ROM data
244
249
250 // CRITICAL FIX: Initialize tile16 editor with the correct overworld palette
253
254 // Set up callback for when tile16 changes are committed
255 tile16_editor_.set_on_changes_committed([this]() -> absl::Status {
256 // Regenerate the overworld editor's tile16 blockset
258
259 // Force refresh of the current overworld map to show changes
261
262 LOG_DEBUG("OverworldEditor",
263 "Overworld editor refreshed after Tile16 changes");
264 return absl::OkStatus();
265 });
266
267 // Set up entity insertion callback for MapPropertiesSystem
269 map_properties_system_->SetEntityCallbacks(
270 [this](const std::string& entity_type) {
271 HandleEntityInsertion(entity_type);
272 });
273
274 // Set up tile16 edit callback for context menu in MOUSE mode
275 map_properties_system_->SetTile16EditCallback(
276 [this]() { HandleTile16Edit(); });
277 }
278
280
281 // Register as palette listener to refresh graphics when palettes change
282 if (palette_listener_id_ < 0) {
284 [this](const std::string& group_name, int palette_index) {
285 // Only respond to overworld-related palette changes
286 if (group_name == "ow_main" || group_name == "ow_animated" ||
287 group_name == "ow_aux" || group_name == "grass") {
288 LOG_DEBUG("OverworldEditor",
289 "Palette change detected: %s, refreshing current map",
290 group_name.c_str());
291 // Refresh current map graphics to reflect palette changes
292 if (current_map_ >= 0 && all_gfx_loaded_) {
294 }
295 }
296 });
297 LOG_DEBUG("OverworldEditor", "Registered as palette listener (ID: %d)",
299 }
300
301 all_gfx_loaded_ = true;
302 return absl::OkStatus();
303}
304
306 status_ = absl::OkStatus();
307
308 // Safety check: Ensure ROM is loaded and graphics are ready
309 if (!rom_ || !rom_->is_loaded()) {
310 gui::CenterText("No ROM loaded");
311 return absl::OkStatus();
312 }
313
314 if (!all_gfx_loaded_) {
315 gui::CenterText("Loading graphics...");
316 return absl::OkStatus();
317 }
318
319 // Process deferred textures for smooth loading
321
322 // Update blockset atlas with any pending tile16 changes for live preview
323 // Tile cache now uses copy semantics so this is safe to enable
326 }
327
328 // Early return if panel_manager is not available
329 // (panels won't be drawn without it, so no point continuing)
331 return status_;
332 }
333
335 return status_;
336 }
337
338 // ===========================================================================
339 // Main Overworld Canvas
340 // ===========================================================================
341 // The panels (Tile16 Selector, Area Graphics, etc.) are now managed by
342 // EditorPanel/PanelManager and drawn automatically. This section only
343 // handles the main canvas and toolbar.
344
345 // ===========================================================================
346 // Non-Panel Windows (not managed by EditorPanel system)
347 // ===========================================================================
348 // These are separate feature windows, not part of the panel system
349
350 // Custom Background Color Editor
352 ImGui::SetNextWindowSize(ImVec2(400, 500), ImGuiCond_FirstUseEver);
353 if (ImGui::Begin(ICON_MD_FORMAT_COLOR_FILL " Background Color",
355 if (rom_->is_loaded() && overworld_.is_loaded() &&
357 map_properties_system_->DrawCustomBackgroundColorEditor(
359 }
360 }
361 ImGui::End();
362 }
363
364 // Visual Effects Editor (Subscreen Overlays)
366 ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_FirstUseEver);
367 if (ImGui::Begin(ICON_MD_LAYERS " Visual Effects Editor###OverlayEditor",
369 if (rom_->is_loaded() && overworld_.is_loaded() &&
371 map_properties_system_->DrawOverlayEditor(current_map_,
373 }
374 }
375 ImGui::End();
376 }
377
378 // Note: Tile16 Editor is now managed as an EditorPanel (Tile16EditorPanel)
379 // It uses UpdateAsPanel() which provides a context menu instead of MenuBar
380
381 // ===========================================================================
382 // Centralized Entity Interaction Logic (extracted to dedicated method)
383 // ===========================================================================
385
386 // Entity insertion error popup
387 if (ImGui::BeginPopupModal("Entity Insert Error", nullptr,
388 ImGuiWindowFlags_AlwaysAutoResize)) {
389 const auto& theme = AgentUI::GetTheme();
390 ImGui::TextColored(theme.status_error,
391 ICON_MD_ERROR " Entity Insertion Failed");
392 ImGui::Separator();
393 ImGui::TextWrapped("%s", entity_insert_error_message_.c_str());
394 ImGui::Separator();
395 ImGui::TextDisabled("Tip: Delete an existing entity to free up a slot.");
396 ImGui::Spacing();
397 if (ImGui::Button("OK", ImVec2(120, 0))) {
399 ImGui::CloseCurrentPopup();
400 }
401 ImGui::EndPopup();
402 }
403 // --- END CENTRALIZED LOGIC ---
404
405 // ROM Upgrade Popup (rendered outside toolbar to avoid ID conflicts)
406 if (ImGui::BeginPopupModal("UpgradeROMVersion", nullptr,
407 ImGuiWindowFlags_AlwaysAutoResize)) {
408 ImGui::Text(ICON_MD_UPGRADE " Upgrade ROM to ZSCustomOverworld");
409 ImGui::Separator();
410 ImGui::TextWrapped(
411 "This will apply the ZSCustomOverworld ASM patch to your ROM,\n"
412 "enabling advanced features like custom tile graphics, animated GFX,\n"
413 "wide/tall areas, and more.");
414 ImGui::Separator();
415
416 uint8_t current_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
417 ImGui::Text("Current Version: %s",
418 current_version == 0xFF
419 ? "Vanilla"
420 : absl::StrFormat("v%d", current_version).c_str());
421
422 static int target_version = 3;
423 ImGui::RadioButton("v2 (Basic features)", &target_version, 2);
424 ImGui::SameLine();
425 ImGui::RadioButton("v3 (All features)", &target_version, 3);
426
427 ImGui::Separator();
428
429 if (ImGui::Button(ICON_MD_CHECK " Apply Upgrade", ImVec2(150, 0))) {
430 auto status = ApplyZSCustomOverworldASM(target_version);
431 if (status.ok()) {
432 // CRITICAL: Reload the editor to reflect changes
433 status_ = Clear();
434 status_ = Load();
435 ImGui::CloseCurrentPopup();
436 } else {
437 LOG_ERROR("OverworldEditor", "Upgrade failed: %s",
438 status.message().data());
439 }
440 }
441 ImGui::SameLine();
442 if (ImGui::Button(ICON_MD_CANCEL " Cancel", ImVec2(150, 0))) {
443 ImGui::CloseCurrentPopup();
444 }
445
446 ImGui::EndPopup();
447 }
448
449 // All editor windows are now rendered in Update() using either EditorPanel
450 // system or MapPropertiesSystem for map-specific panels. This keeps the
451 // toolset clean and prevents ImGui ID stack issues.
452
453 // Legacy window code removed - windows rendered in Update() include:
454 // - Graphics Groups (EditorPanel)
455 // - Area Configuration (MapPropertiesSystem)
456 // - Background Color Editor (MapPropertiesSystem)
457 // - Visual Effects Editor (MapPropertiesSystem)
458 // - Tile16 Editor, Usage Stats, etc. (EditorPanels)
459
460 // Handle keyboard shortcuts (centralized in dedicated method)
462
463 return absl::OkStatus();
464}
465
467 // Skip processing if any ImGui item is active (e.g., text input)
468 if (ImGui::IsAnyItemActive()) {
469 return;
470 }
471
472 using enum EditingMode;
473
474 const bool ctrl_held = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
475 ImGui::IsKeyDown(ImGuiKey_RightCtrl);
476 const bool alt_held = ImGui::IsKeyDown(ImGuiKey_LeftAlt) ||
477 ImGui::IsKeyDown(ImGuiKey_RightAlt);
478
479 // Track mode changes for canvas usage mode updates
480 EditingMode old_mode = current_mode;
481
482 // Tool shortcuts (1-2 for mode selection)
483 if (ImGui::IsKeyPressed(ImGuiKey_1, false)) {
485 } else if (ImGui::IsKeyPressed(ImGuiKey_2, false)) {
487 }
488
489 // Brush/Fill shortcuts (avoid clobbering Ctrl-based shortcuts).
490 if (!ctrl_held && !alt_held) {
491 if (ImGui::IsKeyPressed(ImGuiKey_B, false)) {
493 }
494 if (ImGui::IsKeyPressed(ImGuiKey_F, false)) {
496 }
497 if (ImGui::IsKeyPressed(ImGuiKey_I, false)) {
499 }
500 }
501
502 // Update canvas usage mode when mode changes
503 if (old_mode != current_mode) {
506 } else {
507 // DRAW_TILE and FILL_TILE are both tile painting interactions.
509 }
510 }
511
512 // Entity editing shortcuts (3-8)
514
515 // View shortcuts
516 if (ImGui::IsKeyPressed(ImGuiKey_F11, false)) {
518 }
519
520 // Toggle map lock with Ctrl+L
521 if (ctrl_held && ImGui::IsKeyPressed(ImGuiKey_L, false)) {
523 }
524
525 // Toggle Tile16 editor with Ctrl+T
526 if (ctrl_held && ImGui::IsKeyPressed(ImGuiKey_T, false)) {
530 }
531 }
532
533 // Undo/Redo shortcuts
535}
536
565
567 // Check for Ctrl key (either left or right)
568 bool ctrl_held = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) ||
569 ImGui::IsKeyDown(ImGuiKey_RightCtrl);
570 if (!ctrl_held) {
571 return;
572 }
573
574 // Ctrl+Z: Undo (or Ctrl+Shift+Z: Redo)
575 if (ImGui::IsKeyPressed(ImGuiKey_Z, false)) {
576 bool shift_held = ImGui::IsKeyDown(ImGuiKey_LeftShift) ||
577 ImGui::IsKeyDown(ImGuiKey_RightShift);
578 if (shift_held) {
579 status_ = Redo(); // Ctrl+Shift+Z = Redo
580 } else {
581 status_ = Undo(); // Ctrl+Z = Undo
582 }
583 }
584
585 // Ctrl+Y: Redo (Windows style)
586 if (ImGui::IsKeyPressed(ImGuiKey_Y, false)) {
587 status_ = Redo();
588 }
589}
590
592 // Get hovered entity from previous frame's rendering pass
593 zelda3::GameEntity* hovered_entity =
594 entity_renderer_ ? entity_renderer_->hovered_entity() : nullptr;
595
596 // Handle all MOUSE mode interactions here
598 HandleEntityContextMenus(hovered_entity);
599 HandleEntityDoubleClick(hovered_entity);
600 }
601
602 // Process any pending entity insertion from context menu
603 // This must be called outside the context menu popup context for OpenPopup
604 // to work
606
607 // Draw entity editor popups and update entity data
609}
610
612 zelda3::GameEntity* hovered_entity) {
613 if (!ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
614 return;
615 }
616
617 if (!hovered_entity) {
618 return;
619 }
620
621 current_entity_ = hovered_entity;
622 switch (hovered_entity->entity_type_) {
624 current_exit_ = *static_cast<zelda3::OverworldExit*>(hovered_entity);
625 ImGui::OpenPopup(
627 .c_str());
628 break;
631 *static_cast<zelda3::OverworldEntrance*>(hovered_entity);
632 ImGui::OpenPopup(
634 .c_str());
635 break;
637 current_item_ = *static_cast<zelda3::OverworldItem*>(hovered_entity);
638 ImGui::OpenPopup(
640 .c_str());
641 break;
643 current_sprite_ = *static_cast<zelda3::Sprite*>(hovered_entity);
644 ImGui::OpenPopup(
646 .c_str());
647 break;
648 default:
649 break;
650 }
651}
652
654 zelda3::GameEntity* hovered_entity) {
655 if (!hovered_entity || !ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
656 return;
657 }
658
661 static_cast<zelda3::OverworldExit*>(hovered_entity)->room_id_;
662 } else if (hovered_entity->entity_type_ ==
665 static_cast<zelda3::OverworldEntrance*>(hovered_entity)->entrance_id_;
666 }
667}
668
700
701// DrawOverworldMaps - now in OverworldCanvasRenderer
702
703// DrawOverworldEdits - now delegated to TilePaintingManager
704
705// RenderUpdatedMapBitmap - now delegated to TilePaintingManager
706
708 tile_painting_->CheckForOverworldEdits();
709}
710
711absl::Status OverworldEditor::Copy() {
713 return absl::FailedPreconditionError("Clipboard unavailable");
714 }
715 // If a rectangle selection exists, copy its tile16 IDs into shared clipboard
717 !ow_map_canvas_.selected_points().empty()) {
718 std::vector<int> ids;
719 // selected_points are now stored in world coordinates
720 const auto start = ow_map_canvas_.selected_points()[0];
721 const auto end = ow_map_canvas_.selected_points()[1];
722 const int start_x =
723 static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
724 const int end_x =
725 static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
726 const int start_y =
727 static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
728 const int end_y =
729 static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
730 const int width = end_x - start_x + 1;
731 const int height = end_y - start_y + 1;
732 ids.reserve(width * height);
735 for (int y = start_y; y <= end_y; ++y) {
736 for (int x = start_x; x <= end_x; ++x) {
737 ids.push_back(overworld_.GetTile(x, y));
738 }
739 }
740
745 return absl::OkStatus();
746 }
747 // Single tile copy fallback
748 if (current_tile16_ >= 0) {
753 return absl::OkStatus();
754 }
755 return absl::FailedPreconditionError("Nothing selected to copy");
756}
757
760 return absl::FailedPreconditionError("Clipboard unavailable");
761 }
763 return absl::FailedPreconditionError("Clipboard empty");
764 }
765 if (ow_map_canvas_.points().empty() &&
767 return absl::FailedPreconditionError("No paste target");
768 }
769
770 // Determine paste anchor position (use current mouse drawn tile position)
771 // Unscale coordinates to get world position
772 const ImVec2 scaled_anchor = ow_map_canvas_.drawn_tile_position();
773 float scale = ow_map_canvas_.global_scale();
774 if (scale <= 0.0f)
775 scale = 1.0f;
776 const ImVec2 anchor =
777 ImVec2(scaled_anchor.x / scale, scaled_anchor.y / scale);
778
779 // Compute anchor in tile16 grid within the current map
780 const int tile16_x =
781 (static_cast<int>(anchor.x) % kOverworldMapSize) / kTile16Size;
782 const int tile16_y =
783 (static_cast<int>(anchor.y) % kOverworldMapSize) / kTile16Size;
784
785 auto& selected_world =
786 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
787 : (current_world_ == 1) ? overworld_.mutable_map_tiles()->dark_world
788 : overworld_.mutable_map_tiles()->special_world;
789
790 const int world_offset = current_world_ * 0x40;
791 const int local_map = current_map_ - world_offset;
792 const int superY = local_map / 8;
793 const int superX = local_map % 8;
794
798
799 // Guard
800 if (width * height != static_cast<int>(ids.size())) {
801 return absl::InternalError("Clipboard dimensions mismatch");
802 }
803
804 bool any_changed = false;
805 for (int dy = 0; dy < height; ++dy) {
806 for (int dx = 0; dx < width; ++dx) {
807 const int id = ids[dy * width + dx];
808 const int gx = tile16_x + dx;
809 const int gy = tile16_y + dy;
810
811 const int global_x = superX * 32 + gx;
812 const int global_y = superY * 32 + gy;
813 if (global_x < 0 || global_x >= 256 || global_y < 0 || global_y >= 256)
814 continue;
815 const int old_tile_id = selected_world[global_x][global_y];
816 if (old_tile_id == id) {
817 continue;
818 }
819 CreateUndoPoint(current_map_, current_world_, global_x, global_y,
820 old_tile_id);
821 selected_world[global_x][global_y] = id;
822 any_changed = true;
823 }
824 }
825
826 if (any_changed) {
828 rom_->set_dirty(true);
830 }
831 return absl::OkStatus();
832}
833
835 if (canvas_nav_) return canvas_nav_->CheckForCurrentMap();
836 return absl::OkStatus();
837}
838
840 // Delegate to the existing GfxGroupEditor
841 if (rom_ && rom_->is_loaded()) {
842 return gfx_group_editor_.Update();
843 } else {
844 gui::CenterText("No ROM loaded");
845 return absl::OkStatus();
846 }
847}
848
849// DrawV3Settings - now in OverworldCanvasRenderer
850
851// DrawMapProperties - now in OverworldCanvasRenderer
852
853absl::Status OverworldEditor::Save() {
854 // HACK MANIFEST VALIDATION
855 const bool saving_maps = core::FeatureFlags::get().overworld.kSaveOverworldMaps;
856 if (saving_maps && dependencies_.project &&
858 const auto& manifest = dependencies_.project->hack_manifest;
859 const auto write_policy = dependencies_.project->rom_metadata.write_policy;
860
861 // Calculate memory ranges that would be written by overworld map saves.
862 // `ranges` are PC offsets (ROM file offsets). The hack manifest is in SNES
863 // address space (LoROM), so convert before analysis.
864 auto ranges = overworld_.GetProjectedWriteRanges();
865 auto conflicts = manifest.AnalyzePcWriteRanges(ranges);
866 if (!conflicts.empty()) {
867 std::string error_msg =
868 "Hack manifest write conflicts while saving overworld maps:\n\n";
869 for (const auto& conflict : conflicts) {
870 absl::StrAppend(
871 &error_msg,
872 absl::StrFormat("- Address 0x%06X is %s", conflict.address,
873 core::AddressOwnershipToString(conflict.ownership)));
874 if (!conflict.module.empty()) {
875 absl::StrAppend(&error_msg, " (Module: ", conflict.module, ")");
876 }
877 absl::StrAppend(&error_msg, "\n");
878 }
879
880 if (write_policy == project::RomWritePolicy::kAllow) {
881 LOG_DEBUG("OverworldEditor", "%s", error_msg.c_str());
882 } else {
883 LOG_WARN("OverworldEditor", "%s", error_msg.c_str());
884 }
885
887 write_policy == project::RomWritePolicy::kWarn) {
889 "Save warning: write conflict with hack manifest (see log)",
891 }
892
893 if (write_policy == project::RomWritePolicy::kBlock) {
896 "Save blocked: write conflict with hack manifest (see log)",
898 }
899 return absl::PermissionDeniedError("Write conflict with Hack Manifest");
900 }
901 }
902 }
903
904 if (saving_maps) {
909 }
910 if (core::FeatureFlags::get().overworld.kSaveOverworldEntrances) {
912 }
913 if (core::FeatureFlags::get().overworld.kSaveOverworldExits) {
915 }
916 if (core::FeatureFlags::get().overworld.kSaveOverworldItems) {
918 }
919 if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) {
922 }
923 return absl::OkStatus();
924}
925
926// ============================================================================
927// Undo/Redo System Implementation
928// ============================================================================
929
931 switch (world) {
932 case 0:
933 return overworld_.mutable_map_tiles()->light_world;
934 case 1:
935 return overworld_.mutable_map_tiles()->dark_world;
936 default:
937 return overworld_.mutable_map_tiles()->special_world;
938 }
939}
940
941void OverworldEditor::CreateUndoPoint(int map_id, int world, int x, int y,
942 int old_tile_id) {
943 auto now = std::chrono::steady_clock::now();
944
945 // Check if we should batch with current operation (same map, same world,
946 // within timeout)
947 if (current_paint_operation_.has_value() &&
948 current_paint_operation_->map_id == map_id &&
949 current_paint_operation_->world == world &&
951 // Add to existing operation
952 current_paint_operation_->tile_changes.emplace_back(std::make_pair(x, y),
953 old_tile_id);
954 } else {
955 // Finalize any pending operation before starting a new one
957
958 // Start new operation
960 OverworldUndoPoint{.map_id = map_id,
961 .world = world,
962 .tile_changes = {{{x, y}, old_tile_id}},
963 .timestamp = now};
964 }
965
966 last_paint_time_ = now;
967}
968
970 if (!current_paint_operation_.has_value()) {
971 return;
972 }
973
974 // Push to the UndoManager (new framework path).
975 auto& world_tiles = GetWorldTiles(current_paint_operation_->world);
976 std::vector<OverworldTileChange> changes;
977 changes.reserve(current_paint_operation_->tile_changes.size());
978 for (const auto& [coords, old_tile_id] :
979 current_paint_operation_->tile_changes) {
980 auto [x, y] = coords;
981 int new_tile_id = world_tiles[x][y];
982 changes.push_back({x, y, old_tile_id, new_tile_id});
983 }
984 auto action = std::make_unique<OverworldTilePaintAction>(
986 std::move(changes), &overworld_,
987 [this]() { RefreshOverworldMap(); });
988 undo_manager_.Push(std::move(action));
989
991}
992
993
994absl::Status OverworldEditor::Undo() {
995 // Finalize any pending paint operation first
997 return undo_manager_.Undo();
998}
999
1001 return undo_manager_.Redo();
1002}
1003
1004// ============================================================================
1005
1007 gfx::ScopedTimer timer("LoadGraphics");
1008
1009 LOG_DEBUG("OverworldEditor", "Loading overworld.");
1010 // Load the Link to the Past overworld.
1011 {
1012 gfx::ScopedTimer load_timer("Overworld::Load");
1014 }
1016
1017 // Fix: Set transparency for the first color of each 16-color subpalette
1018 // This ensures the background color (backdrop) shows through
1019 for (size_t i = 0; i < palette_.size(); i += 16) {
1020 if (i < palette_.size()) {
1021 palette_[i].set_transparent(true);
1022 }
1023 }
1024
1025 LOG_DEBUG("OverworldEditor", "Loading overworld graphics (optimized).");
1026
1027 // Phase 1: Create bitmaps without textures for faster loading
1028 // This avoids blocking the main thread with GPU texture creation
1029 {
1030 gfx::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics");
1036 }
1037
1038 LOG_DEBUG("OverworldEditor",
1039 "Loading overworld tileset (deferred textures).");
1040 {
1041 gfx::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
1042 tile16_blockset_bmp_.Create(0x80, 0x2000, 0x08,
1047 }
1048 map_blockset_loaded_ = true;
1049
1050 // Copy the tile16 data into individual tiles.
1051 auto tile16_blockset_data = overworld_.tile16_blockset_data();
1052 LOG_DEBUG("OverworldEditor", "Loading overworld tile16 graphics.");
1053
1054 {
1055 gfx::ScopedTimer tilemap_timer("CreateTilemap");
1057 gfx::CreateTilemap(renderer_, tile16_blockset_data, 0x80, 0x2000,
1059
1060 // Queue texture creation for the tile16 blockset atlas
1065 }
1066 }
1067
1068 // Phase 2: Create bitmaps only for essential maps initially
1069 // Non-essential maps will be created on-demand when accessed
1070 // IMPORTANT: Must match kEssentialMapsPerWorld in overworld.cc
1071#ifdef __EMSCRIPTEN__
1072 constexpr int kEssentialMapsPerWorld = 4; // Match WASM build in overworld.cc
1073#else
1074 constexpr int kEssentialMapsPerWorld =
1075 16; // Match native build in overworld.cc
1076#endif
1077 constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
1078 constexpr int kDarkWorldEssential =
1079 zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld;
1080 constexpr int kSpecialWorldEssential =
1081 zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
1082
1083 LOG_DEBUG(
1084 "OverworldEditor",
1085 "Creating bitmaps for essential maps only (first %d maps per world)",
1086 kEssentialMapsPerWorld);
1087
1088 std::vector<gfx::Bitmap*> maps_to_texture;
1089 maps_to_texture.reserve(kEssentialMapsPerWorld *
1090 3); // 8 maps per world * 3 worlds
1091
1092 {
1093 gfx::ScopedTimer maps_timer("CreateEssentialOverworldMaps");
1094 for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
1095 bool is_essential = false;
1096
1097 // Check if this is an essential map
1098 if (i < kLightWorldEssential) {
1099 is_essential = true;
1100 } else if (i >= zelda3::kDarkWorldMapIdStart && i < kDarkWorldEssential) {
1101 is_essential = true;
1102 } else if (i >= zelda3::kSpecialWorldMapIdStart &&
1103 i < kSpecialWorldEssential) {
1104 is_essential = true;
1105 }
1106
1107 if (is_essential) {
1109 auto palette = overworld_.current_area_palette();
1110 try {
1111 // Create bitmap data and surface but defer texture creation
1114 maps_bmp_[i].SetPalette(palette);
1115 maps_to_texture.push_back(&maps_bmp_[i]);
1116 } catch (const std::bad_alloc& e) {
1117 std::cout << "Error allocating map " << i << ": " << e.what()
1118 << std::endl;
1119 continue;
1120 }
1121 }
1122 // Non-essential maps will be created on-demand when accessed
1123 }
1124 }
1125
1126 // Phase 3: Create textures only for currently visible maps
1127 // Only create textures for the first few maps initially
1128 const int initial_texture_count =
1129 std::min(4, static_cast<int>(maps_to_texture.size()));
1130 {
1131 gfx::ScopedTimer initial_textures_timer("CreateInitialTextures");
1132 for (int i = 0; i < initial_texture_count; ++i) {
1133 // Queue texture creation/update for initial maps via Arena's deferred
1134 // system
1136 gfx::Arena::TextureCommandType::CREATE, maps_to_texture[i]);
1137 }
1138 }
1139
1140 // Queue remaining maps for progressive loading via Arena
1141 // Priority based on current world (0 = current world, 11+ = other worlds)
1142 for (size_t i = initial_texture_count; i < maps_to_texture.size(); ++i) {
1143 // Determine priority based on which world this map belongs to
1144 int map_index = -1;
1145 for (int j = 0; j < zelda3::kNumOverworldMaps; ++j) {
1146 if (&maps_bmp_[j] == maps_to_texture[i]) {
1147 map_index = j;
1148 break;
1149 }
1150 }
1151
1152 int priority = 15; // Default low priority
1153 if (map_index >= 0) {
1154 int map_world = map_index / 0x40;
1155 priority = (map_world == current_world_)
1156 ? 5
1157 : 15; // Current world = priority 5, others = 15
1158 }
1159
1160 // Queue texture creation for remaining maps via Arena's deferred system
1161 // Note: Priority system to be implemented in future enhancement
1163 gfx::Arena::TextureCommandType::CREATE, maps_to_texture[i]);
1164 }
1165
1166 if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
1167 {
1168 gfx::ScopedTimer sprites_timer("LoadSpriteGraphics");
1170 }
1171 }
1172
1173 return absl::OkStatus();
1174}
1175
1177 // Render the sprites for each Overworld map
1178 const int depth = 0x10;
1179 for (int i = 0; i < 3; i++)
1180 for (auto const& sprite : *overworld_.mutable_sprites(i)) {
1181 int width = sprite.width();
1182 int height = sprite.height();
1183 if (width == 0 || height == 0) {
1184 continue;
1185 }
1186 if (sprite_previews_.size() < sprite.id()) {
1187 sprite_previews_.resize(sprite.id() + 1);
1188 }
1189 sprite_previews_[sprite.id()].Create(width, height, depth,
1190 *sprite.preview_graphics());
1191 sprite_previews_[sprite.id()].SetPalette(palette_);
1194 &sprite_previews_[sprite.id()]);
1195 }
1196 return absl::OkStatus();
1197}
1198
1200 // Process queued texture commands via Arena's deferred system
1201 if (renderer_) {
1203 }
1204
1205 // Also process deferred map refreshes for modified maps
1206 int refresh_count = 0;
1207 const int max_refreshes_per_frame = 2;
1208
1209 for (int i = 0;
1210 i < zelda3::kNumOverworldMaps && refresh_count < max_refreshes_per_frame;
1211 ++i) {
1212 if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) {
1213 // Check if this map is in current world (prioritize)
1214 bool is_current_world = (i / 0x40 == current_world_);
1215 bool is_current_map = (i == current_map_);
1216
1217 if (is_current_map || is_current_world) {
1219 refresh_count++;
1220 }
1221 }
1222 }
1223}
1224
1226 if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
1227 return;
1228 }
1229
1230 // Ensure the map is built first (on-demand loading)
1231 auto status = overworld_.EnsureMapBuilt(map_index);
1232 if (!status.ok()) {
1233 LOG_ERROR("OverworldEditor", "Failed to build map %d: %s", map_index,
1234 status.message());
1235 return;
1236 }
1237
1238 auto& bitmap = maps_bmp_[map_index];
1239
1240 // If bitmap doesn't exist yet (non-essential map), create it now
1241 if (!bitmap.is_active()) {
1242 overworld_.set_current_map(map_index);
1243 auto palette = overworld_.current_area_palette();
1244 try {
1245 bitmap.Create(kOverworldMapSize, kOverworldMapSize, 0x80,
1247 bitmap.SetPalette(palette);
1248 } catch (const std::bad_alloc& e) {
1249 LOG_ERROR("OverworldEditor", "Error allocating bitmap for map %d: %s",
1250 map_index, e.what());
1251 return;
1252 }
1253 }
1254
1255 if (!bitmap.texture() && bitmap.is_active()) {
1256 // Queue texture creation for this map
1259 }
1260}
1261
1264 ctx.rom = rom_;
1265 ctx.overworld = &overworld_;
1266 ctx.maps_bmp = &maps_bmp_;
1270 ctx.palette = &palette_;
1271 ctx.renderer = renderer_;
1276 ctx.game_state = &game_state_;
1278 ctx.status = &status_;
1279 ctx.ensure_map_texture = [this](int map_index) {
1280 this->EnsureMapTexture(map_index);
1281 };
1282 map_refresh_ = std::make_unique<MapRefreshCoordinator>(ctx);
1283}
1284
1286 if (canvas_nav_) canvas_nav_->HandleMapInteraction();
1287}
1288
1290 if (canvas_nav_) canvas_nav_->ScrollBlocksetCanvasToCurrentTile();
1291}
1292
1294 // Unregister palette listener
1295 if (palette_listener_id_ >= 0) {
1298 }
1299
1301 current_graphics_set_.clear();
1302 all_gfx_loaded_ = false;
1303 map_blockset_loaded_ = false;
1304 return absl::OkStatus();
1305}
1306
1307absl::Status OverworldEditor::ApplyZSCustomOverworldASM(int target_version) {
1308 // Feature flag deprecated - ROM version gating is sufficient
1309 // User explicitly clicked upgrade button, so respect their request
1310
1311 // Validate target version
1312 if (target_version < 2 || target_version > 3) {
1313 return absl::InvalidArgumentError(absl::StrFormat(
1314 "Invalid target version: %d. Must be 2 or 3.", target_version));
1315 }
1316
1317 // Check current ROM version
1318 uint8_t current_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
1319 if (current_version != 0xFF && current_version >= target_version) {
1320 return absl::AlreadyExistsError(absl::StrFormat(
1321 "ROM is already version %d or higher", current_version));
1322 }
1323
1324 LOG_DEBUG("OverworldEditor", "Applying ZSCustomOverworld ASM v%d to ROM...",
1325 target_version);
1326
1327 // Initialize Asar wrapper
1328 auto asar_wrapper = std::make_unique<core::AsarWrapper>();
1329 RETURN_IF_ERROR(asar_wrapper->Initialize());
1330
1331 // Create backup of ROM data
1332 std::vector<uint8_t> original_rom_data = rom_->vector();
1333 std::vector<uint8_t> working_rom_data = original_rom_data;
1334
1335 try {
1336 // Determine which ASM file to apply and use GetResourcePath for proper
1337 // resolution
1338 std::string asm_file_name =
1339 (target_version == 3) ? "asm/yaze.asm" // Master file with v3
1340 : "asm/ZSCustomOverworld.asm"; // v2 standalone
1341
1342 // Use GetResourcePath to handle app bundles and various deployment
1343 // scenarios
1344 std::string asm_file_path = util::GetResourcePath(asm_file_name);
1345
1346 LOG_DEBUG("OverworldEditor", "Using ASM file: %s", asm_file_path.c_str());
1347
1348 // Verify file exists
1349 if (!std::filesystem::exists(asm_file_path)) {
1350 return absl::NotFoundError(
1351 absl::StrFormat("ASM file not found at: %s\n\n"
1352 "Expected location: assets/%s\n"
1353 "Make sure the assets directory is accessible.",
1354 asm_file_path, asm_file_name));
1355 }
1356
1357 // Apply the ASM patch
1358 auto patch_result =
1359 asar_wrapper->ApplyPatch(asm_file_path, working_rom_data);
1360 if (!patch_result.ok()) {
1361 return absl::InternalError(absl::StrFormat(
1362 "Failed to apply ASM patch: %s", patch_result.status().message()));
1363 }
1364
1365 const auto& result = patch_result.value();
1366 if (!result.success) {
1367 std::string error_details = "ASM patch failed with errors:\n";
1368 for (const auto& error : result.errors) {
1369 error_details += " - " + error + "\n";
1370 }
1371 if (!result.warnings.empty()) {
1372 error_details += "Warnings:\n";
1373 for (const auto& warning : result.warnings) {
1374 error_details += " - " + warning + "\n";
1375 }
1376 }
1377 return absl::InternalError(error_details);
1378 }
1379
1380 // Update ROM with patched data
1381 RETURN_IF_ERROR(rom_->LoadFromData(working_rom_data));
1382
1383 // Update version marker and feature flags
1385
1386 // Log symbols found during patching
1387 LOG_DEBUG("OverworldEditor",
1388 "ASM patch applied successfully. Found %zu symbols:",
1389 result.symbols.size());
1390 for (const auto& symbol : result.symbols) {
1391 LOG_DEBUG("OverworldEditor", " %s @ $%06X", symbol.name.c_str(),
1392 symbol.address);
1393 }
1394
1395 // Refresh overworld data to reflect changes
1397
1398 LOG_DEBUG("OverworldEditor",
1399 "ZSCustomOverworld v%d successfully applied to ROM",
1400 target_version);
1401 return absl::OkStatus();
1402
1403 } catch (const std::exception& e) {
1404 // Restore original ROM data on any exception
1405 auto restore_result = rom_->LoadFromData(original_rom_data);
1406 if (!restore_result.ok()) {
1407 LOG_ERROR("OverworldEditor", "Failed to restore ROM data: %s",
1408 restore_result.message().data());
1409 }
1410 return absl::InternalError(
1411 absl::StrFormat("Exception during ASM application: %s", e.what()));
1412 }
1413}
1414
1415absl::Status OverworldEditor::UpdateROMVersionMarkers(int target_version) {
1416 // Set the main version marker
1418 static_cast<uint8_t>(target_version);
1419
1420 // Enable feature flags based on target version
1421 if (target_version >= 2) {
1422 // v2+ features
1425
1426 LOG_DEBUG("OverworldEditor",
1427 "Enabled v2+ features: Custom BG colors, Main palettes");
1428 }
1429
1430 if (target_version >= 3) {
1431 // v3 features
1436
1437 LOG_DEBUG(
1438 "OverworldEditor",
1439 "Enabled v3+ features: Subscreen overlays, Animated GFX, Tile GFX "
1440 "groups, Mosaic");
1441
1442 // Initialize area size data for v3 (set all areas to small by default)
1443 for (int i = 0; i < 0xA0; i++) {
1444 (*rom_)[zelda3::kOverworldScreenSize + i] =
1445 static_cast<uint8_t>(zelda3::AreaSizeEnum::SmallArea);
1446 }
1447
1448 // Set appropriate sizes for known large areas
1449 const std::vector<int> large_areas = {
1450 0x00, 0x02, 0x05, 0x07, 0x0A, 0x0B, 0x0F, 0x10, 0x11, 0x12,
1451 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D,
1452 0x1E, 0x25, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x30,
1453 0x32, 0x33, 0x34, 0x35, 0x37, 0x3A, 0x3B, 0x3C, 0x3F};
1454
1455 for (int area_id : large_areas) {
1456 if (area_id < 0xA0) {
1457 (*rom_)[zelda3::kOverworldScreenSize + area_id] =
1458 static_cast<uint8_t>(zelda3::AreaSizeEnum::LargeArea);
1459 }
1460 }
1461
1462 LOG_DEBUG("OverworldEditor", "Initialized area size data for %zu areas",
1463 large_areas.size());
1464 }
1465
1466 LOG_DEBUG("OverworldEditor", "ROM version markers updated to v%d",
1467 target_version);
1468 return absl::OkStatus();
1469}
1470
1472 if (canvas_nav_) canvas_nav_->UpdateBlocksetSelectorState();
1473}
1474
1476 current_tile16_ = std::max(0, current_tile16_ + delta);
1477}
1478
1479} // namespace yaze::editor
void set_dirty(bool dirty)
Definition rom.h:134
const auto & vector() const
Definition rom.h:143
absl::Status LoadFromData(const std::vector< uint8_t > &data, const LoadOptions &options=LoadOptions::Defaults())
Definition rom.cc:255
bool is_loaded() const
Definition rom.h:132
static Flags & get()
Definition features.h:118
bool loaded() const
Check if the manifest has been loaded.
UndoManager undo_manager_
Definition editor.h:307
EditorDependencies dependencies_
Definition editor.h:306
std::unique_ptr< UsageStatisticsCard > usage_stats_card_
absl::Status Clear() override
std::unique_ptr< MapPropertiesSystem > map_properties_system_
std::unique_ptr< OverworldCanvasRenderer > canvas_renderer_
zelda3::OverworldItem current_item_
void HandleEntityInteraction()
Handle entity interaction in MOUSE mode Includes: right-click context menus, double-click navigation,...
zelda3::OverworldEntranceTileTypes entrance_tiletypes_
zelda3::OverworldEntrance current_entrance_
void HandleTile16Edit()
Handle tile16 editing from context menu (MOUSE mode) Gets the tile16 under the cursor and opens the T...
absl::Status ApplyZSCustomOverworldASM(int target_version)
Apply ZSCustomOverworld ASM patch to upgrade ROM version.
std::optional< OverworldUndoPoint > current_paint_operation_
absl::Status CheckForCurrentMap()
Check for map changes and refresh if needed.
void CreateUndoPoint(int map_id, int world, int x, int y, int old_tile_id)
void ForceRefreshGraphics(int map_index)
std::vector< int > selected_tile16_ids_
void ProcessPendingEntityInsertion()
Process any pending entity insertion request Called from Update() - needed because ImGui::OpenPopup()...
Definition automation.cc:88
void InitCanvasNavigationManager()
Initialize the canvas navigation manager (called during Initialize)
void InitMapRefreshCoordinator()
Initialize the map refresh coordinator (called during Initialize)
absl::Status Undo() override
void HandleEntityInsertion(const std::string &entity_type)
Handle entity insertion from context menu.
Definition automation.cc:75
std::unique_ptr< MapRefreshCoordinator > map_refresh_
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > maps_bmp_
void CheckForOverworldEdits()
Check for tile edits - delegates to TilePaintingManager.
absl::Status UpdateROMVersionMarkers(int target_version)
Update ROM version markers and feature flags after ASM patching.
zelda3::OverworldExit current_exit_
void RefreshSiblingMapGraphics(int map_index, bool include_self=false)
void RefreshOverworldMapOnDemand(int map_index)
std::unique_ptr< OverworldSidebar > sidebar_
std::chrono::steady_clock::time_point last_paint_time_
void InvalidateGraphicsCache(int map_id=-1)
Invalidate cached graphics for a specific map or all maps.
void HandleEntityDoubleClick(zelda3::GameEntity *hovered_entity)
Handle double-click actions on entities (e.g., jump to room)
void HandleEntityContextMenus(zelda3::GameEntity *hovered_entity)
Handle right-click context menus for entities.
absl::Status SaveCurrentSelectionToScratch()
zelda3::Overworld & overworld()
Access the underlying Overworld data.
std::unique_ptr< OverworldEntityRenderer > entity_renderer_
absl::Status Load() override
void EnsureMapTexture(int map_index)
Ensure a specific map has its texture created.
std::vector< gfx::Bitmap > sprite_previews_
std::unique_ptr< CanvasNavigationManager > canvas_nav_
absl::Status Copy() override
std::unique_ptr< OverworldToolbar > toolbar_
void ProcessDeferredTextures()
Create textures for deferred map bitmaps on demand.
std::unique_ptr< gui::TileSelectorWidget > blockset_selector_
void DrawEntityEditorPopups()
Draw entity editor popups and update entity data.
absl::Status Paste() override
void ScrollBlocksetCanvasToCurrentTile()
Scroll the blockset canvas to show the current selected tile16.
static constexpr auto kPaintBatchTimeout
std::unique_ptr< TilePaintingManager > tile_painting_
absl::Status LoadGraphics()
Load the Bitmap objects for each OverworldMap.
absl::Status Save() override
std::unique_ptr< DebugWindowCard > debug_window_card_
zelda3::GameEntity * current_entity_
void HandleKeyboardShortcuts()
Handle keyboard shortcuts for the Overworld Editor Shortcuts: 1-2 (modes), 3-8 (entities),...
void InitTilePaintingManager()
Initialize the tile painting manager (called after graphics load)
bool TogglePanel(size_t session_id, const std::string &base_card_id)
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
bool has_pending_changes() const
Check if any tiles have uncommitted changes.
void set_palette(const gfx::SnesPalette &palette)
void set_on_changes_committed(std::function< absl::Status()> callback)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
int RegisterPaletteListener(PaletteChangeCallback callback)
Register a callback for palette change notifications.
Definition arena.cc:441
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:116
void UnregisterPaletteListener(int listener_id)
Unregister a palette change listener.
Definition arena.cc:448
static Arena & Get()
Definition arena.cc:21
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:201
bool is_active() const
Definition bitmap.h:384
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
SDL_Surface * surface() const
Definition bitmap.h:379
RAII timer for automatic timing management.
auto selected_tile_pos() const
Definition canvas.h:489
auto global_scale() const
Definition canvas.h:491
auto select_rect_active() const
Definition canvas.h:487
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:280
auto drawn_tile_position() const
Definition canvas.h:450
const ImVector< ImVec2 > & points() const
Definition canvas.h:439
auto selected_points() const
Definition canvas.h:553
Base class for all overworld and dungeon entities.
Definition common.h:31
enum yaze::zelda3::GameEntity::EntityType entity_type_
Represents an overworld exit that transitions from dungeon to overworld.
auto tile16_blockset_data() const
Definition overworld.h:575
auto current_area_palette() const
Definition overworld.h:567
void set_current_world(int world)
Definition overworld.h:590
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:36
absl::Status SaveMapProperties()
Save per-area graphics, palettes, and messages.
std::vector< std::pair< uint32_t, uint32_t > > GetProjectedWriteRanges() const
Get the projected write ranges (PC offsets) for overworld map saves.
absl::Status SaveMap32Tiles()
Save tile32 definitions to ROM.
absl::Status SaveMap16Tiles()
Save tile16 definitions to ROM.
auto is_loaded() const
Definition overworld.h:583
auto current_graphics() const
Definition overworld.h:553
absl::Status CreateTile32Tilemap()
Build tile32 tilemap from current tile16 data.
void set_current_map(int i)
Definition overworld.h:589
absl::Status SaveEntrances()
Save entrance warp points to ROM.
absl::Status SaveExits()
Save exit return points to ROM.
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
Definition overworld.cc:888
absl::Status SaveItems()
Save hidden overworld items to ROM.
uint16_t GetTile(int x, int y) const
Definition overworld.h:591
absl::Status SaveOverworldMaps()
Save compressed map tile data to ROM.
auto mutable_sprites(int state)
Definition overworld.h:549
auto current_map_bitmap_data() const
Definition overworld.h:571
absl::Status SaveMusic()
Save per-area music IDs.
A class for managing sprites in the overworld and underworld.
Definition sprite.h:35
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:830
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_LAYERS
Definition icons.h:1068
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define LOG_WARN(category, format,...)
Definition log.h:107
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
std::string AddressOwnershipToString(AddressOwnership ownership)
const AgentUITheme & GetTheme()
Editors are the view controllers for the application.
constexpr unsigned int kOverworldMapSize
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
Definition entity.cc:513
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
Definition entity.cc:111
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
Definition entity.cc:383
constexpr int kTile16Size
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
Definition entity.cc:196
Tilemap CreateTilemap(IRenderer *renderer, std::vector< uint8_t > &data, int width, int height, int tile_size, int num_tiles, SnesPalette &palette)
Definition tilemap.cc:14
constexpr const char * kOverworld
Definition popup_id.h:53
void CenterText(const char *text)
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
Definition popup_id.h:23
std::string GetResourcePath(const std::string &resource_path)
Definition file_util.cc:70
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldScreenSize
Definition overworld.h:144
constexpr int kNumTile16Individual
Definition overworld.h:239
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
Definition common.h:85
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:89
constexpr int kDarkWorldMapIdStart
absl::StatusOr< OverworldEntranceTileTypes > LoadEntranceTileTypes(Rom *rom)
constexpr int OverworldCustomMosaicEnabled
constexpr int OverworldCustomSubscreenOverlayEnabled
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
struct yaze::core::FeatureFlags::Flags::Overworld overworld
Callbacks for operations that remain in the OverworldEditor.
std::function< absl::Status()> refresh_tile16_blockset
std::function< bool()> is_entity_hovered
Returns true if an entity is currently hovered (for pan suppression).
Shared state pointers that the navigation manager reads/writes.
std::unique_ptr< gui::TileSelectorWidget > * blockset_selector
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
project::YazeProject * project
Definition editor.h:167
SharedClipboard * shared_clipboard
Definition editor.h:178
gfx::IRenderer * renderer
Definition editor.h:183
Context struct holding all data dependencies for map refresh operations. All pointers/references must...
std::function< void(int map_index)> ensure_map_texture
Callback to ensure a map texture is created (stays in OverworldEditor)
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
static constexpr const char * kTile16Editor
std::vector< int > overworld_tile16_ids
Definition editor.h:117
Callbacks for undo integration and map refresh.
std::function< void(int map_index)> refresh_overworld_map_on_demand
std::function< void()> scroll_blockset_to_current_tile
std::function< void()> finalize_paint_operation
std::function< void(int map_id, int world, int x, int y, int old_tile_id)> create_undo_point
Shared state for the tile painting system.
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
RomWritePolicy write_policy
Definition project.h:108
core::HackManifest hack_manifest
Definition project.h:160