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#include "overworld_editor.h"
2
3#ifndef IM_PI
4#define IM_PI 3.14159265358979323846f
5#endif
6
7#include <algorithm>
8#include <cmath>
9#include <cstddef>
10#include <cstdint>
11#include <exception>
12#include <filesystem>
13#include <iostream>
14#include <memory>
15#include <new>
16#include <ostream>
17#include <set>
18#include <string>
19#include <unordered_map>
20#include <utility>
21#include <vector>
22
23#include "absl/status/status.h"
24#include "absl/strings/str_format.h"
30#include "app/gfx/core/bitmap.h"
38#include "app/gui/core/icons.h"
39#include "app/gui/core/style.h"
42#include "app/rom.h"
44#include "core/asar_wrapper.h"
45#include "core/features.h"
47#include "imgui/imgui.h"
48#include "imgui_memory_editor.h"
49#include "util/file_util.h"
50#include "util/hex.h"
51#include "util/log.h"
52#include "util/macro.h"
53#include "zelda3/common.h"
60
61namespace yaze::editor {
62
64 // Register cards with EditorCardRegistry (dependency injection)
66 return;
67 }
68 auto* card_registry = dependencies_.card_registry;
69
70 // Register Overworld Canvas (main canvas card with toolset)
71 card_registry->RegisterCard({
72 .card_id = MakeCardId("overworld.canvas"),
73 .display_name = "Overworld Canvas",
74 .icon = ICON_MD_MAP,
75 .category = "Overworld",
76 .shortcut_hint = "Ctrl+Shift+O",
77 .visibility_flag = &show_overworld_canvas_,
78 .priority = 5 // Show first, most important
79 });
80
81 card_registry->RegisterCard(
82 {.card_id = MakeCardId("overworld.tile16_selector"),
83 .display_name = "Tile16 Selector",
84 .icon = ICON_MD_GRID_ON,
85 .category = "Overworld",
86 .shortcut_hint = "Ctrl+Alt+1",
87 .visibility_flag = &show_tile16_selector_,
88 .priority = 10});
89
90 card_registry->RegisterCard(
91 {.card_id = MakeCardId("overworld.tile8_selector"),
92 .display_name = "Tile8 Selector",
93 .icon = ICON_MD_GRID_3X3,
94 .category = "Overworld",
95 .shortcut_hint = "Ctrl+Alt+2",
96 .visibility_flag = &show_tile8_selector_,
97 .priority = 20});
98
99 card_registry->RegisterCard({.card_id = MakeCardId("overworld.area_graphics"),
100 .display_name = "Area Graphics",
101 .icon = ICON_MD_IMAGE,
102 .category = "Overworld",
103 .shortcut_hint = "Ctrl+Alt+3",
104 .visibility_flag = &show_area_gfx_,
105 .priority = 30});
106
107 card_registry->RegisterCard({.card_id = MakeCardId("overworld.scratch"),
108 .display_name = "Scratch Workspace",
109 .icon = ICON_MD_DRAW,
110 .category = "Overworld",
111 .shortcut_hint = "Ctrl+Alt+4",
112 .visibility_flag = &show_scratch_,
113 .priority = 40});
114
115 card_registry->RegisterCard({.card_id = MakeCardId("overworld.gfx_groups"),
116 .display_name = "GFX Groups",
117 .icon = ICON_MD_FOLDER,
118 .category = "Overworld",
119 .shortcut_hint = "Ctrl+Alt+5",
120 .visibility_flag = &show_gfx_groups_,
121 .priority = 50});
122
123 card_registry->RegisterCard({.card_id = MakeCardId("overworld.usage_stats"),
124 .display_name = "Usage Statistics",
125 .icon = ICON_MD_ANALYTICS,
126 .category = "Overworld",
127 .shortcut_hint = "Ctrl+Alt+6",
128 .visibility_flag = &show_usage_stats_,
129 .priority = 60});
130
131 card_registry->RegisterCard({.card_id = MakeCardId("overworld.v3_settings"),
132 .display_name = "v3 Settings",
133 .icon = ICON_MD_SETTINGS,
134 .category = "Overworld",
135 .shortcut_hint = "Ctrl+Alt+7",
136 .visibility_flag = &show_v3_settings_,
137 .priority = 70});
138
139 // Original initialization code below:
140 // Initialize MapPropertiesSystem with canvas and bitmap data
141 map_properties_system_ = std::make_unique<MapPropertiesSystem>(
143
144 // Set up refresh callbacks for MapPropertiesSystem
145 map_properties_system_->SetRefreshCallbacks(
146 [this]() { this->RefreshMapProperties(); },
147 [this]() { this->RefreshOverworldMap(); },
148 [this]() -> absl::Status { return this->RefreshMapPalette(); },
149 [this]() -> absl::Status { return this->RefreshTile16Blockset(); },
150 [this](int map_index) { this->ForceRefreshGraphics(map_index); });
151
152 // Initialize OverworldEntityRenderer for entity visualization
153 entity_renderer_ = std::make_unique<OverworldEntityRenderer>(
155
156 // Setup Canvas Automation API callbacks (Phase 4)
158
159}
160
161absl::Status OverworldEditor::Load() {
162 gfx::ScopedTimer timer("OverworldEditor::Load");
163
164 LOG_DEBUG("OverworldEditor", "Loading overworld.");
165 if (!rom_ || !rom_->is_loaded()) {
166 return absl::FailedPreconditionError("ROM not loaded");
167 }
168
173
174 // CRITICAL FIX: Initialize tile16 editor with the correct overworld palette
177
178 // Set up callback for when tile16 changes are committed
179 tile16_editor_.set_on_changes_committed([this]() -> absl::Status {
180 // Regenerate the overworld editor's tile16 blockset
182
183 // Force refresh of the current overworld map to show changes
185
186 LOG_DEBUG("OverworldEditor",
187 "Overworld editor refreshed after Tile16 changes");
188 return absl::OkStatus();
189 });
190
191 // Set up entity insertion callback for MapPropertiesSystem
193 map_properties_system_->SetEntityCallbacks(
194 [this](const std::string& entity_type) {
195 HandleEntityInsertion(entity_type);
196 });
197 }
198
200 all_gfx_loaded_ = true;
201 return absl::OkStatus();
202}
203
205 status_ = absl::OkStatus();
206
207 // Process deferred textures for smooth loading
209
212 return status_;
213 }
214
215 // Create session-aware cards (non-static for multi-session support)
216 gui::EditorCard overworld_canvas_card(
217 MakeCardTitle("Overworld Canvas").c_str(), ICON_MD_PUBLIC);
218 gui::EditorCard tile16_card(MakeCardTitle("Tile16 Selector").c_str(),
220 gui::EditorCard tile8_card(MakeCardTitle("Tile8 Selector").c_str(),
222 gui::EditorCard area_gfx_card(MakeCardTitle("Area Graphics").c_str(),
224 gui::EditorCard scratch_card(MakeCardTitle("Scratch Space").c_str(),
226 gui::EditorCard tile16_editor_card(MakeCardTitle("Tile16 Editor").c_str(),
228 gui::EditorCard gfx_groups_card(MakeCardTitle("Graphics Groups").c_str(),
230 gui::EditorCard usage_stats_card(MakeCardTitle("Usage Statistics").c_str(),
232 gui::EditorCard v3_settings_card(MakeCardTitle("v3 Settings").c_str(),
234
235 // Configure card positions (these settings persist via imgui.ini)
236 static bool cards_configured = false;
237 if (!cards_configured) {
238 // Position cards for optimal workflow
239 overworld_canvas_card.SetDefaultSize(1000, 700);
241
242 tile16_card.SetDefaultSize(300, 600);
244
245 tile8_card.SetDefaultSize(280, 500);
247
248 area_gfx_card.SetDefaultSize(300, 400);
250
251 scratch_card.SetDefaultSize(350, 500);
253
254 tile16_editor_card.SetDefaultSize(800, 600);
256
257 gfx_groups_card.SetDefaultSize(700, 550);
259
260 usage_stats_card.SetDefaultSize(600, 500);
262
263 v3_settings_card.SetDefaultSize(500, 600);
265
266 cards_configured = true;
267 }
268
269 // Main canvas (full width when cards are docked)
271 if (overworld_canvas_card.Begin(&show_overworld_canvas_)) {
272 DrawToolset();
274 }
275 overworld_canvas_card.End(); // ALWAYS call End after Begin
276 }
277
278 // Floating tile selector cards (4 tabs converted to separate cards)
280 if (tile16_card.Begin(&show_tile16_selector_)) {
282 }
283 tile16_card.End(); // ALWAYS call End after Begin
284 }
285
287 if (tile8_card.Begin(&show_tile8_selector_)) {
289 gui::BeginChildWithScrollbar("##Tile8SelectorScrollRegion");
291 ImGui::EndChild();
293 }
294 tile8_card.End(); // ALWAYS call End after Begin
295 }
296
297 if (show_area_gfx_) {
298 if (area_gfx_card.Begin(&show_area_gfx_)) {
300 }
301 area_gfx_card.End(); // ALWAYS call End after Begin
302 }
303
304 if (show_scratch_) {
305 if (scratch_card.Begin(&show_scratch_)) {
307 }
308 scratch_card.End(); // ALWAYS call End after Begin
309 }
310
311 // Tile16 Editor popup-only (no tab)
313 if (tile16_editor_card.Begin(&show_tile16_editor_)) {
314 if (rom_->is_loaded()) {
316 } else {
317 gui::CenterText("No ROM loaded");
318 }
319 }
320 tile16_editor_card.End(); // ALWAYS call End after Begin
321 }
322
323 // Graphics Groups popup
324 if (show_gfx_groups_) {
325 if (gfx_groups_card.Begin(&show_gfx_groups_)) {
326 if (rom_->is_loaded()) {
328 } else {
329 gui::CenterText("No ROM loaded");
330 }
331 }
332 gfx_groups_card.End(); // ALWAYS call End after Begin
333 }
334
335 // Usage Statistics popup
336 if (show_usage_stats_) {
337 if (usage_stats_card.Begin(&show_usage_stats_)) {
338 if (rom_->is_loaded()) {
340 } else {
341 gui::CenterText("No ROM loaded");
342 }
343 }
344 usage_stats_card.End(); // ALWAYS call End after Begin
345 }
346
347 // Area Configuration Panel (detailed editing)
349 ImGui::SetNextWindowSize(ImVec2(650, 750), ImGuiCond_FirstUseEver);
350 if (ImGui::Begin(ICON_MD_TUNE " Area Configuration###AreaConfig",
352 if (rom_->is_loaded() && overworld_.is_loaded() &&
354 map_properties_system_->DrawMapPropertiesPanel(
356 }
357 }
358 ImGui::End();
359 }
360
361 // Custom Background Color Editor
363 ImGui::SetNextWindowSize(ImVec2(400, 500), ImGuiCond_FirstUseEver);
364 if (ImGui::Begin(ICON_MD_FORMAT_COLOR_FILL " Background Color",
366 if (rom_->is_loaded() && overworld_.is_loaded() &&
368 map_properties_system_->DrawCustomBackgroundColorEditor(
370 }
371 }
372 ImGui::End();
373 }
374
375 // Visual Effects Editor (Subscreen Overlays)
377 ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_FirstUseEver);
378 if (ImGui::Begin(ICON_MD_LAYERS " Visual Effects Editor###OverlayEditor",
380 if (rom_->is_loaded() && overworld_.is_loaded() &&
382 map_properties_system_->DrawOverlayEditor(current_map_,
384 }
385 }
386 ImGui::End();
387 }
388
389 // --- BEGIN CENTRALIZED INTERACTION LOGIC ---
390 auto* hovered_entity = entity_renderer_->hovered_entity();
391
392 // Handle all MOUSE mode interactions here
394 // --- CONTEXT MENUS & POPOVERS ---
395 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
396 if (hovered_entity) {
397 current_entity_ = hovered_entity;
398 switch (hovered_entity->entity_type_) {
401 *static_cast<zelda3::OverworldExit*>(hovered_entity);
402 ImGui::OpenPopup("Exit editor");
403 break;
406 *static_cast<zelda3::OverworldEntrance*>(hovered_entity);
407 ImGui::OpenPopup("Entrance Editor");
408 break;
411 *static_cast<zelda3::OverworldItem*>(hovered_entity);
412 ImGui::OpenPopup("Item editor");
413 break;
415 current_sprite_ = *static_cast<zelda3::Sprite*>(hovered_entity);
416 ImGui::OpenPopup("Sprite editor");
417 break;
418 default:
419 break;
420 }
421 }
422 }
423
424 // --- DOUBLE-CLICK ACTIONS ---
425 if (hovered_entity && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
426 if (hovered_entity->entity_type_ ==
429 static_cast<zelda3::OverworldExit*>(hovered_entity)->room_id_;
430 } else if (hovered_entity->entity_type_ ==
432 jump_to_tab_ = static_cast<zelda3::OverworldEntrance*>(hovered_entity)
433 ->entrance_id_;
434 }
435 }
436 }
437
438 // --- DRAW POPUPS ---
443 rom_->set_dirty(true);
444 }
445 }
451 rom_->set_dirty(true);
452 }
453 }
458 rom_->set_dirty(true);
459 }
460 }
465 rom_->set_dirty(true);
466 }
467 }
468 // --- END CENTRALIZED LOGIC ---
469
470 return status_;
471}
472
474 static bool use_work_area = true;
475 static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
476 ImGuiWindowFlags_NoMove |
477 ImGuiWindowFlags_NoSavedSettings;
478 const ImGuiViewport* viewport = ImGui::GetMainViewport();
479 ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos);
480 ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size);
481 if (ImGui::Begin("Fullscreen Overworld Editor", &overworld_canvas_fullscreen_,
482 flags)) {
483 // Draws the toolset for editing the Overworld.
484 DrawToolset();
486 }
487 ImGui::End();
488}
489
491 // Modern adaptive toolbar with inline mode switching and properties
492 static gui::Toolset toolbar;
493
494 // IMPORTANT: Don't make asm_version static - it needs to update after ROM upgrade
495 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
496
497 // Don't use WidgetIdScope here - it conflicts with ImGui::Begin/End ID stack in cards
498 // Widgets register themselves individually instead
499
500 toolbar.Begin();
501
502 // Mode buttons - simplified to 2 modes
503 toolbar.BeginModeGroup();
504
505 if (toolbar.ModeButton(
507 "Mouse Mode (1)\nNavigate, pan, and manage entities")) {
511 }
512 }
513
515 "Tile Paint Mode (2)\nDraw tiles on the map")) {
519 }
520 }
521
522 toolbar.EndModeGroup();
523
524 // Entity editing indicator (shows current entity mode if active)
526 toolbar.AddSeparator();
527 const char* entity_label = "";
528 const char* entity_icon = "";
529 switch (entity_edit_mode_) {
531 entity_icon = ICON_MD_DOOR_FRONT;
532 entity_label = "Entrances";
533 break;
535 entity_icon = ICON_MD_DOOR_BACK;
536 entity_label = "Exits";
537 break;
539 entity_icon = ICON_MD_GRASS;
540 entity_label = "Items";
541 break;
543 entity_icon = ICON_MD_PEST_CONTROL_RODENT;
544 entity_label = "Sprites";
545 break;
547 entity_icon = ICON_MD_ADD_LOCATION;
548 entity_label = "Transports";
549 break;
551 entity_icon = ICON_MD_MUSIC_NOTE;
552 entity_label = "Music";
553 break;
554 default:
555 break;
556 }
557 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s Editing: %s",
558 entity_icon, entity_label);
559 }
560
561 // ROM version badge (already read above)
562 toolbar.AddRomBadge(asm_version,
563 [this]() { ImGui::OpenPopup("UpgradeROMVersion"); });
564
565 // Inline map properties with icon labels - use toolbar methods for consistency
566 if (toolbar.AddProperty(ICON_MD_IMAGE, " Gfx",
568 ->mutable_area_graphics(),
569 [this]() {
570 // CORRECT ORDER: Properties first, then graphics reload
571
572 // 1. Propagate properties to siblings FIRST (this also calls LoadAreaGraphics on siblings)
573 RefreshMapProperties();
574
575 // 2. Force immediate refresh of current map and all siblings
576 maps_bmp_[current_map_].set_modified(true);
577 RefreshChildMapOnDemand(current_map_);
578 RefreshSiblingMapGraphics(current_map_);
579
580 // 3. Update tile selector
581 RefreshTile16Blockset();
582 })) {
583 // Property changed
584 }
585
586 if (toolbar.AddProperty(ICON_MD_PALETTE, " Pal",
588 ->mutable_area_palette(),
589 [this]() {
590 // Palette changes also need to propagate to siblings
591 RefreshSiblingMapGraphics(current_map_);
592 RefreshMapProperties();
593 status_ = RefreshMapPalette();
594 RefreshOverworldMap();
595 })) {
596 // Property changed
597 }
598
599 toolbar.AddSeparator();
600
601 // Quick actions
602 if (toolbar.AddAction(ICON_MD_ZOOM_OUT, "Zoom Out")) {
604 }
605
606 if (toolbar.AddAction(ICON_MD_ZOOM_IN, "Zoom In")) {
608 }
609
611 "Fullscreen (F11)")) {
612 // Toggled by helper
613 }
614
615 toolbar.AddSeparator();
616
617 // Card visibility toggles (with automation-friendly paths)
618 if (toolbar.AddAction(ICON_MD_GRID_3X3, "Toggle Tile16 Selector")) {
620 }
621
622 if (toolbar.AddAction(ICON_MD_GRID_4X4, "Toggle Tile8 Selector")) {
624 }
625
626 if (toolbar.AddAction(ICON_MD_IMAGE, "Toggle Area Graphics")) {
628 }
629
630 if (toolbar.AddAction(ICON_MD_BRUSH, "Toggle Scratch Space")) {
632 }
633
634 toolbar.AddSeparator();
635
636 if (toolbar.AddAction(ICON_MD_GRID_VIEW, "Open Tile16 Editor")) {
638 }
639
640 if (toolbar.AddAction(ICON_MD_COLLECTIONS, "Open Graphics Groups")) {
642 }
643
644 if (toolbar.AddUsageStatsButton("Open Usage Statistics")) {
646 }
647
648 if (toolbar.AddAction(ICON_MD_TUNE, "Open Area Configuration")) {
650 }
651
652 toolbar.End();
653
654 // ROM Upgrade Popup (rendered outside toolbar to avoid ID conflicts)
655 if (ImGui::BeginPopupModal("UpgradeROMVersion", nullptr,
656 ImGuiWindowFlags_AlwaysAutoResize)) {
657 ImGui::Text(ICON_MD_UPGRADE " Upgrade ROM to ZSCustomOverworld");
658 ImGui::Separator();
659 ImGui::TextWrapped(
660 "This will apply the ZSCustomOverworld ASM patch to your ROM,\n"
661 "enabling advanced features like custom tile graphics, animated GFX,\n"
662 "wide/tall areas, and more.");
663 ImGui::Separator();
664
665 uint8_t current_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
666 ImGui::Text("Current Version: %s",
667 current_version == 0xFF
668 ? "Vanilla"
669 : absl::StrFormat("v%d", current_version).c_str());
670
671 static int target_version = 3;
672 ImGui::RadioButton("v2 (Basic features)", &target_version, 2);
673 ImGui::SameLine();
674 ImGui::RadioButton("v3 (All features)", &target_version, 3);
675
676 ImGui::Separator();
677
678 if (ImGui::Button(ICON_MD_CHECK " Apply Upgrade", ImVec2(150, 0))) {
679 auto status = ApplyZSCustomOverworldASM(target_version);
680 if (status.ok()) {
681 // CRITICAL: Reload the editor to reflect changes
682 status_ = Clear();
683 status_ = Load();
684 ImGui::CloseCurrentPopup();
685 } else {
686 LOG_ERROR("OverworldEditor", "Upgrade failed: %s",
687 status.message().data());
688 }
689 }
690 ImGui::SameLine();
691 if (ImGui::Button(ICON_MD_CANCEL " Cancel", ImVec2(150, 0))) {
692 ImGui::CloseCurrentPopup();
693 }
694
695 ImGui::EndPopup();
696 }
697
698 // All editor windows are now rendered in Update() using either EditorCard system
699 // or MapPropertiesSystem for map-specific panels. This keeps the toolset clean
700 // and prevents ImGui ID stack issues.
701
702 // Legacy window code removed - windows rendered in Update() include:
703 // - Graphics Groups (EditorCard)
704 // - Area Configuration (MapPropertiesSystem)
705 // - Background Color Editor (MapPropertiesSystem)
706 // - Visual Effects Editor (MapPropertiesSystem)
707 // - Tile16 Editor, Usage Stats, etc. (EditorCards)
708
709 // Keyboard shortcuts for the Overworld Editor
710 if (!ImGui::IsAnyItemActive()) {
711 using enum EditingMode;
712
713 EditingMode old_mode = current_mode;
714
715 // Tool shortcuts (simplified)
716 if (ImGui::IsKeyDown(ImGuiKey_1)) {
718 } else if (ImGui::IsKeyDown(ImGuiKey_2)) {
720 }
721
722 // Update canvas usage mode when mode changes
723 if (old_mode != current_mode) {
726 } else if (current_mode == EditingMode::DRAW_TILE) {
728 }
729 }
730
731 // Entity editing shortcuts (3-8)
732 if (ImGui::IsKeyDown(ImGuiKey_3)) {
736 } else if (ImGui::IsKeyDown(ImGuiKey_4)) {
740 } else if (ImGui::IsKeyDown(ImGuiKey_5)) {
744 } else if (ImGui::IsKeyDown(ImGuiKey_6)) {
748 } else if (ImGui::IsKeyDown(ImGuiKey_7)) {
752 } else if (ImGui::IsKeyDown(ImGuiKey_8)) {
756 }
757
758 // View shortcuts
759 if (ImGui::IsKeyDown(ImGuiKey_F11)) {
761 }
762
763 // Toggle map lock with L key
764 if (ImGui::IsKeyDown(ImGuiKey_L) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
766 }
767
768 // Toggle Tile16 editor with T key
769 if (ImGui::IsKeyDown(ImGuiKey_T) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
771 }
772 }
773}
774
776 int xx = 0;
777 int yy = 0;
778 for (int i = 0; i < 0x40; i++) {
779 int world_index = i + (current_world_ * 0x40);
780
781 // Bounds checking to prevent crashes
782 if (world_index < 0 || world_index >= static_cast<int>(maps_bmp_.size())) {
783 continue; // Skip invalid map index
784 }
785
786 // Don't apply scale to coordinates - scale is applied to canvas rendering
787 int map_x = xx * kOverworldMapSize;
788 int map_y = yy * kOverworldMapSize;
789
790 // Check if the map has a texture, if not, ensure it gets loaded
791 if (!maps_bmp_[world_index].texture() &&
792 maps_bmp_[world_index].is_active()) {
793 EnsureMapTexture(world_index);
794 }
795
796 // Only draw if the map has a texture or is the currently selected map
797 if (maps_bmp_[world_index].texture() || world_index == current_map_) {
798 // Draw without applying scale here - canvas handles zoom uniformly
799 ow_map_canvas_.DrawBitmap(maps_bmp_[world_index], map_x, map_y, 1.0f);
800 } else {
801 // Draw a placeholder for maps that haven't loaded yet
802 ImDrawList* draw_list = ImGui::GetWindowDrawList();
803 ImVec2 canvas_pos = ow_map_canvas_.zero_point();
804 ImVec2 placeholder_pos =
805 ImVec2(canvas_pos.x + map_x, canvas_pos.y + map_y);
806 ImVec2 placeholder_size = ImVec2(kOverworldMapSize, kOverworldMapSize);
807
808 // Modern loading indicator with theme colors
809 draw_list->AddRectFilled(
810 placeholder_pos,
811 ImVec2(placeholder_pos.x + placeholder_size.x,
812 placeholder_pos.y + placeholder_size.y),
813 IM_COL32(32, 32, 32, 128)); // Dark gray with transparency
814
815 // Animated loading spinner
816 ImVec2 spinner_pos = ImVec2(placeholder_pos.x + placeholder_size.x / 2,
817 placeholder_pos.y + placeholder_size.y / 2);
818
819 const float spinner_radius = 8.0f;
820 const float rotation = static_cast<float>(ImGui::GetTime()) * 3.0f;
821 const float start_angle = rotation;
822 const float end_angle = rotation + IM_PI * 1.5f;
823
824 draw_list->PathArcTo(spinner_pos, spinner_radius, start_angle, end_angle,
825 12);
826 draw_list->PathStroke(IM_COL32(100, 180, 100, 255), 0, 2.5f);
827 }
828
829 xx++;
830 if (xx >= 8) {
831 yy++;
832 xx = 0;
833 }
834 }
835}
836
838 // Determine which overworld map the user is currently editing.
839 auto mouse_position = ow_map_canvas_.drawn_tile_position();
840
841 int map_x = mouse_position.x / kOverworldMapSize;
842 int map_y = mouse_position.y / kOverworldMapSize;
843 current_map_ = map_x + map_y * 8;
844 if (current_world_ == 1) {
845 current_map_ += 0x40;
846 } else if (current_world_ == 2) {
847 current_map_ += 0x80;
848 }
849
850 // Bounds checking to prevent crashes
851 if (current_map_ < 0 || current_map_ >= static_cast<int>(maps_bmp_.size())) {
852 return; // Invalid map index, skip drawing
853 }
854
855 // Validate tile16_blockset_ before calling GetTilemapData
857 tile16_blockset_.atlas.vector().empty()) {
858 LOG_ERROR(
859 "OverworldEditor",
860 "Error: tile16_blockset_ is not properly initialized (active: %s, "
861 "size: %zu)",
862 tile16_blockset_.atlas.is_active() ? "true" : "false",
863 tile16_blockset_.atlas.vector().size());
864 return; // Skip drawing if blockset is invalid
865 }
866
867 // Render the updated map bitmap.
869 RenderUpdatedMapBitmap(mouse_position, tile_data);
870
871 // Calculate the correct superX and superY values
872 int superY = current_map_ / 8;
873 int superX = current_map_ % 8;
874 int mouse_x = mouse_position.x;
875 int mouse_y = mouse_position.y;
876 // Calculate the correct tile16_x and tile16_y positions
877 int tile16_x = (mouse_x % kOverworldMapSize) / (kOverworldMapSize / 32);
878 int tile16_y = (mouse_y % kOverworldMapSize) / (kOverworldMapSize / 32);
879
880 // Update the overworld_.map_tiles() based on tile16 ID and current world
881 auto& selected_world =
882 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
883 : (current_world_ == 1) ? overworld_.mutable_map_tiles()->dark_world
884 : overworld_.mutable_map_tiles()->special_world;
885
886 int index_x = superX * 32 + tile16_x;
887 int index_y = superY * 32 + tile16_y;
888
889 selected_world[index_x][index_y] = current_tile16_;
890}
891
893 const ImVec2& click_position, const std::vector<uint8_t>& tile_data) {
894
895 // Bounds checking to prevent crashes
896 if (current_map_ < 0 || current_map_ >= static_cast<int>(maps_bmp_.size())) {
897 LOG_ERROR("OverworldEditor",
898 "ERROR: RenderUpdatedMapBitmap - Invalid current_map_ %d "
899 "(maps_bmp_.size()=%zu)",
900 current_map_, maps_bmp_.size());
901 return; // Invalid map index, skip rendering
902 }
903
904 // Calculate the tile index for x and y based on the click_position
905 int tile_index_x =
906 (static_cast<int>(click_position.x) % kOverworldMapSize) / kTile16Size;
907 int tile_index_y =
908 (static_cast<int>(click_position.y) % kOverworldMapSize) / kTile16Size;
909
910 // Calculate the pixel start position based on tile index and tile size
911 ImVec2 start_position;
912 start_position.x = static_cast<float>(tile_index_x * kTile16Size);
913 start_position.y = static_cast<float>(tile_index_y * kTile16Size);
914
915 // Update the bitmap's pixel data based on the start_position and tile_data
916 gfx::Bitmap& current_bitmap = maps_bmp_[current_map_];
917
918 // Validate bitmap state before writing
919 if (!current_bitmap.is_active() || current_bitmap.size() == 0) {
920 LOG_ERROR(
921 "OverworldEditor",
922 "ERROR: RenderUpdatedMapBitmap - Bitmap %d is not active or has no "
923 "data (active=%s, size=%zu)",
924 current_map_, current_bitmap.is_active() ? "true" : "false",
925 current_bitmap.size());
926 return;
927 }
928
929 for (int y = 0; y < kTile16Size; ++y) {
930 for (int x = 0; x < kTile16Size; ++x) {
931 int pixel_index =
932 (start_position.y + y) * kOverworldMapSize + (start_position.x + x);
933
934 // Bounds check for pixel index
935 if (pixel_index < 0 ||
936 pixel_index >= static_cast<int>(current_bitmap.size())) {
937 LOG_ERROR(
938 "OverworldEditor",
939 "ERROR: RenderUpdatedMapBitmap - pixel_index %d out of bounds "
940 "(bitmap size=%zu)",
941 pixel_index, current_bitmap.size());
942 continue;
943 }
944
945 // Bounds check for tile data
946 int tile_data_index = y * kTile16Size + x;
947 if (tile_data_index < 0 ||
948 tile_data_index >= static_cast<int>(tile_data.size())) {
949 LOG_ERROR(
950 "OverworldEditor",
951 "ERROR: RenderUpdatedMapBitmap - tile_data_index %d out of bounds "
952 "(tile_data size=%zu)",
953 tile_data_index, tile_data.size());
954 continue;
955 }
956
957 current_bitmap.WriteToPixel(pixel_index, tile_data[tile_data_index]);
958 }
959 }
960
961 current_bitmap.set_modified(true);
962
963 // Immediately update the texture to reflect changes
965 &current_bitmap);
966}
967
969 LOG_DEBUG("OverworldEditor", "CheckForOverworldEdits: Frame %d",
970 ImGui::GetFrameCount());
971
973
974 // User has selected a tile they want to draw from the blockset
975 // and clicked on the canvas.
976 // Note: With TileSelectorWidget, we check if a valid tile is selected instead of canvas points
980 }
981
983 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
984 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
985 LOG_DEBUG("OverworldEditor",
986 "CheckForOverworldEdits: About to apply rectangle selection");
987
988 auto& selected_world =
989 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
990 : (current_world_ == 1)
991 ? overworld_.mutable_map_tiles()->dark_world
992 : overworld_.mutable_map_tiles()->special_world;
993 // new_start_pos and new_end_pos
994 auto start = ow_map_canvas_.selected_points()[0];
995 auto end = ow_map_canvas_.selected_points()[1];
996
997 // Calculate the bounds of the rectangle in terms of 16x16 tile indices
998 int start_x = std::floor(start.x / kTile16Size) * kTile16Size;
999 int start_y = std::floor(start.y / kTile16Size) * kTile16Size;
1000 int end_x = std::floor(end.x / kTile16Size) * kTile16Size;
1001 int end_y = std::floor(end.y / kTile16Size) * kTile16Size;
1002
1003 if (start_x > end_x)
1004 std::swap(start_x, end_x);
1005 if (start_y > end_y)
1006 std::swap(start_y, end_y);
1007
1008 constexpr int local_map_size = 512; // Size of each local map
1009 // Number of tiles per local map (since each tile is 16x16)
1010 constexpr int tiles_per_local_map = local_map_size / kTile16Size;
1011
1012 LOG_DEBUG("OverworldEditor",
1013 "CheckForOverworldEdits: About to fill rectangle with "
1014 "current_tile16_=%d",
1016
1017 // Apply the selected tiles to each position in the rectangle
1018 // CRITICAL FIX: Use pre-computed tile16_ids_ instead of recalculating from selected_tiles_
1019 // This prevents wrapping issues when dragging near boundaries
1020 int i = 0;
1021 for (int y = start_y;
1022 y <= end_y && i < static_cast<int>(selected_tile16_ids_.size());
1023 y += kTile16Size) {
1024 for (int x = start_x;
1025 x <= end_x && i < static_cast<int>(selected_tile16_ids_.size());
1026 x += kTile16Size, ++i) {
1027
1028 // Determine which local map (512x512) the tile is in
1029 int local_map_x = x / local_map_size;
1030 int local_map_y = y / local_map_size;
1031
1032 // Calculate the tile's position within its local map
1033 int tile16_x = (x % local_map_size) / kTile16Size;
1034 int tile16_y = (y % local_map_size) / kTile16Size;
1035
1036 // Calculate the index within the overall map structure
1037 int index_x = local_map_x * tiles_per_local_map + tile16_x;
1038 int index_y = local_map_y * tiles_per_local_map + tile16_y;
1039
1040 // FIXED: Use pre-computed tile ID from the ORIGINAL selection
1041 int tile16_id = selected_tile16_ids_[i];
1042 // Bounds check for the selected world array, accounting for rectangle size
1043 // Ensure the entire rectangle fits within the world bounds
1044 int rect_width = ((end_x - start_x) / kTile16Size) + 1;
1045 int rect_height = ((end_y - start_y) / kTile16Size) + 1;
1046
1047 // Prevent painting from wrapping around at the edges of large maps
1048 // Only allow painting if the entire rectangle is within the same 512x512 local map
1049 int start_local_map_x = start_x / local_map_size;
1050 int start_local_map_y = start_y / local_map_size;
1051 int end_local_map_x = end_x / local_map_size;
1052 int end_local_map_y = end_y / local_map_size;
1053
1054 bool in_same_local_map = (start_local_map_x == end_local_map_x) &&
1055 (start_local_map_y == end_local_map_y);
1056
1057 if (in_same_local_map && index_x >= 0 &&
1058 (index_x + rect_width - 1) < 0x200 && index_y >= 0 &&
1059 (index_y + rect_height - 1) < 0x200) {
1060 selected_world[index_x][index_y] = tile16_id;
1061
1062 // CRITICAL FIX: Also update the bitmap directly like single tile drawing
1063 ImVec2 tile_position(x, y);
1064 auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile16_id);
1065 if (!tile_data.empty()) {
1066 RenderUpdatedMapBitmap(tile_position, tile_data);
1067 LOG_DEBUG(
1068 "OverworldEditor",
1069 "CheckForOverworldEdits: Updated bitmap at position (%d,%d) "
1070 "with tile16_id=%d",
1071 x, y, tile16_id);
1072 } else {
1073 LOG_ERROR("OverworldEditor",
1074 "ERROR: Failed to get tile data for tile16_id=%d",
1075 tile16_id);
1076 }
1077 }
1078 }
1079 }
1080
1082 // Clear the rectangle selection after applying
1083 // This is commented out for now, will come back to later.
1084 // ow_map_canvas_.mutable_selected_tiles()->clear();
1085 // ow_map_canvas_.mutable_points()->clear();
1086 LOG_DEBUG(
1087 "OverworldEditor",
1088 "CheckForOverworldEdits: Rectangle selection applied and cleared");
1089 }
1090 }
1091}
1092
1095
1096 // Single tile case
1097 if (ow_map_canvas_.selected_tile_pos().x != -1) {
1100 ow_map_canvas_.set_selected_tile_pos(ImVec2(-1, -1));
1101
1102 // Scroll blockset canvas to show the selected tile
1104 }
1105
1106 // Rectangle selection case - use member variable instead of static local
1108 // Get the tile16 IDs from the selected tile ID positions
1109 selected_tile16_ids_.clear();
1110
1111 if (ow_map_canvas_.selected_tiles().size() > 0) {
1112 // Set the current world and map in overworld for proper tile lookup
1115 for (auto& each : ow_map_canvas_.selected_tiles()) {
1117 }
1118 }
1119 }
1120 // Create a composite image of all the tile16s selected
1123}
1124
1127 return absl::FailedPreconditionError("Clipboard unavailable");
1128 }
1129 // If a rectangle selection exists, copy its tile16 IDs into shared clipboard
1131 !ow_map_canvas_.selected_points().empty()) {
1132 std::vector<int> ids;
1133 const auto start = ow_map_canvas_.selected_points()[0];
1134 const auto end = ow_map_canvas_.selected_points()[1];
1135 const int start_x =
1136 static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
1137 const int end_x =
1138 static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
1139 const int start_y =
1140 static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
1141 const int end_y =
1142 static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
1143 const int width = end_x - start_x + 1;
1144 const int height = end_y - start_y + 1;
1145 ids.reserve(width * height);
1148 for (int y = start_y; y <= end_y; ++y) {
1149 for (int x = start_x; x <= end_x; ++x) {
1150 ids.push_back(overworld_.GetTile(x, y));
1151 }
1152 }
1153
1158 return absl::OkStatus();
1159 }
1160 // Single tile copy fallback
1161 if (current_tile16_ >= 0) {
1166 return absl::OkStatus();
1167 }
1168 return absl::FailedPreconditionError("Nothing selected to copy");
1169}
1170
1173 return absl::FailedPreconditionError("Clipboard unavailable");
1174 }
1176 return absl::FailedPreconditionError("Clipboard empty");
1177 }
1178 if (ow_map_canvas_.points().empty() &&
1180 return absl::FailedPreconditionError("No paste target");
1181 }
1182
1183 // Determine paste anchor position (use current mouse drawn tile position)
1184 const ImVec2 anchor = ow_map_canvas_.drawn_tile_position();
1185
1186 // Compute anchor in tile16 grid within the current map
1187 const int tile16_x =
1188 (static_cast<int>(anchor.x) % kOverworldMapSize) / kTile16Size;
1189 const int tile16_y =
1190 (static_cast<int>(anchor.y) % kOverworldMapSize) / kTile16Size;
1191
1192 auto& selected_world =
1193 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
1194 : (current_world_ == 1) ? overworld_.mutable_map_tiles()->dark_world
1195 : overworld_.mutable_map_tiles()->special_world;
1196
1197 const int superY = current_map_ / 8;
1198 const int superX = current_map_ % 8;
1199 const int tiles_per_local_map = 512 / kTile16Size;
1200
1204
1205 // Guard
1206 if (width * height != static_cast<int>(ids.size())) {
1207 return absl::InternalError("Clipboard dimensions mismatch");
1208 }
1209
1210 for (int dy = 0; dy < height; ++dy) {
1211 for (int dx = 0; dx < width; ++dx) {
1212 const int id = ids[dy * width + dx];
1213 const int gx = tile16_x + dx;
1214 const int gy = tile16_y + dy;
1215
1216 const int global_x = superX * 32 + gx;
1217 const int global_y = superY * 32 + gy;
1218 if (global_x < 0 || global_x >= 256 || global_y < 0 || global_y >= 256)
1219 continue;
1220 selected_world[global_x][global_y] = id;
1221 }
1222 }
1223
1225 return absl::OkStatus();
1226}
1227
1229 // 4096x4096, 512x512 maps and some are larges maps 1024x1024
1230 // CRITICAL FIX: Use canvas hover position (not raw ImGui mouse) for proper coordinate sync
1231 // hover_mouse_pos() already returns canvas-local coordinates (world space, not screen space)
1232 const auto mouse_position = ow_map_canvas_.hover_mouse_pos();
1233 const int large_map_size = 1024;
1234
1235 // Calculate which small map the mouse is currently over
1236 // No need to subtract canvas_zero_point - mouse_position is already in world coordinates
1237 int map_x = mouse_position.x / kOverworldMapSize;
1238 int map_y = mouse_position.y / kOverworldMapSize;
1239
1240 // Calculate the index of the map in the `maps_bmp_` vector
1241 int hovered_map = map_x + map_y * 8;
1242 if (current_world_ == 1) {
1243 hovered_map += 0x40;
1244 } else if (current_world_ == 2) {
1245 hovered_map += 0x80;
1246 }
1247
1248 // Only update current_map_ if not locked
1249 if (!current_map_lock_) {
1250 current_map_ = hovered_map;
1252
1253 // Ensure the current map is built (on-demand loading)
1255 }
1256
1257 const int current_highlighted_map = current_map_;
1258
1259 // Check if ZSCustomOverworld v3 is present
1260 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
1261 bool use_v3_area_sizes = (asm_version >= 3);
1262
1263 // Get area size for v3+ ROMs, otherwise use legacy logic
1264 if (use_v3_area_sizes) {
1266 auto area_size = overworld_.overworld_map(current_map_)->area_size();
1267 const int highlight_parent =
1268 overworld_.overworld_map(current_highlighted_map)->parent();
1269
1270 // Calculate parent map coordinates accounting for world offset
1271 int parent_map_x;
1272 int parent_map_y;
1273 if (current_world_ == 0) {
1274 // Light World (0x00-0x3F)
1275 parent_map_x = highlight_parent % 8;
1276 parent_map_y = highlight_parent / 8;
1277 } else if (current_world_ == 1) {
1278 // Dark World (0x40-0x7F)
1279 parent_map_x = (highlight_parent - 0x40) % 8;
1280 parent_map_y = (highlight_parent - 0x40) / 8;
1281 } else {
1282 // Special World (0x80-0x9F)
1283 parent_map_x = (highlight_parent - 0x80) % 8;
1284 parent_map_y = (highlight_parent - 0x80) / 8;
1285 }
1286
1287 // Draw outline based on area size
1288 switch (area_size) {
1289 case AreaSizeEnum::LargeArea:
1290 // 2x2 grid (1024x1024)
1292 parent_map_y * kOverworldMapSize,
1293 large_map_size, large_map_size);
1294 break;
1295 case AreaSizeEnum::WideArea:
1296 // 2x1 grid (1024x512) - horizontal
1298 parent_map_y * kOverworldMapSize,
1299 large_map_size, kOverworldMapSize);
1300 break;
1301 case AreaSizeEnum::TallArea:
1302 // 1x2 grid (512x1024) - vertical
1304 parent_map_y * kOverworldMapSize,
1305 kOverworldMapSize, large_map_size);
1306 break;
1307 case AreaSizeEnum::SmallArea:
1308 default:
1310 parent_map_y * kOverworldMapSize,
1312 break;
1313 }
1314 } else {
1315 // Legacy logic for vanilla and v2 ROMs
1316 int world_offset = current_world_ * 0x40;
1317 if (overworld_.overworld_map(current_map_)->is_large_map() ||
1318 overworld_.overworld_map(current_map_)->large_index() != 0) {
1319 const int highlight_parent =
1320 overworld_.overworld_map(current_highlighted_map)->parent();
1321
1322 // CRITICAL FIX: Account for world offset when calculating parent coordinates
1323 // For Dark World (0x40-0x7F), parent IDs are in range 0x40-0x7F
1324 // For Special World (0x80-0x9F), parent IDs are in range 0x80-0x9F
1325 // We need to subtract the world offset to get display grid coordinates (0-7)
1326 int parent_map_x;
1327 int parent_map_y;
1328 if (current_world_ == 0) {
1329 // Light World (0x00-0x3F)
1330 parent_map_x = highlight_parent % 8;
1331 parent_map_y = highlight_parent / 8;
1332 } else if (current_world_ == 1) {
1333 // Dark World (0x40-0x7F) - subtract 0x40 to get display coordinates
1334 parent_map_x = (highlight_parent - 0x40) % 8;
1335 parent_map_y = (highlight_parent - 0x40) / 8;
1336 } else {
1337 // Special World (0x80-0x9F) - subtract 0x80 to get display coordinates
1338 parent_map_x = (highlight_parent - 0x80) % 8;
1339 parent_map_y = (highlight_parent - 0x80) / 8;
1340 }
1341
1343 parent_map_y * kOverworldMapSize,
1344 large_map_size, large_map_size);
1345 } else {
1346 // Calculate map coordinates accounting for world offset
1347 int current_map_x;
1348 int current_map_y;
1349 if (current_world_ == 0) {
1350 // Light World (0x00-0x3F)
1351 current_map_x = current_highlighted_map % 8;
1352 current_map_y = current_highlighted_map / 8;
1353 } else if (current_world_ == 1) {
1354 // Dark World (0x40-0x7F)
1355 current_map_x = (current_highlighted_map - 0x40) % 8;
1356 current_map_y = (current_highlighted_map - 0x40) / 8;
1357 } else {
1358 // Special World (0x80-0x9F) - use display coordinates based on current_world_
1359 // The special world maps are displayed in the same 8x8 grid as LW/DW
1360 current_map_x = (current_highlighted_map - 0x80) % 8;
1361 current_map_y = (current_highlighted_map - 0x80) / 8;
1362 }
1364 current_map_y * kOverworldMapSize,
1366 }
1367 }
1368
1369 // Ensure current map has texture created for rendering
1371
1372 if (maps_bmp_[current_map_].modified()) {
1375
1376 // Ensure tile16 blockset is fully updated before rendering
1378 // TODO: Queue texture for later rendering.
1379 // Renderer::Get().UpdateBitmap(&tile16_blockset_.atlas);
1380 }
1381
1382 // Update map texture with the traditional direct update approach
1383 // TODO: Queue texture for later rendering.
1384 // Renderer::Get().UpdateBitmap(&maps_bmp_[current_map_]);
1385 maps_bmp_[current_map_].set_modified(false);
1386 }
1387
1388 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
1390 }
1391
1392 // If double clicked, toggle the current map
1393 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
1395 }
1396
1397 return absl::OkStatus();
1398}
1399
1400// Overworld Canvas Pan/Zoom Helpers
1401
1402namespace {
1403
1404// Calculate the total canvas content size based on world layout
1406 // 8x8 grid of 512x512 maps = 4096x4096 total
1407 constexpr float kWorldSize = 512.0f * 8.0f; // 4096
1408 return ImVec2(kWorldSize * scale, kWorldSize * scale);
1409}
1410
1411// Clamp scroll position to valid bounds
1412ImVec2 ClampScrollPosition(ImVec2 scroll, ImVec2 content_size,
1413 ImVec2 visible_size) {
1414 // Calculate maximum scroll values
1415 float max_scroll_x = std::max(0.0f, content_size.x - visible_size.x);
1416 float max_scroll_y = std::max(0.0f, content_size.y - visible_size.y);
1417
1418 // Clamp to valid range [min_scroll, 0]
1419 // Note: Canvas uses negative scrolling for right/down
1420 float clamped_x = std::clamp(scroll.x, -max_scroll_x, 0.0f);
1421 float clamped_y = std::clamp(scroll.y, -max_scroll_y, 0.0f);
1422
1423 return ImVec2(clamped_x, clamped_y);
1424}
1425
1426} // namespace
1427
1429 // Middle mouse button panning (works in all modes)
1430 if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle) &&
1431 ImGui::IsItemHovered()) {
1433
1434 // Get mouse delta and apply to scroll
1435 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
1436 ImVec2 current_scroll = ow_map_canvas_.scrolling();
1437 ImVec2 new_scroll = ImVec2(current_scroll.x + mouse_delta.x,
1438 current_scroll.y + mouse_delta.y);
1439
1440 // Clamp scroll to boundaries
1441 ImVec2 content_size =
1442 CalculateOverworldContentSize(ow_map_canvas_.global_scale());
1443 ImVec2 visible_size = ow_map_canvas_.canvas_size();
1444 new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size);
1445
1446 ow_map_canvas_.set_scrolling(new_scroll);
1447 }
1448
1449 if (ImGui::IsMouseReleased(ImGuiMouseButton_Middle) &&
1451 middle_mouse_dragging_ = false;
1452 }
1453}
1454
1456 if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
1457 return;
1458 }
1459
1460 const ImGuiIO& io = ImGui::GetIO();
1461
1462 // Mouse wheel zoom with Ctrl key
1463 if (io.MouseWheel != 0.0f && io.KeyCtrl) {
1464 float current_scale = ow_map_canvas_.global_scale();
1465 float zoom_delta = io.MouseWheel * 0.1f;
1466 float new_scale = current_scale + zoom_delta;
1467
1468 // Clamp zoom range (0.25x to 2.0x)
1469 new_scale = std::clamp(new_scale, 0.25f, 2.0f);
1470
1471 if (new_scale != current_scale) {
1472 // Get mouse position relative to canvas
1473 ImVec2 mouse_pos_canvas =
1474 ImVec2(io.MousePos.x - ow_map_canvas_.zero_point().x,
1475 io.MousePos.y - ow_map_canvas_.zero_point().y);
1476
1477 // Calculate content position under mouse before zoom
1478 ImVec2 scroll = ow_map_canvas_.scrolling();
1479 ImVec2 content_pos_before =
1480 ImVec2((mouse_pos_canvas.x - scroll.x) / current_scale,
1481 (mouse_pos_canvas.y - scroll.y) / current_scale);
1482
1483 // Apply new scale
1485
1486 // Calculate new scroll to keep same content under mouse
1487 ImVec2 new_scroll =
1488 ImVec2(mouse_pos_canvas.x - (content_pos_before.x * new_scale),
1489 mouse_pos_canvas.y - (content_pos_before.y * new_scale));
1490
1491 // Clamp scroll to boundaries with new scale
1492 ImVec2 content_size = CalculateOverworldContentSize(new_scale);
1493 ImVec2 visible_size = ow_map_canvas_.canvas_size();
1494 new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size);
1495
1496 ow_map_canvas_.set_scrolling(new_scroll);
1497 }
1498 }
1499}
1500
1505
1507 float scale = ow_map_canvas_.global_scale();
1508 ImVec2 content_size = CalculateOverworldContentSize(scale);
1509 ImVec2 visible_size = ow_map_canvas_.canvas_size();
1510
1511 // Center the view
1512 ImVec2 centered_scroll = ImVec2(-(content_size.x - visible_size.x) / 2.0f,
1513 -(content_size.y - visible_size.y) / 2.0f);
1514
1515 ow_map_canvas_.set_scrolling(centered_scroll);
1516}
1517
1519 // Legacy wrapper - now calls HandleOverworldPan
1521}
1522
1524 // Simplified map settings - compact row with popup panels for detailed editing
1526 map_properties_system_->DrawSimplifiedMapSettings(
1530 (int&)current_mode);
1531 }
1532
1537
1538 // Setup dynamic context menu based on current map state (Phase 3B)
1540 map_properties_system_->SetupCanvasContextMenu(
1543 show_overlay_editor_, static_cast<int>(current_mode));
1544 }
1545
1546 // Handle pan and zoom (works in all modes)
1549
1550 // Context menu only in MOUSE mode
1552 if (entity_renderer_->hovered_entity() == nullptr) {
1554 }
1555 } else if (current_mode == EditingMode::DRAW_TILE) {
1556 // Tile painting mode - handle tile edits and right-click tile picking
1558 }
1559
1560 if (overworld_.is_loaded()) {
1562
1563 // Draw all entities using the entity renderer
1564 // Convert entity_edit_mode_ to legacy mode int for entity renderer
1565 int entity_mode_int = static_cast<int>(entity_edit_mode_);
1568 entity_mode_int);
1571 entity_mode_int);
1572 entity_renderer_->DrawItems(current_world_, entity_mode_int);
1573 entity_renderer_->DrawSprites(current_world_, game_state_, entity_mode_int);
1574
1575 // Draw overlay preview if enabled
1577 map_properties_system_->DrawOverlayPreviewOnMap(
1579 }
1580
1583 }
1584 // CRITICAL FIX: Use canvas hover state, not ImGui::IsItemHovered()
1585 // IsItemHovered() checks the LAST drawn item, which could be entities/overlay,
1586 // not the canvas InvisibleButton. ow_map_canvas_.IsMouseHovering() correctly
1587 // tracks whether mouse is over the canvas area.
1590
1591 // --- BEGIN NEW DRAG/DROP LOGIC ---
1593 auto hovered_entity = entity_renderer_->hovered_entity();
1594
1595 // 1. Initiate drag
1596 if (!is_dragging_entity_ && hovered_entity &&
1597 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
1598 dragged_entity_ = hovered_entity;
1599 is_dragging_entity_ = true;
1603 }
1604 }
1605
1606 // 2. Update drag
1608 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
1609 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
1610 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
1611 float scale = ow_map_canvas_.global_scale();
1612 if (scale > 0.0f) {
1613 dragged_entity_->x_ += mouse_delta.x / scale;
1614 dragged_entity_->y_ += mouse_delta.y / scale;
1615 }
1616 }
1617
1618 // 3. End drag
1619 if (is_dragging_entity_ &&
1620 ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
1621 if (dragged_entity_) {
1626 rom_->set_dirty(true);
1627 }
1628 is_dragging_entity_ = false;
1629 dragged_entity_ = nullptr;
1631 }
1632 }
1633 // --- END NEW DRAG/DROP LOGIC ---
1634 }
1635
1638 ImGui::EndChild();
1639
1640}
1641
1644 ImGui::BeginGroup();
1645 gui::BeginChildWithScrollbar("##Tile16SelectorScrollRegion");
1647
1648 if (!blockset_selector_) {
1649 gui::TileSelectorWidget::Config selector_config;
1650 selector_config.tile_size = 16;
1651 selector_config.display_scale = 2.0f;
1652 selector_config.tiles_per_row = 8;
1653 selector_config.total_tiles = zelda3::kNumTile16Individual;
1654 selector_config.draw_offset = ImVec2(2.0f, 0.0f);
1655 selector_config.highlight_color = ImVec4(0.95f, 0.75f, 0.3f, 1.0f);
1656
1657 blockset_selector_ = std::make_unique<gui::TileSelectorWidget>(
1658 "OwBlocksetSelector", selector_config);
1659 blockset_selector_->AttachCanvas(&blockset_canvas_);
1660 }
1661
1663
1665 bool atlas_ready = map_blockset_loaded_ && atlas.is_active();
1666 auto result = blockset_selector_->Render(atlas, atlas_ready);
1667
1668 if (result.selection_changed) {
1669 current_tile16_ = result.selected_tile;
1671 if (!status.ok()) {
1672 // Store error but ensure we close the child before returning
1673 ImGui::EndChild();
1674 ImGui::EndGroup();
1675 return status;
1676 }
1677 // Note: We do NOT auto-scroll here because it breaks user interaction.
1678 // The canvas should only scroll when explicitly requested (e.g., when
1679 // selecting a tile from the overworld canvas via ScrollBlocksetCanvasToCurrentTile).
1680 }
1681
1682 if (result.tile_double_clicked) {
1683 show_tile16_editor_ = true;
1684 }
1685
1686 ImGui::EndChild();
1687 ImGui::EndGroup();
1688 return absl::OkStatus();
1689}
1690
1694 if (all_gfx_loaded_) {
1695 int key = 0;
1696 for (auto& value : gfx::Arena::Get().gfx_sheets()) {
1697 int offset = 0x40 * (key + 1);
1698 int top_left_y = graphics_bin_canvas_.zero_point().y + 2;
1699 if (key >= 1) {
1700 top_left_y = graphics_bin_canvas_.zero_point().y + 0x40 * key;
1701 }
1702 auto texture = value.texture();
1703 graphics_bin_canvas_.draw_list()->AddImage(
1704 (ImTextureID)(intptr_t)texture,
1705 ImVec2(graphics_bin_canvas_.zero_point().x + 2, top_left_y),
1706 ImVec2(graphics_bin_canvas_.zero_point().x + 0x100,
1707 graphics_bin_canvas_.zero_point().y + offset));
1708 key++;
1709 }
1710 }
1713}
1714
1716 if (overworld_.is_loaded()) {
1717 // Always ensure current map graphics are loaded
1718 if (!current_graphics_set_.contains(current_map_)) {
1721 gfx::Bitmap bmp;
1722 // TODO: Queue texture for later rendering.
1723 // Renderer::Get().CreateAndRenderBitmap(0x80, kOverworldMapSize, 0x08,
1724 // overworld_.current_graphics(), bmp,
1725 // palette_);
1727 }
1728 }
1729
1731 ImGui::BeginGroup();
1732 gui::BeginChildWithScrollbar("##AreaGraphicsScrollRegion");
1735 {
1737 if (current_graphics_set_.contains(current_map_) &&
1738 current_graphics_set_[current_map_].is_active()) {
1740 2.0f);
1741 }
1745 }
1746 ImGui::EndChild();
1747 ImGui::EndGroup();
1748 return absl::OkStatus();
1749}
1750
1752 if (core::FeatureFlags::get().overworld.kSaveOverworldMaps) {
1757 }
1758 if (core::FeatureFlags::get().overworld.kSaveOverworldEntrances) {
1760 }
1761 if (core::FeatureFlags::get().overworld.kSaveOverworldExits) {
1763 }
1764 if (core::FeatureFlags::get().overworld.kSaveOverworldItems) {
1766 }
1767 if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) {
1770 }
1771 return absl::OkStatus();
1772}
1773
1775 gfx::ScopedTimer timer("LoadGraphics");
1776
1777 LOG_DEBUG("OverworldEditor", "Loading overworld.");
1778 // Load the Link to the Past overworld.
1779 {
1780 gfx::ScopedTimer load_timer("Overworld::Load");
1782 }
1784
1785 LOG_DEBUG("OverworldEditor", "Loading overworld graphics (optimized).");
1786
1787 // Phase 1: Create bitmaps without textures for faster loading
1788 // This avoids blocking the main thread with GPU texture creation
1789 {
1790 gfx::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics");
1791 // TODO: Queue texture for later rendering.
1792 // Renderer::Get().CreateBitmapWithoutTexture(0x80, kOverworldMapSize, 0x40,
1793 // overworld_.current_graphics(),
1794 // current_gfx_bmp_, palette_);
1795 }
1796
1797 LOG_DEBUG("OverworldEditor",
1798 "Loading overworld tileset (deferred textures).");
1799 {
1800 gfx::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
1801 // TODO: Queue texture for later rendering.
1802 // Renderer::Get().CreateBitmapWithoutTexture(
1803 // 0x80, 0x2000, 0x08, overworld_.tile16_blockset_data(),
1804 // tile16_blockset_bmp_, palette_);
1805 }
1806 map_blockset_loaded_ = true;
1807
1808 // Copy the tile16 data into individual tiles.
1809 auto tile16_blockset_data = overworld_.tile16_blockset_data();
1810 LOG_DEBUG("OverworldEditor", "Loading overworld tile16 graphics.");
1811
1812 {
1813 gfx::ScopedTimer tilemap_timer("CreateTilemap");
1815 gfx::CreateTilemap(renderer_, tile16_blockset_data, 0x80, 0x2000,
1817
1818 // Queue texture creation for the tile16 blockset atlas
1823 }
1824 }
1825
1826 // Phase 2: Create bitmaps only for essential maps initially
1827 // Non-essential maps will be created on-demand when accessed
1828 constexpr int kEssentialMapsPerWorld = 8;
1829 constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
1830 constexpr int kDarkWorldEssential =
1831 zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld;
1832 constexpr int kSpecialWorldEssential =
1833 zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
1834
1835 LOG_DEBUG(
1836 "OverworldEditor",
1837 "Creating bitmaps for essential maps only (first %d maps per world)",
1838 kEssentialMapsPerWorld);
1839
1840 std::vector<gfx::Bitmap*> maps_to_texture;
1841 maps_to_texture.reserve(kEssentialMapsPerWorld *
1842 3); // 8 maps per world * 3 worlds
1843
1844 {
1845 gfx::ScopedTimer maps_timer("CreateEssentialOverworldMaps");
1846 for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
1847 bool is_essential = false;
1848
1849 // Check if this is an essential map
1850 if (i < kLightWorldEssential) {
1851 is_essential = true;
1852 } else if (i >= zelda3::kDarkWorldMapIdStart && i < kDarkWorldEssential) {
1853 is_essential = true;
1854 } else if (i >= zelda3::kSpecialWorldMapIdStart &&
1855 i < kSpecialWorldEssential) {
1856 is_essential = true;
1857 }
1858
1859 if (is_essential) {
1861 auto palette = overworld_.current_area_palette();
1862 try {
1863 // Create bitmap data and surface but defer texture creation
1866 maps_bmp_[i].SetPalette(palette);
1867 maps_to_texture.push_back(&maps_bmp_[i]);
1868 } catch (const std::bad_alloc& e) {
1869 std::cout << "Error allocating map " << i << ": " << e.what()
1870 << std::endl;
1871 continue;
1872 }
1873 }
1874 // Non-essential maps will be created on-demand when accessed
1875 }
1876 }
1877
1878 // Phase 3: Create textures only for currently visible maps
1879 // Only create textures for the first few maps initially
1880 const int initial_texture_count =
1881 std::min(4, static_cast<int>(maps_to_texture.size()));
1882 {
1883 gfx::ScopedTimer initial_textures_timer("CreateInitialTextures");
1884 for (int i = 0; i < initial_texture_count; ++i) {
1885 // Queue texture creation/update for initial maps via Arena's deferred system
1887 gfx::Arena::TextureCommandType::CREATE, maps_to_texture[i]);
1888 }
1889 }
1890
1891 // Queue remaining maps for progressive loading via Arena
1892 // Priority based on current world (0 = current world, 11+ = other worlds)
1893 for (size_t i = initial_texture_count; i < maps_to_texture.size(); ++i) {
1894 // Determine priority based on which world this map belongs to
1895 int map_index = -1;
1896 for (int j = 0; j < zelda3::kNumOverworldMaps; ++j) {
1897 if (&maps_bmp_[j] == maps_to_texture[i]) {
1898 map_index = j;
1899 break;
1900 }
1901 }
1902
1903 int priority = 15; // Default low priority
1904 if (map_index >= 0) {
1905 int map_world = map_index / 0x40;
1906 priority = (map_world == current_world_)
1907 ? 5
1908 : 15; // Current world = priority 5, others = 15
1909 }
1910
1911 // Queue texture creation for remaining maps via Arena's deferred system
1912 // Note: Priority system to be implemented in future enhancement
1914 gfx::Arena::TextureCommandType::CREATE, maps_to_texture[i]);
1915 }
1916
1917 if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
1918 {
1919 gfx::ScopedTimer sprites_timer("LoadSpriteGraphics");
1921 }
1922 }
1923
1924 return absl::OkStatus();
1925}
1926
1928 // Render the sprites for each Overworld map
1929 const int depth = 0x10;
1930 for (int i = 0; i < 3; i++)
1931 for (auto const& sprite : *overworld_.mutable_sprites(i)) {
1932 int width = sprite.width();
1933 int height = sprite.height();
1934 if (width == 0 || height == 0) {
1935 continue;
1936 }
1937 if (sprite_previews_.size() < sprite.id()) {
1938 sprite_previews_.resize(sprite.id() + 1);
1939 }
1940 sprite_previews_[sprite.id()].Create(width, height, depth,
1941 *sprite.preview_graphics());
1942 sprite_previews_[sprite.id()].SetPalette(palette_);
1943 // TODO: Queue texture for later rendering.
1944 // Renderer::Get().RenderBitmap(&(sprite_previews_[sprite.id()]));
1945 }
1946 return absl::OkStatus();
1947}
1948
1950 // Process queued texture commands via Arena's deferred system
1951 if (renderer_) {
1953 }
1954
1955 // Also process deferred map refreshes for modified maps
1956 int refresh_count = 0;
1957 const int max_refreshes_per_frame = 2;
1958
1959 for (int i = 0;
1960 i < zelda3::kNumOverworldMaps && refresh_count < max_refreshes_per_frame;
1961 ++i) {
1962 if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) {
1963 // Check if this map is in current world (prioritize)
1964 bool is_current_world = (i / 0x40 == current_world_);
1965 bool is_current_map = (i == current_map_);
1966
1967 if (is_current_map || is_current_world) {
1969 refresh_count++;
1970 }
1971 }
1972 }
1973}
1974
1976 if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
1977 return;
1978 }
1979
1980 // Ensure the map is built first (on-demand loading)
1981 auto status = overworld_.EnsureMapBuilt(map_index);
1982 if (!status.ok()) {
1983 LOG_ERROR("OverworldEditor", "Failed to build map %d: %s", map_index,
1984 status.message());
1985 return;
1986 }
1987
1988 auto& bitmap = maps_bmp_[map_index];
1989
1990 // If bitmap doesn't exist yet (non-essential map), create it now
1991 if (!bitmap.is_active()) {
1992 overworld_.set_current_map(map_index);
1993 auto palette = overworld_.current_area_palette();
1994 try {
1995 bitmap.Create(kOverworldMapSize, kOverworldMapSize, 0x80,
1997 bitmap.SetPalette(palette);
1998 } catch (const std::bad_alloc& e) {
1999 LOG_ERROR("OverworldEditor", "Error allocating bitmap for map %d: %s",
2000 map_index, e.what());
2001 return;
2002 }
2003 }
2004
2005 if (!bitmap.texture() && bitmap.is_active()) {
2006 // Queue texture creation for this map
2009 }
2010}
2011
2013 overworld_.mutable_overworld_map(map_index)->LoadAreaGraphics();
2014 status_ = overworld_.mutable_overworld_map(map_index)->BuildTileset();
2016 status_ = overworld_.mutable_overworld_map(map_index)->BuildTiles16Gfx(
2019 status_ = overworld_.mutable_overworld_map(map_index)->BuildBitmap(
2021 maps_bmp_[map_index].set_data(
2022 overworld_.mutable_overworld_map(map_index)->bitmap_data());
2023 maps_bmp_[map_index].set_modified(true);
2025}
2026
2028 // Use the new on-demand refresh system
2030}
2031
2040 if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
2041 return;
2042 }
2043
2044 // Check if the map is actually visible or being edited
2045 bool is_current_map = (map_index == current_map_);
2046 bool is_current_world = (map_index / 0x40 == current_world_);
2047
2048 // For non-current maps in non-current worlds, defer the refresh
2049 if (!is_current_map && !is_current_world) {
2050 // Mark for deferred refresh - will be processed when the map becomes visible
2051 maps_bmp_[map_index].set_modified(true);
2052 return;
2053 }
2054
2055 // For visible maps, do immediate refresh
2056 RefreshChildMapOnDemand(map_index);
2057}
2058
2063 auto* map = overworld_.mutable_overworld_map(map_index);
2064
2065 // Check what actually needs to be refreshed
2066 bool needs_graphics_rebuild = maps_bmp_[map_index].modified();
2067 bool needs_palette_rebuild = false; // Could be tracked more granularly
2068
2069 if (needs_graphics_rebuild) {
2070 // Only rebuild what's actually changed
2071 map->LoadAreaGraphics();
2072
2073 // Rebuild tileset only if graphics changed
2074 auto status = map->BuildTileset();
2075 if (!status.ok()) {
2076 LOG_ERROR("OverworldEditor", "Failed to build tileset for map %d: %s",
2077 map_index, status.message().data());
2078 return;
2079 }
2080
2081 // Rebuild tiles16 graphics
2082 status = map->BuildTiles16Gfx(*overworld_.mutable_tiles16(),
2083 overworld_.tiles16().size());
2084 if (!status.ok()) {
2085 LOG_ERROR("OverworldEditor",
2086 "Failed to build tiles16 graphics for map %d: %s", map_index,
2087 status.message().data());
2088 return;
2089 }
2090
2091 // Rebuild bitmap
2092 status = map->BuildBitmap(overworld_.GetMapTiles(current_world_));
2093 if (!status.ok()) {
2094 LOG_ERROR("OverworldEditor", "Failed to build bitmap for map %d: %s",
2095 map_index, status.message().data());
2096 return;
2097 }
2098
2099 // Update bitmap data
2100 maps_bmp_[map_index].set_data(map->bitmap_data());
2101 maps_bmp_[map_index].set_modified(false);
2102
2103 // Validate surface synchronization to help debug crashes
2104 if (!maps_bmp_[map_index].ValidateDataSurfaceSync()) {
2105 LOG_WARN("OverworldEditor",
2106 "Warning: Surface synchronization issue detected for map %d",
2107 map_index);
2108 }
2109
2110 // Queue texture update to ensure changes are visible
2111 if (maps_bmp_[map_index].texture()) {
2114 } else {
2117 }
2118 }
2119
2120 // Handle multi-area maps (large, wide, tall) with safe coordination
2121 // Check if ZSCustomOverworld v3 is present
2122 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
2123 bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
2124
2125 if (use_v3_area_sizes) {
2126 // Use v3 multi-area coordination
2127 RefreshMultiAreaMapsSafely(map_index, map);
2128 } else {
2129 // Legacy logic: only handle large maps for vanilla/v2
2130 if (map->is_large_map()) {
2131 RefreshMultiAreaMapsSafely(map_index, map);
2132 }
2133 }
2134}
2135
2144 zelda3::OverworldMap* map) {
2146
2147 // Skip if this is already a processed sibling to avoid double-processing
2148 static std::set<int> currently_processing;
2149 if (currently_processing.count(map_index)) {
2150 return;
2151 }
2152
2153 auto area_size = map->area_size();
2154 if (area_size == AreaSizeEnum::SmallArea) {
2155 return; // No siblings to coordinate
2156 }
2157
2158 LOG_DEBUG(
2159 "OverworldEditor",
2160 "RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)",
2161 (area_size == AreaSizeEnum::LargeArea) ? "large"
2162 : (area_size == AreaSizeEnum::WideArea) ? "wide"
2163 : "tall",
2164 map_index, map->parent());
2165
2166 // Determine all maps that are part of this multi-area structure
2167 std::vector<int> sibling_maps;
2168 int parent_id = map->parent();
2169
2170 // Use the same logic as ZScream for area coordination
2171 switch (area_size) {
2172 case AreaSizeEnum::LargeArea: {
2173 // Large Area: 2x2 grid (4 maps total)
2174 // Parent is top-left (quadrant 0), siblings are:
2175 // +1 (top-right, quadrant 1), +8 (bottom-left, quadrant 2), +9 (bottom-right, quadrant 3)
2176 sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
2177 LOG_DEBUG(
2178 "OverworldEditor",
2179 "RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
2180 parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
2181 break;
2182 }
2183
2184 case AreaSizeEnum::WideArea: {
2185 // Wide Area: 2x1 grid (2 maps total, horizontally adjacent)
2186 // Parent is left, sibling is +1 (right)
2187 sibling_maps = {parent_id, parent_id + 1};
2188 LOG_DEBUG("OverworldEditor",
2189 "RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d",
2190 parent_id, parent_id + 1);
2191 break;
2192 }
2193
2194 case AreaSizeEnum::TallArea: {
2195 // Tall Area: 1x2 grid (2 maps total, vertically adjacent)
2196 // Parent is top, sibling is +8 (bottom)
2197 sibling_maps = {parent_id, parent_id + 8};
2198 LOG_DEBUG("OverworldEditor",
2199 "RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d",
2200 parent_id, parent_id + 8);
2201 break;
2202 }
2203
2204 default:
2205 LOG_WARN("OverworldEditor",
2206 "RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
2207 static_cast<int>(area_size), map_index);
2208 return;
2209 }
2210
2211 // Mark all siblings as being processed to prevent recursion
2212 for (int sibling : sibling_maps) {
2213 currently_processing.insert(sibling);
2214 }
2215
2216 // Only refresh siblings that are visible/current and need updating
2217 for (int sibling : sibling_maps) {
2218 if (sibling == map_index) {
2219 continue; // Skip self (already processed above)
2220 }
2221
2222 // Bounds check
2223 if (sibling < 0 || sibling >= zelda3::kNumOverworldMaps) {
2224 continue;
2225 }
2226
2227 // Only refresh if it's visible or current
2228 bool is_current_map = (sibling == current_map_);
2229 bool is_current_world = (sibling / 0x40 == current_world_);
2230 bool needs_refresh = maps_bmp_[sibling].modified();
2231
2232 if ((is_current_map || is_current_world) && needs_refresh) {
2233 LOG_DEBUG("OverworldEditor",
2234 "RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d "
2235 "(parent: %d)",
2236 (area_size == AreaSizeEnum::LargeArea) ? "large"
2237 : (area_size == AreaSizeEnum::WideArea) ? "wide"
2238 : "tall",
2239 sibling, parent_id);
2240
2241 // Direct refresh without calling RefreshChildMapOnDemand to avoid recursion
2242 auto* sibling_map = overworld_.mutable_overworld_map(sibling);
2243 if (sibling_map && maps_bmp_[sibling].modified()) {
2244 sibling_map->LoadAreaGraphics();
2245
2246 auto status = sibling_map->BuildTileset();
2247 if (status.ok()) {
2248 status = sibling_map->BuildTiles16Gfx(*overworld_.mutable_tiles16(),
2249 overworld_.tiles16().size());
2250 if (status.ok()) {
2251 // Load palette for the sibling map
2252 status = sibling_map->LoadPalette();
2253 if (status.ok()) {
2254 status = sibling_map->BuildBitmap(
2256 if (status.ok()) {
2257 maps_bmp_[sibling].set_data(sibling_map->bitmap_data());
2258
2259 // SAFETY: Only set palette if bitmap has a valid surface
2260 if (maps_bmp_[sibling].is_active() &&
2261 maps_bmp_[sibling].surface()) {
2262 maps_bmp_[sibling].SetPalette(
2264 }
2265 maps_bmp_[sibling].set_modified(false);
2266
2267 // Queue texture update/creation
2268 if (maps_bmp_[sibling].texture()) {
2271 &maps_bmp_[sibling]);
2272 } else {
2273 EnsureMapTexture(sibling);
2274 }
2275 }
2276 }
2277 }
2278 }
2279
2280 if (!status.ok()) {
2281 LOG_ERROR(
2282 "OverworldEditor",
2283 "RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: "
2284 "%s",
2285 sibling, status.message().data());
2286 }
2287 }
2288 } else if (!is_current_map && !is_current_world) {
2289 // Mark non-visible siblings for deferred refresh
2290 maps_bmp_[sibling].set_modified(true);
2291 }
2292 }
2293
2294 // Clear processing set after completion
2295 for (int sibling : sibling_maps) {
2296 currently_processing.erase(sibling);
2297 }
2298}
2299
2303 const auto current_map_palette = overworld_.current_area_palette();
2304
2305 // Check if ZSCustomOverworld v3 is present
2306 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
2307 bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
2308
2309 if (use_v3_area_sizes) {
2310 // Use v3 area size system
2312 auto area_size = overworld_.overworld_map(current_map_)->area_size();
2313
2314 if (area_size != AreaSizeEnum::SmallArea) {
2315 // Get all sibling maps that need palette updates
2316 std::vector<int> sibling_maps;
2317 int parent_id = overworld_.overworld_map(current_map_)->parent();
2318
2319 switch (area_size) {
2320 case AreaSizeEnum::LargeArea:
2321 // 2x2 grid: parent, parent+1, parent+8, parent+9
2322 sibling_maps = {parent_id, parent_id + 1, parent_id + 8,
2323 parent_id + 9};
2324 break;
2325 case AreaSizeEnum::WideArea:
2326 // 2x1 grid: parent, parent+1
2327 sibling_maps = {parent_id, parent_id + 1};
2328 break;
2329 case AreaSizeEnum::TallArea:
2330 // 1x2 grid: parent, parent+8
2331 sibling_maps = {parent_id, parent_id + 8};
2332 break;
2333 default:
2334 break;
2335 }
2336
2337 // Update palette for all siblings
2338 for (int sibling_index : sibling_maps) {
2339 if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
2340 continue;
2341 }
2343 overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
2344 maps_bmp_[sibling_index].SetPalette(current_map_palette);
2345 }
2346 } else {
2347 // Small area - only update current map
2348 maps_bmp_[current_map_].SetPalette(current_map_palette);
2349 }
2350 } else {
2351 // Legacy logic for vanilla and v2 ROMs
2352 if (overworld_.overworld_map(current_map_)->is_large_map()) {
2353 // We need to update the map and its siblings if it's a large map
2354 for (int i = 1; i < 4; i++) {
2355 int sibling_index =
2356 overworld_.overworld_map(current_map_)->parent() + i;
2357 if (i >= 2)
2358 sibling_index += 6;
2360 overworld_.mutable_overworld_map(sibling_index)->LoadPalette());
2361
2362 // SAFETY: Only set palette if bitmap has a valid surface
2363 if (maps_bmp_[sibling_index].is_active() &&
2364 maps_bmp_[sibling_index].surface()) {
2365 maps_bmp_[sibling_index].SetPalette(current_map_palette);
2366 }
2367 }
2368 }
2369
2370 // SAFETY: Only set palette if bitmap has a valid surface
2371 if (maps_bmp_[current_map_].is_active() &&
2372 maps_bmp_[current_map_].surface()) {
2373 maps_bmp_[current_map_].SetPalette(current_map_palette);
2374 }
2375 }
2376
2377 return absl::OkStatus();
2378}
2379
2381 // Mark the bitmap as modified to force refresh on next update
2382 if (map_index >= 0 && map_index < static_cast<int>(maps_bmp_.size())) {
2383 maps_bmp_[map_index].set_modified(true);
2384
2385 // Clear blockset cache
2386 current_blockset_ = 0xFF;
2387
2388 LOG_DEBUG("OverworldEditor",
2389 "ForceRefreshGraphics: Map %d marked for refresh", map_index);
2390 }
2391}
2392
2394 bool include_self) {
2395 if (map_index < 0 || map_index >= static_cast<int>(maps_bmp_.size())) {
2396 return;
2397 }
2398
2399 auto* map = overworld_.mutable_overworld_map(map_index);
2400 if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) {
2401 return; // No siblings for small areas
2402 }
2403
2404 int parent_id = map->parent();
2405 std::vector<int> siblings;
2406
2407 switch (map->area_size()) {
2409 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
2410 break;
2412 siblings = {parent_id, parent_id + 1};
2413 break;
2415 siblings = {parent_id, parent_id + 8};
2416 break;
2417 default:
2418 return;
2419 }
2420
2421 for (int sibling : siblings) {
2422 if (sibling >= 0 && sibling < 0xA0) {
2423 // Skip self unless include_self is true
2424 if (sibling == map_index && !include_self) {
2425 continue;
2426 }
2427
2428 // Mark as modified FIRST before loading
2429 maps_bmp_[sibling].set_modified(true);
2430
2431 // Load graphics from ROM
2432 overworld_.mutable_overworld_map(sibling)->LoadAreaGraphics();
2433
2434 // CRITICAL FIX: Bypass visibility check - force immediate refresh
2435 // Call RefreshChildMapOnDemand() directly instead of RefreshOverworldMapOnDemand()
2436 RefreshChildMapOnDemand(sibling);
2437
2438 LOG_DEBUG("OverworldEditor",
2439 "RefreshSiblingMapGraphics: Refreshed sibling map %d", sibling);
2440 }
2441 }
2442}
2443
2445 const auto& current_ow_map = *overworld_.mutable_overworld_map(current_map_);
2446
2447 // Check if ZSCustomOverworld v3 is present
2448 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
2449 bool use_v3_area_sizes = (asm_version >= 3);
2450
2451 if (use_v3_area_sizes) {
2452 // Use v3 area size system
2454 auto area_size = current_ow_map.area_size();
2455
2456 if (area_size != AreaSizeEnum::SmallArea) {
2457 // Get all sibling maps that need property updates
2458 std::vector<int> sibling_maps;
2459 int parent_id = current_ow_map.parent();
2460
2461 switch (area_size) {
2462 case AreaSizeEnum::LargeArea:
2463 // 2x2 grid: parent+1, parent+8, parent+9 (skip parent itself)
2464 sibling_maps = {parent_id + 1, parent_id + 8, parent_id + 9};
2465 break;
2466 case AreaSizeEnum::WideArea:
2467 // 2x1 grid: parent+1 (skip parent itself)
2468 sibling_maps = {parent_id + 1};
2469 break;
2470 case AreaSizeEnum::TallArea:
2471 // 1x2 grid: parent+8 (skip parent itself)
2472 sibling_maps = {parent_id + 8};
2473 break;
2474 default:
2475 break;
2476 }
2477
2478 // Copy properties from parent map to all siblings
2479 for (int sibling_index : sibling_maps) {
2480 if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
2481 continue;
2482 }
2483 auto& map = *overworld_.mutable_overworld_map(sibling_index);
2484 map.set_area_graphics(current_ow_map.area_graphics());
2485 map.set_area_palette(current_ow_map.area_palette());
2486 map.set_sprite_graphics(game_state_,
2487 current_ow_map.sprite_graphics(game_state_));
2488 map.set_sprite_palette(game_state_,
2489 current_ow_map.sprite_palette(game_state_));
2490 map.set_message_id(current_ow_map.message_id());
2491
2492 // CRITICAL FIX: Reload graphics after changing properties
2493 map.LoadAreaGraphics();
2494 }
2495 }
2496 } else {
2497 // Legacy logic for vanilla and v2 ROMs
2498 if (current_ow_map.is_large_map()) {
2499 // We need to copy the properties from the parent map to the children
2500 for (int i = 1; i < 4; i++) {
2501 int sibling_index = current_ow_map.parent() + i;
2502 if (i >= 2) {
2503 sibling_index += 6;
2504 }
2505 auto& map = *overworld_.mutable_overworld_map(sibling_index);
2506 map.set_area_graphics(current_ow_map.area_graphics());
2507 map.set_area_palette(current_ow_map.area_palette());
2508 map.set_sprite_graphics(game_state_,
2509 current_ow_map.sprite_graphics(game_state_));
2510 map.set_sprite_palette(game_state_,
2511 current_ow_map.sprite_palette(game_state_));
2512 map.set_message_id(current_ow_map.message_id());
2513
2514 // CRITICAL FIX: Reload graphics after changing properties
2515 map.LoadAreaGraphics();
2516 }
2517 }
2518 }
2519}
2520
2522 LOG_DEBUG("OverworldEditor", "RefreshTile16Blockset called");
2523 if (current_blockset_ ==
2524 overworld_.overworld_map(current_map_)->area_graphics()) {
2525 return absl::OkStatus();
2526 }
2528
2531
2532 const auto tile16_data = overworld_.tile16_blockset_data();
2533
2536
2537 // Queue texture update for the atlas
2541 } else if (!tile16_blockset_.atlas.texture() &&
2543 // Create texture if it doesn't exist yet
2546 }
2547
2548 return absl::OkStatus();
2549}
2550
2552 // Handle middle-click for map interaction instead of right-click
2553 if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle) &&
2554 ImGui::IsItemHovered()) {
2555 // Get the current map from mouse position
2556 auto mouse_position = ow_map_canvas_.drawn_tile_position();
2557 int map_x = mouse_position.x / kOverworldMapSize;
2558 int map_y = mouse_position.y / kOverworldMapSize;
2559 int hovered_map = map_x + map_y * 8;
2560 if (current_world_ == 1) {
2561 hovered_map += 0x40;
2562 } else if (current_world_ == 2) {
2563 hovered_map += 0x80;
2564 }
2565
2566 // Only interact if we're hovering over a valid map
2567 if (hovered_map >= 0 && hovered_map < 0xA0) {
2568 // Toggle map lock or open properties panel
2569 if (current_map_lock_ && current_map_ == hovered_map) {
2570 current_map_lock_ = false;
2571 } else {
2572 current_map_lock_ = true;
2573 current_map_ = hovered_map;
2575 }
2576 }
2577 }
2578
2579 // Handle double-click to open properties panel
2580 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) &&
2581 ImGui::IsItemHovered()) {
2583 }
2584}
2585
2586// Note: SetupOverworldCanvasContextMenu has been removed (Phase 3B).
2587// Context menu is now setup dynamically in DrawOverworldCanvas() via
2588// MapPropertiesSystem::SetupCanvasContextMenu() for context-aware menu items.
2589
2591 if (blockset_selector_) {
2592 blockset_selector_->ScrollToTile(current_tile16_);
2593 return;
2594 }
2595
2596 // CRITICAL FIX: Do NOT use fallback scrolling from overworld canvas context!
2597 // The fallback code uses ImGui::SetScrollX/Y which scrolls the CURRENT window,
2598 // and when called from CheckForSelectRectangle() during overworld canvas rendering,
2599 // it incorrectly scrolls the overworld canvas instead of the tile16 selector.
2600 //
2601 // The blockset_selector_ should always be available in modern code paths.
2602 // If it's not available, we skip scrolling rather than scroll the wrong window.
2603 //
2604 // This fixes the bug where right-clicking to select tiles on the Dark World
2605 // causes the overworld canvas to scroll unexpectedly.
2606}
2607
2609 static bool init_properties = false;
2610
2611 if (!init_properties) {
2612 for (int i = 0; i < 0x40; i++) {
2613 std::string area_graphics_str = absl::StrFormat(
2614 "%02hX", overworld_.overworld_map(i)->area_graphics());
2616 ->push_back(area_graphics_str);
2617
2618 area_graphics_str = absl::StrFormat(
2619 "%02hX", overworld_.overworld_map(i + 0x40)->area_graphics());
2621 ->push_back(area_graphics_str);
2622
2623 std::string area_palette_str =
2624 absl::StrFormat("%02hX", overworld_.overworld_map(i)->area_palette());
2626 ->push_back(area_palette_str);
2627
2628 area_palette_str = absl::StrFormat(
2629 "%02hX", overworld_.overworld_map(i + 0x40)->area_palette());
2631 ->push_back(area_palette_str);
2632 std::string sprite_gfx_str = absl::StrFormat(
2633 "%02hX", overworld_.overworld_map(i)->sprite_graphics(1));
2635 ->push_back(sprite_gfx_str);
2636
2637 sprite_gfx_str = absl::StrFormat(
2638 "%02hX", overworld_.overworld_map(i)->sprite_graphics(2));
2640 ->push_back(sprite_gfx_str);
2641
2642 sprite_gfx_str = absl::StrFormat(
2643 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_graphics(1));
2645 ->push_back(sprite_gfx_str);
2646
2647 sprite_gfx_str = absl::StrFormat(
2648 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_graphics(2));
2650 ->push_back(sprite_gfx_str);
2651
2652 std::string sprite_palette_str = absl::StrFormat(
2653 "%02hX", overworld_.overworld_map(i)->sprite_palette(1));
2655 ->push_back(sprite_palette_str);
2656
2657 sprite_palette_str = absl::StrFormat(
2658 "%02hX", overworld_.overworld_map(i)->sprite_palette(2));
2660 ->push_back(sprite_palette_str);
2661
2662 sprite_palette_str = absl::StrFormat(
2663 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_palette(1));
2665 ->push_back(sprite_palette_str);
2666
2667 sprite_palette_str = absl::StrFormat(
2668 "%02hX", overworld_.overworld_map(i + 0x40)->sprite_palette(2));
2670 ->push_back(sprite_palette_str);
2671 }
2672 init_properties = true;
2673 }
2674
2675 ImGui::Text("Area Gfx LW/DW");
2676 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2678 ImGui::SameLine();
2679 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2681 ImGui::Separator();
2682
2683 ImGui::Text("Sprite Gfx LW/DW");
2684 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2686 ImGui::SameLine();
2687 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2689 ImGui::SameLine();
2690 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2692 ImGui::SameLine();
2693 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2695 ImGui::Separator();
2696
2697 ImGui::Text("Area Pal LW/DW");
2698 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2700 ImGui::SameLine();
2701 properties_canvas_.UpdateInfoGrid(ImVec2(256, 256), 32,
2703
2704 static bool show_gfx_group = false;
2705 ImGui::Checkbox("Show Gfx Group Editor", &show_gfx_group);
2706 if (show_gfx_group) {
2707 gui::BeginWindowWithDisplaySettings("Gfx Group Editor", &show_gfx_group);
2710 }
2711}
2712
2714 if (ImGui::BeginTable(
2715 "UsageStatsTable", 3,
2716 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter,
2717 ImVec2(0, 0))) {
2718 ImGui::TableSetupColumn("Entrances");
2719 ImGui::TableSetupColumn("Grid", ImGuiTableColumnFlags_WidthStretch,
2720 ImGui::GetContentRegionAvail().x);
2721 ImGui::TableSetupColumn("Usage", ImGuiTableColumnFlags_WidthFixed, 256);
2722 ImGui::TableHeadersRow();
2723 ImGui::TableNextRow();
2724
2725 ImGui::TableNextColumn();
2726 if (ImGui::BeginChild("UnusedSpritesetScroll", ImVec2(0, 0), true,
2727 ImGuiWindowFlags_HorizontalScrollbar)) {
2728 for (int i = 0; i < 0x81; i++) {
2729 auto entrance_name = rom_->resource_label()->CreateOrGetLabel(
2730 "Dungeon Entrance Names", util::HexByte(i),
2732 std::string str = absl::StrFormat("%#x - %s", i, entrance_name);
2733 if (ImGui::Selectable(str.c_str(), selected_entrance_ == i,
2734 overworld_.entrances().at(i).deleted
2735 ? ImGuiSelectableFlags_Disabled
2736 : 0)) {
2738 selected_usage_map_ = overworld_.entrances().at(i).map_id_;
2740 }
2741 if (ImGui::IsItemHovered()) {
2742 ImGui::BeginTooltip();
2743 ImGui::Text("Entrance ID: %d", i);
2744 ImGui::Text("Map ID: %d", overworld_.entrances().at(i).map_id_);
2745 ImGui::Text("Entrance ID: %d",
2746 overworld_.entrances().at(i).entrance_id_);
2747 ImGui::Text("X: %d", overworld_.entrances().at(i).x_);
2748 ImGui::Text("Y: %d", overworld_.entrances().at(i).y_);
2749 ImGui::Text("Deleted? %s",
2750 overworld_.entrances().at(i).deleted ? "Yes" : "No");
2751 ImGui::EndTooltip();
2752 }
2753 }
2754 ImGui::EndChild();
2755 }
2756
2757 ImGui::TableNextColumn();
2758 DrawUsageGrid();
2759
2760 ImGui::TableNextColumn();
2762
2763 ImGui::EndTable();
2764 }
2765 return absl::OkStatus();
2766}
2767
2769 // Create a grid of 8x8 squares
2770 int total_squares = 128;
2771 int squares_wide = 8;
2772 int squares_tall = (total_squares + squares_wide - 1) /
2773 squares_wide; // Ceiling of total_squares/squares_wide
2774
2775 // Loop through each row
2776 for (int row = 0; row < squares_tall; ++row) {
2777 ImGui::NewLine();
2778
2779 for (int col = 0; col < squares_wide; ++col) {
2780 if (row * squares_wide + col >= total_squares) {
2781 break;
2782 }
2783 // Determine if this square should be highlighted
2784 bool highlight = selected_usage_map_ == (row * squares_wide + col);
2785
2786 // Set highlight color if needed
2787 if (highlight) {
2788 ImGui::PushStyleColor(ImGuiCol_Button, gui::GetSelectedColor());
2789 }
2790
2791 // Create a button or selectable for each square
2792 if (ImGui::Button("##square", ImVec2(20, 20))) {
2793 // Switch over to the room editor tab
2794 // and add a room tab by the ID of the square
2795 // that was clicked
2796 }
2797
2798 // Reset style if it was highlighted
2799 if (highlight) {
2800 ImGui::PopStyleColor();
2801 }
2802
2803 // Check if the square is hovered
2804 if (ImGui::IsItemHovered()) {
2805 // Display a tooltip with all the room properties
2806 }
2807
2808 // Keep squares in the same line
2809 ImGui::SameLine();
2810 }
2811 }
2812}
2813
2815 ImGui::Text("Current Map: %d", current_map_);
2816 ImGui::Text("Current Tile16: %d", current_tile16_);
2817 int relative_x = (int)ow_map_canvas_.drawn_tile_position().x % 512;
2818 int relative_y = (int)ow_map_canvas_.drawn_tile_position().y % 512;
2819 ImGui::Text("Current Tile16 Drawn Position (Relative): %d, %d", relative_x,
2820 relative_y);
2821
2822 // Print the size of the overworld map_tiles per world
2823 ImGui::Text("Light World Map Tiles: %d",
2824 (int)overworld_.mutable_map_tiles()->light_world.size());
2825 ImGui::Text("Dark World Map Tiles: %d",
2826 (int)overworld_.mutable_map_tiles()->dark_world.size());
2827 ImGui::Text("Special World Map Tiles: %d",
2828 (int)overworld_.mutable_map_tiles()->special_world.size());
2829
2830 static bool view_lw_map_tiles = false;
2831 static MemoryEditor mem_edit;
2832 // Let's create buttons which let me view containers in the memory editor
2833 if (ImGui::Button("View Light World Map Tiles")) {
2834 view_lw_map_tiles = !view_lw_map_tiles;
2835 }
2836
2837 if (view_lw_map_tiles) {
2838 mem_edit.DrawContents(
2839 overworld_.mutable_map_tiles()->light_world[current_map_].data(),
2840 overworld_.mutable_map_tiles()->light_world[current_map_].size());
2841 }
2842}
2843
2846 current_graphics_set_.clear();
2847 all_gfx_loaded_ = false;
2848 map_blockset_loaded_ = false;
2849 return absl::OkStatus();
2850}
2851
2852absl::Status OverworldEditor::ApplyZSCustomOverworldASM(int target_version) {
2853 // Feature flag deprecated - ROM version gating is sufficient
2854 // User explicitly clicked upgrade button, so respect their request
2855
2856 // Validate target version
2857 if (target_version < 2 || target_version > 3) {
2858 return absl::InvalidArgumentError(absl::StrFormat(
2859 "Invalid target version: %d. Must be 2 or 3.", target_version));
2860 }
2861
2862 // Check current ROM version
2863 uint8_t current_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
2864 if (current_version != 0xFF && current_version >= target_version) {
2865 return absl::AlreadyExistsError(absl::StrFormat(
2866 "ROM is already version %d or higher", current_version));
2867 }
2868
2869 LOG_DEBUG("OverworldEditor", "Applying ZSCustomOverworld ASM v%d to ROM...",
2870 target_version);
2871
2872 // Initialize Asar wrapper
2873 auto asar_wrapper = std::make_unique<core::AsarWrapper>();
2874 RETURN_IF_ERROR(asar_wrapper->Initialize());
2875
2876 // Create backup of ROM data
2877 std::vector<uint8_t> original_rom_data = rom_->vector();
2878 std::vector<uint8_t> working_rom_data = original_rom_data;
2879
2880 try {
2881 // Determine which ASM file to apply and use GetResourcePath for proper resolution
2882 std::string asm_file_name =
2883 (target_version == 3) ? "asm/yaze.asm" // Master file with v3
2884 : "asm/ZSCustomOverworld.asm"; // v2 standalone
2885
2886 // Use GetResourcePath to handle app bundles and various deployment scenarios
2887 std::string asm_file_path = util::GetResourcePath(asm_file_name);
2888
2889 LOG_DEBUG("OverworldEditor", "Using ASM file: %s", asm_file_path.c_str());
2890
2891 // Verify file exists
2892 if (!std::filesystem::exists(asm_file_path)) {
2893 return absl::NotFoundError(
2894 absl::StrFormat("ASM file not found at: %s\n\n"
2895 "Expected location: assets/%s\n"
2896 "Make sure the assets directory is accessible.",
2897 asm_file_path, asm_file_name));
2898 }
2899
2900 // Apply the ASM patch
2901 auto patch_result =
2902 asar_wrapper->ApplyPatch(asm_file_path, working_rom_data);
2903 if (!patch_result.ok()) {
2904 return absl::InternalError(absl::StrFormat(
2905 "Failed to apply ASM patch: %s", patch_result.status().message()));
2906 }
2907
2908 const auto& result = patch_result.value();
2909 if (!result.success) {
2910 std::string error_details = "ASM patch failed with errors:\n";
2911 for (const auto& error : result.errors) {
2912 error_details += " - " + error + "\n";
2913 }
2914 if (!result.warnings.empty()) {
2915 error_details += "Warnings:\n";
2916 for (const auto& warning : result.warnings) {
2917 error_details += " - " + warning + "\n";
2918 }
2919 }
2920 return absl::InternalError(error_details);
2921 }
2922
2923 // Update ROM with patched data
2924 RETURN_IF_ERROR(rom_->LoadFromData(working_rom_data, false));
2925
2926 // Update version marker and feature flags
2928
2929 // Log symbols found during patching
2930 LOG_DEBUG("OverworldEditor",
2931 "ASM patch applied successfully. Found %zu symbols:",
2932 result.symbols.size());
2933 for (const auto& symbol : result.symbols) {
2934 LOG_DEBUG("OverworldEditor", " %s @ $%06X", symbol.name.c_str(),
2935 symbol.address);
2936 }
2937
2938 // Refresh overworld data to reflect changes
2940
2941 LOG_DEBUG("OverworldEditor",
2942 "ZSCustomOverworld v%d successfully applied to ROM",
2943 target_version);
2944 return absl::OkStatus();
2945
2946 } catch (const std::exception& e) {
2947 // Restore original ROM data on any exception
2948 auto restore_result = rom_->LoadFromData(original_rom_data, false);
2949 if (!restore_result.ok()) {
2950 LOG_ERROR("OverworldEditor", "Failed to restore ROM data: %s",
2951 restore_result.message().data());
2952 }
2953 return absl::InternalError(
2954 absl::StrFormat("Exception during ASM application: %s", e.what()));
2955 }
2956}
2957
2958absl::Status OverworldEditor::UpdateROMVersionMarkers(int target_version) {
2959 // Set the main version marker
2961 static_cast<uint8_t>(target_version);
2962
2963 // Enable feature flags based on target version
2964 if (target_version >= 2) {
2965 // v2+ features
2968
2969 LOG_DEBUG("OverworldEditor",
2970 "Enabled v2+ features: Custom BG colors, Main palettes");
2971 }
2972
2973 if (target_version >= 3) {
2974 // v3 features
2979
2980 LOG_DEBUG(
2981 "OverworldEditor",
2982 "Enabled v3+ features: Subscreen overlays, Animated GFX, Tile GFX "
2983 "groups, Mosaic");
2984
2985 // Initialize area size data for v3 (set all areas to small by default)
2986 for (int i = 0; i < 0xA0; i++) {
2987 (*rom_)[zelda3::kOverworldScreenSize + i] =
2988 static_cast<uint8_t>(zelda3::AreaSizeEnum::SmallArea);
2989 }
2990
2991 // Set appropriate sizes for known large areas
2992 const std::vector<int> large_areas = {
2993 0x00, 0x02, 0x05, 0x07, 0x0A, 0x0B, 0x0F, 0x10, 0x11, 0x12,
2994 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D,
2995 0x1E, 0x25, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x30,
2996 0x32, 0x33, 0x34, 0x35, 0x37, 0x3A, 0x3B, 0x3C, 0x3F};
2997
2998 for (int area_id : large_areas) {
2999 if (area_id < 0xA0) {
3000 (*rom_)[zelda3::kOverworldScreenSize + area_id] =
3001 static_cast<uint8_t>(zelda3::AreaSizeEnum::LargeArea);
3002 }
3003 }
3004
3005 LOG_DEBUG("OverworldEditor", "Initialized area size data for %zu areas",
3006 large_areas.size());
3007 }
3008
3009 LOG_DEBUG("OverworldEditor", "ROM version markers updated to v%d",
3010 target_version);
3011 return absl::OkStatus();
3012}
3013
3015 if (!blockset_selector_) {
3016 return;
3017 }
3018
3020 blockset_selector_->SetSelectedTile(current_tile16_);
3021}
3022
3023// ============================================================================
3024// Canvas Automation API Integration (Phase 4)
3025// ============================================================================
3026
3028 auto* api = ow_map_canvas_.GetAutomationAPI();
3029
3030 // Set tile paint callback
3031 api->SetTilePaintCallback([this](int x, int y, int tile_id) {
3032 return AutomationSetTile(x, y, tile_id);
3033 });
3034
3035 // Set tile query callback
3036 api->SetTileQueryCallback(
3037 [this](int x, int y) { return AutomationGetTile(x, y); });
3038}
3039
3040bool OverworldEditor::AutomationSetTile(int x, int y, int tile_id) {
3041 if (!overworld_.is_loaded()) {
3042 return false;
3043 }
3044
3045 // Bounds check
3046 if (x < 0 || y < 0 || x >= 512 || y >= 512) {
3047 return false;
3048 }
3049
3050 // Set current world based on current_map_
3053
3054 // Set the tile in the overworld data structure
3055 overworld_.SetTile(x, y, static_cast<uint16_t>(tile_id));
3056
3057 // Update the bitmap
3058 auto tile_data = gfx::GetTilemapData(tile16_blockset_, tile_id);
3059 if (!tile_data.empty()) {
3061 ImVec2(static_cast<float>(x * 16), static_cast<float>(y * 16)),
3062 tile_data);
3063 return true;
3064 }
3065
3066 return false;
3067}
3068
3070 if (!overworld_.is_loaded()) {
3071 return -1;
3072 }
3073
3074 // Bounds check
3075 if (x < 0 || y < 0 || x >= 512 || y >= 512) {
3076 return -1;
3077 }
3078
3079 // Set current world
3082
3083 return overworld_.GetTile(x, y);
3084}
3085
3086void OverworldEditor::HandleEntityInsertion(const std::string& entity_type) {
3087 if (!overworld_.is_loaded()) {
3088 LOG_ERROR("OverworldEditor", "Cannot insert entity: overworld not loaded");
3089 return;
3090 }
3091
3092 // Get mouse position from canvas (in world coordinates)
3093 ImVec2 mouse_pos = ow_map_canvas_.hover_mouse_pos();
3094
3095 LOG_DEBUG("OverworldEditor",
3096 "HandleEntityInsertion called: type='%s' at pos=(%.0f,%.0f) map=%d",
3097 entity_type.c_str(), mouse_pos.x, mouse_pos.y, current_map_);
3098
3099 if (entity_type == "entrance") {
3100 auto result = InsertEntrance(&overworld_, mouse_pos, current_map_, false);
3101 if (result.ok()) {
3102 current_entrance_ = **result;
3103 current_entity_ = *result;
3104 ImGui::OpenPopup("Entrance Editor");
3105 rom_->set_dirty(true);
3106 LOG_DEBUG("OverworldEditor", "Entrance inserted successfully at map=%d",
3107 current_map_);
3108 } else {
3109 LOG_ERROR("OverworldEditor", "Failed to insert entrance: %s",
3110 result.status().message().data());
3111 }
3112
3113 } else if (entity_type == "hole") {
3114 auto result = InsertEntrance(&overworld_, mouse_pos, current_map_, true);
3115 if (result.ok()) {
3116 current_entrance_ = **result;
3117 current_entity_ = *result;
3118 ImGui::OpenPopup("Entrance Editor");
3119 rom_->set_dirty(true);
3120 LOG_DEBUG("OverworldEditor", "Hole inserted successfully at map=%d",
3121 current_map_);
3122 } else {
3123 LOG_ERROR("OverworldEditor", "Failed to insert hole: %s",
3124 result.status().message().data());
3125 }
3126
3127 } else if (entity_type == "exit") {
3128 auto result = InsertExit(&overworld_, mouse_pos, current_map_);
3129 if (result.ok()) {
3130 current_exit_ = **result;
3131 current_entity_ = *result;
3132 ImGui::OpenPopup("Exit editor");
3133 rom_->set_dirty(true);
3134 LOG_DEBUG("OverworldEditor", "Exit inserted successfully at map=%d",
3135 current_map_);
3136 } else {
3137 LOG_ERROR("OverworldEditor", "Failed to insert exit: %s",
3138 result.status().message().data());
3139 }
3140
3141 } else if (entity_type == "item") {
3142 auto result = InsertItem(&overworld_, mouse_pos, current_map_, 0x00);
3143 if (result.ok()) {
3144 current_item_ = **result;
3145 current_entity_ = *result;
3146 ImGui::OpenPopup("Item editor");
3147 rom_->set_dirty(true);
3148 LOG_DEBUG("OverworldEditor", "Item inserted successfully at map=%d",
3149 current_map_);
3150 } else {
3151 LOG_ERROR("OverworldEditor", "Failed to insert item: %s",
3152 result.status().message().data());
3153 }
3154
3155 } else if (entity_type == "sprite") {
3156 auto result =
3157 InsertSprite(&overworld_, mouse_pos, current_map_, game_state_, 0x00);
3158 if (result.ok()) {
3159 current_sprite_ = **result;
3160 current_entity_ = *result;
3161 ImGui::OpenPopup("Sprite editor");
3162 rom_->set_dirty(true);
3163 LOG_DEBUG("OverworldEditor", "Sprite inserted successfully at map=%d",
3164 current_map_);
3165 } else {
3166 LOG_ERROR("OverworldEditor", "Failed to insert sprite: %s",
3167 result.status().message().data());
3168 }
3169
3170 } else {
3171 LOG_WARN("OverworldEditor", "Unknown entity type: %s", entity_type.c_str());
3172 }
3173}
3174
3175} // namespace yaze::editor
project::ResourceLabelManager * resource_label()
Definition rom.h:223
absl::Status LoadFromData(const std::vector< uint8_t > &data, bool z3_load=true)
Definition rom.cc:384
auto vector() const
Definition rom.h:210
void set_dirty(bool dirty)
Definition rom.h:202
bool is_loaded() const
Definition rom.h:200
static Flags & get()
Definition features.h:82
void RegisterCard(size_t session_id, const CardInfo &base_info)
Register a card for a specific session.
EditorDependencies dependencies_
Definition editor.h:165
std::string MakeCardTitle(const std::string &base_title) const
Definition editor.h:168
std::string MakeCardId(const std::string &base_id) const
Definition editor.h:176
absl::Status Clear() override
std::unique_ptr< MapPropertiesSystem > map_properties_system_
zelda3::OverworldItem current_item_
zelda3::OverworldEntranceTileTypes entrance_tiletypes_
zelda3::OverworldEntrance current_entrance_
absl::Status ApplyZSCustomOverworldASM(int target_version)
Apply ZSCustomOverworld ASM patch to upgrade ROM version.
absl::Status CheckForCurrentMap()
Check for changes to the overworld map. Calls RefreshOverworldMap and RefreshTile16Blockset on the cu...
void ForceRefreshGraphics(int map_index)
std::vector< int > selected_tile16_ids_
void HandleEntityInsertion(const std::string &entity_type)
Handle entity insertion from context menu.
zelda3::GameEntity * dragged_entity_
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > maps_bmp_
void CheckForOverworldEdits()
Check for changes to the overworld map.
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 RenderUpdatedMapBitmap(const ImVec2 &click_position, const std::vector< uint8_t > &tile_data)
void RefreshOverworldMapOnDemand(int map_index)
On-demand map refresh that only updates what's actually needed.
bool AutomationSetTile(int x, int y, int tile_id)
void RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap *map)
Safely refresh multi-area maps without recursion.
zelda3::Overworld & overworld()
void CheckForSelectRectangle()
Draw and create the tile16 IDs that are currently selected.
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_
void ProcessDeferredTextures()
Create textures for deferred map bitmaps on demand.
std::unique_ptr< gui::TileSelectorWidget > blockset_selector_
absl::Status Paste() override
void ScrollBlocksetCanvasToCurrentTile()
Scroll the blockset canvas to show the current selected tile16.
absl::Status LoadGraphics()
Load the Bitmap objects for each OverworldMap.
void RefreshChildMapOnDemand(int map_index)
On-demand child map refresh with selective updates.
zelda3::GameEntity * current_entity_
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status SetCurrentTile(int id)
void set_palette(const gfx::SnesPalette &palette)
void set_on_changes_committed(std::function< absl::Status()> callback)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:32
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:36
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Definition arena.h:78
static Arena & Get()
Definition arena.cc:15
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:66
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
Definition bitmap.cc:475
TextureHandle texture() const
Definition bitmap.h:289
const std::vector< uint8_t > & vector() const
Definition bitmap.h:290
auto size() const
Definition bitmap.h:285
bool is_active() const
Definition bitmap.h:293
void set_modified(bool modified)
Definition bitmap.h:296
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
Definition bitmap.cc:334
SDL_Surface * surface() const
Definition bitmap.h:288
RAII timer for automatic timing management.
void SetTilePaintCallback(TilePaintCallback callback)
void set_scrolling(ImVec2 scroll)
Definition canvas.h:296
auto selected_tile_pos() const
Definition canvas.h:327
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
Definition canvas.cc:1004
auto global_scale() const
Definition canvas.h:329
auto select_rect_active() const
Definition canvas.h:325
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:187
auto selected_tiles() const
Definition canvas.h:326
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f, int local_map_size=0x200, ImVec2 total_map_size=ImVec2(0x1000, 0x1000))
Draw group of bitmaps for multi-tile selection preview.
Definition canvas.cc:1075
CanvasAutomationAPI * GetAutomationAPI()
Definition canvas.cc:1846
auto hover_mouse_pos() const
Definition canvas.h:386
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
Definition canvas.cc:364
void DrawContextMenu()
Definition canvas.cc:428
auto drawn_tile_position() const
Definition canvas.h:297
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
Definition canvas.cc:716
auto draw_list() const
Definition canvas.h:293
bool DrawTileSelector(int size, int size_y=0)
Definition canvas.cc:862
auto mutable_labels(int i)
Definition canvas.h:367
auto canvas_size() const
Definition canvas.h:298
void set_selected_tile_pos(ImVec2 pos)
Definition canvas.h:328
void set_global_scale(float scale)
Definition canvas.h:299
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
Definition canvas.cc:890
auto zero_point() const
Definition canvas.h:294
bool IsMouseHovering() const
Definition canvas.h:285
void DrawOutline(int x, int y, int w, int h)
Definition canvas.cc:1060
auto scrolling() const
Definition canvas.h:295
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
Definition canvas.cc:372
const ImVector< ImVec2 > & points() const
Definition canvas.h:290
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
Definition canvas.cc:1304
auto selected_points() const
Definition canvas.h:384
auto set_highlight_tile_id(int i)
Definition canvas.h:380
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
void SetPosition(Position pos)
Ultra-compact toolbar that merges mode buttons with settings.
bool ModeButton(const char *icon, bool selected, const char *tooltip)
bool AddUsageStatsButton(const char *tooltip)
bool AddProperty(const char *icon, const char *label, uint8_t *value, std::function< void()> on_change=nullptr)
bool AddAction(const char *icon, const char *tooltip)
void AddRomBadge(uint8_t version, std::function< void()> on_upgrade=nullptr)
bool AddToggle(const char *icon, bool *state, const char *tooltip)
enum yaze::zelda3::GameEntity::EntityType entity_type_
virtual void UpdateMapProperties(uint16_t map_id)=0
Represents a single Overworld map screen.
auto tile16_blockset_data() const
Definition overworld.h:273
auto current_area_palette() const
Definition overworld.h:267
void set_current_world(int world)
Definition overworld.h:281
int GetTileFromPosition(ImVec2 position) const
Definition overworld.h:223
absl::Status Load(Rom *rom)
Definition overworld.cc:31
absl::Status SaveMapProperties()
absl::Status SaveMap32Tiles()
absl::Status SaveMap16Tiles()
std::vector< gfx::Tile16 > tiles16() const
Definition overworld.h:251
auto is_loaded() const
Definition overworld.h:276
absl::Status CreateTile32Tilemap()
auto overworld_map(int i) const
Definition overworld.h:247
void set_current_map(int i)
Definition overworld.h:280
auto mutable_overworld_map(int i)
Definition overworld.h:248
absl::Status SaveEntrances()
absl::Status SaveExits()
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
Definition overworld.cc:660
absl::Status SaveItems()
uint16_t GetTile(int x, int y) const
Definition overworld.h:282
absl::Status SaveOverworldMaps()
Definition overworld.cc:792
void SetTile(int x, int y, uint16_t tile_id)
Definition overworld.h:291
auto mutable_sprites(int state)
Definition overworld.h:255
const std::vector< OverworldEntrance > & entrances() const
Definition overworld.h:259
auto current_map_bitmap_data() const
Definition overworld.h:270
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:233
absl::Status SaveMusic()
A class for managing sprites in the overworld and underworld.
Definition sprite.h:279
#define ICON_MD_GRID_VIEW
Definition icons.h:895
#define ICON_MD_SETTINGS
Definition icons.h:1697
#define ICON_MD_CANCEL
Definition icons.h:362
#define ICON_MD_UPGRADE
Definition icons.h:2045
#define ICON_MD_CHECK
Definition icons.h:395
#define ICON_MD_COLLECTIONS
Definition icons.h:436
#define ICON_MD_DRAW
Definition icons.h:623
#define ICON_MD_BRUSH
Definition icons.h:323
#define ICON_MD_TUNE
Definition icons.h:2020
#define ICON_MD_ZOOM_OUT
Definition icons.h:2194
#define ICON_MD_OPEN_IN_FULL
Definition icons.h:1351
#define ICON_MD_MAP
Definition icons.h:1171
#define ICON_MD_GRID_3X3
Definition icons.h:890
#define ICON_MD_GRASS
Definition icons.h:889
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:828
#define ICON_MD_DOOR_BACK
Definition icons.h:610
#define ICON_MD_PUBLIC
Definition icons.h:1522
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1262
#define ICON_MD_GRID_ON
Definition icons.h:894
#define ICON_MD_LAYERS
Definition icons.h:1066
#define ICON_MD_DOOR_FRONT
Definition icons.h:611
#define ICON_MD_IMAGE
Definition icons.h:980
#define ICON_MD_ADD_LOCATION
Definition icons.h:98
#define ICON_MD_ZOOM_IN
Definition icons.h:2192
#define ICON_MD_GRID_4X4
Definition icons.h:891
#define ICON_MD_MOUSE
Definition icons.h:1249
#define ICON_MD_FOLDER
Definition icons.h:807
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_PEST_CONTROL_RODENT
Definition icons.h:1428
#define ICON_MD_ANALYTICS
Definition icons.h:152
#define LOG_DEBUG(category, format,...)
Definition log.h:104
#define LOG_ERROR(category, format,...)
Definition log.h:110
#define LOG_WARN(category, format,...)
Definition log.h:108
#define PRINT_IF_ERROR(expression)
Definition macro.h:27
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
ImVec2 ClampScrollPosition(ImVec2 scroll, ImVec2 content_size, ImVec2 visible_size)
Editors are the view controllers for the application.
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement)
Definition entity.cc:36
absl::StatusOr< zelda3::OverworldItem * > InsertItem(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map, uint8_t item_id)
Insert a new item at the specified position.
absl::StatusOr< zelda3::OverworldEntrance * > InsertEntrance(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map, bool is_hole)
Flat helper functions for entity insertion/manipulation.
constexpr unsigned int kOverworldMapSize
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
Definition entity.cc:436
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
Definition entity.cc:82
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
Definition entity.cc:315
absl::StatusOr< zelda3::OverworldExit * > InsertExit(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map)
Insert a new exit at the specified position.
constexpr int kTile16Size
absl::StatusOr< zelda3::Sprite * > InsertSprite(zelda3::Overworld *overworld, ImVec2 mouse_pos, int current_map, int game_state, uint8_t sprite_id)
Insert a new sprite at the specified position.
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
Definition entity.cc:153
void UpdateTilemap(IRenderer *renderer, Tilemap &tilemap, const std::vector< uint8_t > &data)
Definition tilemap.cc:32
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Definition tilemap.cc:235
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
void BeginPadding(int i)
Definition style.cc:272
void BeginChildBothScrollbars(int id)
Definition style.cc:309
ImVec4 GetSelectedColor()
Definition ui_helpers.cc:67
void EndNoPadding()
Definition style.cc:282
void CenterText(const char *text)
void EndPadding()
Definition style.cc:276
void BeginNoPadding()
Definition style.cc:278
void EndWindowWithDisplaySettings()
Definition style.cc:267
void BeginWindowWithDisplaySettings(const char *id, bool *active, const ImVec2 &size, ImGuiWindowFlags flags)
Definition style.cc:247
void BeginChildWithScrollbar(const char *str_id)
Definition style.cc:284
std::string HexByte(uint8_t byte, HexStringParams params)
Definition hex.cc:30
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:69
constexpr int kNumTile16Individual
Definition overworld.h:113
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
Definition common.h:46
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:50
constexpr int kDarkWorldMapIdStart
absl::StatusOr< OverworldEntranceTileTypes > LoadEntranceTileTypes(Rom *rom)
constexpr int OverworldCustomMosaicEnabled
constexpr const char * kEntranceNames[]
Definition common.h:52
constexpr int OverworldCustomSubscreenOverlayEnabled
#define IM_PI
SharedClipboard * shared_clipboard
Definition editor.h:84
EditorCardRegistry * card_registry
Definition editor.h:80
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:110
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
Definition project.cc:874