yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_editor.cc
Go to the documentation of this file.
1// Related header
3
4#ifndef IM_PI
5#define IM_PI 3.14159265358979323846f
6#endif
7
8// C system headers
9#include <cmath>
10#include <cstddef>
11#include <cstdint>
12
13// C++ standard library headers
14#include <algorithm>
15#include <exception>
16#include <filesystem>
17#include <iostream>
18#include <memory>
19#include <new>
20#include <ostream>
21#include <string>
22#include <unordered_map>
23#include <utility>
24#include <vector>
25
26// Third-party library headers
27#include "absl/status/status.h"
28#include "absl/strings/str_format.h"
29#include "imgui/imgui.h"
30
31// Project headers
43// Note: All overworld panels now self-register via REGISTER_PANEL macro:
44// AreaGraphicsPanel, DebugWindowPanel, GfxGroupsPanel, MapPropertiesPanel,
45// OverworldCanvasPanel, ScratchSpacePanel, Tile16EditorPanel, Tile16SelectorPanel,
46// Tile8SelectorPanel, UsageStatisticsPanel, V3SettingsPanel, OverworldItemListPanel
54#include "app/gfx/core/bitmap.h"
64#include "app/gui/core/icons.h"
66#include "app/gui/core/style.h"
70#include "core/asar_wrapper.h"
71#include "core/features.h"
72#include "core/project.h"
73#include "rom/rom.h"
74#include "util/file_util.h"
75#include "util/hex.h"
76#include "util/log.h"
77#include "util/macro.h"
78#include "zelda3/common.h"
86
87namespace yaze::editor {
88
89namespace {
90
92 const zelda3::OverworldItem& rhs) {
93 return lhs.id_ == rhs.id_ && lhs.room_map_id_ == rhs.room_map_id_ &&
94 lhs.x_ == rhs.x_ && lhs.y_ == rhs.y_ && lhs.bg2_ == rhs.bg2_;
95}
96
98 const OverworldItemsSnapshot& rhs) {
99 if (lhs.items.size() != rhs.items.size()) {
100 return false;
101 }
102
103 for (size_t i = 0; i < lhs.items.size(); ++i) {
104 if (!ItemIdentityMatchesForUndo(lhs.items[i], rhs.items[i])) {
105 return false;
106 }
107 }
108
109 if (lhs.selected_item_identity.has_value() !=
110 rhs.selected_item_identity.has_value()) {
111 return false;
112 }
113 if (!lhs.selected_item_identity.has_value()) {
114 return true;
115 }
116
119}
120
121} // namespace
122
124 // Initialize renderer from dependencies
126
128 "Gfx Groups: selection syncs with the Graphics editor for this ROM "
129 "session "
130 "(this surface uses its own preview canvases).");
131
132 // Initialize MapRefreshCoordinator (must be before callbacks that use it)
134
135 if (rom_) {
136 upgrade_system_ = std::make_unique<zelda3::OverworldUpgradeSystem>(*rom_);
137 }
139 std::make_unique<EntityMutationService>(overworld_);
140
142
143 // Note: All WindowContent instances now self-register via REGISTER_PANEL macro
144 // and use ContentRegistry::Context to access the current editor.
145 // See comment at include section for the current set of view wrappers.
146
147 // Original initialization code below:
148 // Initialize MapPropertiesSystem with canvas and bitmap data
149 // Initialize cards
150 usage_stats_card_ = std::make_unique<UsageStatisticsCard>(&overworld_);
151 debug_window_card_ = std::make_unique<DebugWindowCard>();
152
153 map_properties_system_ = std::make_unique<MapPropertiesSystem>(
155
156 // Set up refresh callbacks for MapPropertiesSystem
157 map_properties_system_->SetRefreshCallbacks(
158 [this]() { this->RefreshMapProperties(); },
159 [this]() { this->RefreshOverworldMap(); },
160 [this]() -> absl::Status { return this->RefreshMapPalette(); },
161 [this]() -> absl::Status { return this->RefreshTile16Blockset(); },
162 [this](int map_index) { this->ForceRefreshGraphics(map_index); });
163
164 // Initialize OverworldSidebar
165 sidebar_ = std::make_unique<OverworldSidebar>(&overworld_, rom_,
167
168 // Initialize OverworldEntityRenderer for entity visualization
169 entity_renderer_ = std::make_unique<OverworldEntityRenderer>(
171
172 // Initialize Toolbar
173 toolbar_ = std::make_unique<OverworldToolbar>();
174 toolbar_->on_refresh_graphics = [this]() {
175 // Invalidate cached graphics for the current map area to force re-render
176 // with potentially new palette/graphics settings
179 };
180 toolbar_->on_refresh_map = [this]() {
182 };
183
184 toolbar_->on_save_to_scratch = [this]() {
186 };
187 toolbar_->on_load_from_scratch = [this]() {
189 };
190 toolbar_->on_upgrade_rom_version = [this](int) {
191 ImGui::OpenPopup("UpgradeROMVersion");
192 };
193
194 // Initialize OverworldCanvasRenderer for canvas and panel drawing
195 canvas_renderer_ = std::make_unique<OverworldCanvasRenderer>(this);
196
200}
201
204 sink.on_set_editor_mode = [this](EditingMode mode) {
205 current_mode = mode;
208 } else {
210 }
211 };
212 sink.on_set_entity_mode = [this](EntityEditMode mode) {
213 entity_edit_mode_ = mode;
216 };
217 sink.on_toggle_brush = [this]() {
219 };
220 sink.on_activate_fill = [this]() {
222 };
223 sink.on_pick_tile_from_hover = [this]() {
225 };
226 sink.on_duplicate_selected = [this]() {
227 (void)DuplicateSelectedItem();
228 };
229 sink.on_nudge_selected = [this](int delta_x, int delta_y, bool shift_held) {
230 const int step = shift_held ? 16 : 1;
231 (void)this->NudgeSelectedItem(delta_x * step, delta_y * step);
232 };
233 sink.on_entity_context_menu = [this](zelda3::GameEntity* entity) {
235 auto* workbench = static_cast<OverworldEntityWorkbench*>(
237 "overworld.entity_workbench"));
238 if (workbench) {
239 workbench->OpenContextMenuFor(entity);
240 }
241 }
242 };
243 sink.on_entity_double_click = [this](zelda3::GameEntity* entity) {
244 if (!entity) {
245 return;
246 }
247 switch (entity->entity_type_) {
249 this->jump_to_tab_ =
250 static_cast<zelda3::OverworldExit*>(entity)->room_id_;
251 break;
253 this->jump_to_tab_ =
254 static_cast<zelda3::OverworldEntrance*>(entity)->entrance_id_;
255 break;
256 default:
257 break;
258 }
259 };
260 sink.on_toggle_item_list = [this]() {
262 return;
263 }
264 const size_t session_id =
268 };
269 sink.can_edit_items = [this]() {
271 };
272 sink.on_toggle_lock = [this]() {
274 };
275 sink.on_toggle_tile16_editor = [this]() {
277 return;
278 }
279 const size_t session_id =
283 };
284 sink.on_toggle_fullscreen = [this]() {
286 };
287 sink.on_undo = [this]() {
288 status_ = Undo();
289 };
290 sink.on_redo = [this]() {
291 status_ = Redo();
292 };
294 std::make_unique<OverworldInteractionCoordinator>(std::move(sink));
295}
296
300 deps.overworld = &overworld_;
301 deps.maps_bmp = &maps_bmp_;
309 deps.game_state = &game_state_;
310 deps.rom = rom_;
312
313 TilePaintingCallbacks callbacks;
314 callbacks.create_undo_point = [this](int map_id, int world, int x, int y,
315 int old_tile_id) {
316 this->CreateUndoPoint(map_id, world, x, y, old_tile_id);
317 };
318 callbacks.finalize_paint_operation = [this]() {
320 };
321 callbacks.refresh_overworld_map = [this]() {
322 this->RefreshOverworldMap();
323 };
324 callbacks.refresh_overworld_map_on_demand = [this](int map_index) {
325 this->RefreshOverworldMapOnDemand(map_index);
326 };
327 callbacks.scroll_blockset_to_current_tile = [this]() {
329 };
330 callbacks.request_tile16_selection = [this](int tile_id) {
331 this->RequestTile16Selection(tile_id);
332 };
333
334 tile_painting_ = std::make_unique<TilePaintingManager>(deps, callbacks);
335}
336
340 ctx.overworld = &overworld_;
341 ctx.rom = rom_;
350 ctx.maps_bmp = &maps_bmp_;
353
355 callbacks.refresh_overworld_map = [this]() {
356 this->RefreshOverworldMap();
357 };
358 callbacks.refresh_tile16_blockset = [this]() -> absl::Status {
359 return this->RefreshTile16Blockset();
360 };
361 callbacks.ensure_map_texture = [this](int map_index) {
362 this->EnsureMapTexture(map_index);
363 };
364 callbacks.pick_tile16_from_hovered_canvas = [this]() -> bool {
365 return this->PickTile16FromHoveredCanvas();
366 };
367 callbacks.is_entity_hovered = [this]() -> bool {
368 return entity_renderer_ && entity_renderer_->hovered_entity() != nullptr;
369 };
370
371 canvas_nav_ = std::make_unique<CanvasNavigationManager>();
372 canvas_nav_->Initialize(ctx, callbacks);
373}
374
376 current_entity_ = entity;
377 if (!entity) {
379 return;
380 }
382 selected_item_identity_ = *static_cast<zelda3::OverworldItem*>(entity);
383 } else {
385 }
386}
387
393
394absl::Status OverworldEditor::Load() {
395 gfx::ScopedTimer timer("OverworldEditor::Load");
396
397 LOG_DEBUG("OverworldEditor", "Loading overworld.");
398 if (!rom_ || !rom_->is_loaded()) {
399 return absl::FailedPreconditionError("ROM not loaded");
400 }
401
403 dependencies_.window_manager->RegisterPanelAlias("overworld.debug_window",
404 "overworld.debug");
405 }
406
407 // Clear undo/redo state when loading new ROM data
410
415
416 // CRITICAL FIX: Initialize tile16 editor with the correct overworld palette
420 [this](int id) { current_tile16_ = id; });
421
422 // Set up callback for when tile16 changes are committed
423 tile16_editor_.set_on_changes_committed([this]() -> absl::Status {
424 // Regenerate the overworld editor's tile16 blockset
426
427 // Force refresh of the current overworld map to show changes
429
430 LOG_DEBUG("OverworldEditor",
431 "Overworld editor refreshed after Tile16 changes");
432 return absl::OkStatus();
433 });
434
435 // Set up entity insertion callback for MapPropertiesSystem
437 map_properties_system_->SetEntityCallbacks(
438 [this](const std::string& entity_type) {
439 HandleEntityInsertion(entity_type);
440 });
441
442 // Set up tile16 edit callback for context menu in MOUSE mode
443 map_properties_system_->SetTile16EditCallback(
444 [this]() { HandleTile16Edit(); });
445 }
446
448
449 // Register as palette listener to refresh graphics when palettes change
450 if (palette_listener_id_ < 0) {
452 [this](const std::string& group_name, int palette_index) {
453 // Only respond to overworld-related palette changes
454 if (group_name == "ow_main" || group_name == "ow_animated" ||
455 group_name == "ow_aux" || group_name == "grass") {
456 LOG_DEBUG("OverworldEditor",
457 "Palette change detected: %s, refreshing current map",
458 group_name.c_str());
459 // Refresh current map graphics to reflect palette changes
460 if (current_map_ >= 0 && all_gfx_loaded_) {
462 }
463 }
464 });
465 LOG_DEBUG("OverworldEditor", "Registered as palette listener (ID: %d)",
467 }
468
469 all_gfx_loaded_ = true;
470
472 const int pending = *pending_tile16_selection_after_gfx_;
474 RequestTile16Selection(pending);
475 }
476
477 return absl::OkStatus();
478}
479
481 status_ = absl::OkStatus();
482
483 // Safety check: Ensure ROM is loaded and graphics are ready
484 if (!rom_ || !rom_->is_loaded()) {
485 gui::CenterText("No ROM loaded");
486 return absl::OkStatus();
487 }
488
489 if (!all_gfx_loaded_) {
490 gui::CenterText("Loading graphics...");
491 return absl::OkStatus();
492 }
493
494 // Process deferred textures for smooth loading
496
497 // Update blockset atlas with any pending tile16 changes for live preview
498 // Tile cache now uses copy semantics so this is safe to enable
501 }
502
503 // Early return if window_manager is not available
504 // (panels won't be drawn without it, so no point continuing)
506 return status_;
507 }
508
510 return status_;
511 }
512
513 // ===========================================================================
514 // Main Overworld Canvas
515 // ===========================================================================
516 // The panels (Tile16 Selector, Area Graphics, etc.) are now managed by
517 // WindowContent/WorkspaceWindowManager and drawn automatically. This section only
518 // handles the main canvas and toolbar.
519
520 // ===========================================================================
521 // Non-Panel Windows (not managed by WindowContent system)
522 // ===========================================================================
523 // These are separate feature windows, not part of the panel system
524
525 // Custom Background Color Editor
527 ImGui::SetNextWindowSize(ImVec2(400, 500), ImGuiCond_FirstUseEver);
528 if (ImGui::Begin(ICON_MD_FORMAT_COLOR_FILL " Background Color",
530 if (rom_->is_loaded() && overworld_.is_loaded() &&
532 map_properties_system_->DrawCustomBackgroundColorEditor(
534 }
535 }
536 ImGui::End();
537 }
538
539 // Visual Effects Editor (Subscreen Overlays)
541 ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_FirstUseEver);
542 if (ImGui::Begin(ICON_MD_LAYERS " Visual Effects Editor###OverlayEditor",
544 if (rom_->is_loaded() && overworld_.is_loaded() &&
546 map_properties_system_->DrawOverlayEditor(current_map_,
548 }
549 }
550 ImGui::End();
551 }
552
553 // Note: Tile16 Editor is now managed as an WindowContent (Tile16EditorPanel)
554 // It uses UpdateAsPanel() which provides a context menu instead of MenuBar
555
556 if (auto* workbench = GetWorkbench()) {
557 workbench->ProcessPendingInsertion(entity_mutation_service_.get(),
559 workbench->DrawPopups();
560 }
561
562 if (ImGui::BeginPopupModal("UpgradeROMVersion", nullptr,
563 ImGuiWindowFlags_AlwaysAutoResize)) {
564 ImGui::Text(ICON_MD_UPGRADE " Upgrade ROM to ZSCustomOverworld");
565 ImGui::Separator();
566 ImGui::TextWrapped(
567 "This will apply the ZSCustomOverworld ASM patch to your ROM,\n"
568 "enabling advanced overworld features.");
569 ImGui::Separator();
570
571 const uint8_t current_version =
573 : 0xFF;
574 ImGui::Text("Current Version: %s",
575 current_version == 0xFF
576 ? "Vanilla"
577 : absl::StrFormat("v%d", current_version).c_str());
578
579 static int target_version = 3;
580 ImGui::RadioButton("v2 (Basic features)", &target_version, 2);
581 ImGui::SameLine();
582 ImGui::RadioButton("v3 (All features)", &target_version, 3);
583
584 ImGui::Separator();
585
586 if (ImGui::Button(ICON_MD_CHECK " Apply Upgrade", ImVec2(150.0f, 0.0f))) {
587 auto upgrade_status =
588 upgrade_system_ != nullptr
589 ? upgrade_system_->ApplyZSCustomOverworldASM(target_version)
590 : absl::FailedPreconditionError("Upgrade system not initialized");
591 if (upgrade_status.ok()) {
592 status_ = Clear();
593 if (status_.ok()) {
594 status_ = Load();
595 }
596 if (status_.ok()) {
597 ImGui::CloseCurrentPopup();
598 }
599 } else {
600 LOG_ERROR("OverworldEditor", "Upgrade failed: %s",
601 upgrade_status.message().data());
602 }
603 }
604 ImGui::SameLine();
605 if (ImGui::Button(ICON_MD_CANCEL " Cancel", ImVec2(150.0f, 0.0f))) {
606 ImGui::CloseCurrentPopup();
607 }
608
609 ImGui::EndPopup();
610 }
611
612 // ===========================================================================
613 // Centralized Entity Interaction Logic (extracted to dedicated method)
614 // ===========================================================================
615 zelda3::GameEntity* hovered_entity =
616 entity_renderer_ ? entity_renderer_->hovered_entity() : nullptr;
618 interaction_coordinator_->Update(hovered_entity);
619 }
620
621 return absl::OkStatus();
622}
623
629
631 const zelda3::OverworldItem& item_identity) {
632 auto* item = FindItemByIdentity(&overworld_, item_identity);
633 if (!item) {
634 return false;
635 }
636
638 current_entity_ = item;
640 auto* workbench = static_cast<OverworldEntityWorkbench*>(
642 "overworld.entity_workbench"));
643 if (workbench) {
644 workbench->SetActiveEntity(item);
645 }
646 }
647 return true;
648}
649
652 current_entity_ = nullptr;
654 auto* workbench = static_cast<OverworldEntityWorkbench*>(
656 "overworld.entity_workbench"));
657 if (workbench) {
658 workbench->SetActiveEntity(nullptr);
659 }
660 }
661}
662
665 return nullptr;
666 return static_cast<OverworldEntityWorkbench*>(
668 "overworld.entity_workbench"));
669}
670
672 if (!selected_item_identity_.has_value()) {
673 return nullptr;
674 }
675
677 if (!item) {
679 return nullptr;
680 }
681
682 current_entity_ = item;
683 if (auto* workbench = GetWorkbench()) {
684 workbench->SetActiveEntity(item);
685 }
686 return item;
687}
688
690 return const_cast<OverworldEditor*>(this)->GetSelectedItem();
691}
692
699
701 const OverworldItemsSnapshot& snapshot) {
702 auto* items = overworld_.mutable_all_items();
703 if (!items) {
704 return;
705 }
706
707 *items = snapshot.items;
709 if (selected_item_identity_.has_value()) {
710 auto* selected_item =
712 if (selected_item) {
713 current_entity_ = selected_item;
714 if (auto* workbench = GetWorkbench()) {
715 workbench->SetActiveEntity(selected_item);
716 }
717 } else {
719 }
720 } else {
722 }
723
724 if (rom_) {
725 rom_->set_dirty(true);
726 }
728}
729
731 std::string description) {
733 if (ItemSnapshotsEqual(before, after)) {
734 return;
735 }
736
737 undo_manager_.Push(std::make_unique<OverworldItemsEditAction>(
738 std::move(before), std::move(after),
739 [this](const OverworldItemsSnapshot& snapshot) {
740 RestoreItemUndoSnapshot(snapshot);
741 },
742 std::move(description)));
743}
744
745bool OverworldEditor::DuplicateSelectedItem(int offset_x, int offset_y) {
746 auto* selected_item = GetSelectedItem();
747 if (!selected_item) {
750 "Select an overworld item first (Item Mode: key 5)",
752 }
753 return false;
754 }
755
756 auto before_snapshot = CaptureItemUndoSnapshot();
759 duplicate_result = entity_mutation_service_->DuplicateItem(
760 *selected_item, offset_x, offset_y);
761 } else {
762 auto duplicate_or = DuplicateItemByIdentity(&overworld_, *selected_item,
763 offset_x, offset_y);
764 if (duplicate_or.ok()) {
765 duplicate_result.entity = *duplicate_or;
766 duplicate_result.status = absl::OkStatus();
767 } else {
768 duplicate_result.status = duplicate_or.status();
769 duplicate_result.error_message =
770 "Failed to duplicate overworld item: " +
771 std::string(duplicate_or.status().message());
772 }
773 }
774 if (!duplicate_result.ok()) {
777 duplicate_result.error_message.empty()
778 ? "Failed to duplicate overworld item"
779 : duplicate_result.error_message,
781 }
782 return false;
783 }
784
785 auto* duplicated_item =
786 static_cast<zelda3::OverworldItem*>(duplicate_result.entity);
787 selected_item_identity_ = *duplicated_item;
788 current_entity_ = duplicated_item;
789 if (auto* workbench = GetWorkbench()) {
790 workbench->SetActiveEntity(duplicated_item);
791 }
792 PushItemUndoAction(std::move(before_snapshot),
793 absl::StrFormat("Duplicate overworld item 0x%02X",
794 static_cast<int>(duplicated_item->id_)));
795 rom_->set_dirty(true);
798 absl::StrFormat("Duplicated item 0x%02X",
799 static_cast<int>(duplicated_item->id_)),
801 }
802 return true;
803}
804
805bool OverworldEditor::NudgeSelectedItem(int delta_x, int delta_y) {
806 auto* selected_item = GetSelectedItem();
807 if (!selected_item) {
808 return false;
809 }
810
811 auto before_snapshot = CaptureItemUndoSnapshot();
812 auto status = NudgeItem(selected_item, delta_x, delta_y);
813 if (!status.ok()) {
815 dependencies_.toast_manager->Show("Failed to move selected item",
817 }
818 return false;
819 }
820
821 selected_item_identity_ = *selected_item;
822 current_entity_ = selected_item;
823 if (auto* workbench = GetWorkbench()) {
824 workbench->SetActiveEntity(selected_item);
825 }
827 std::move(before_snapshot),
828 absl::StrFormat("Move overworld item 0x%02X (%+d,%+d)",
829 static_cast<int>(selected_item->id_), delta_x, delta_y));
830 rom_->set_dirty(true);
831 return true;
832}
833
835 auto* selected_item = GetSelectedItem();
836 if (!selected_item) {
837 return false;
838 }
839
840 auto before_snapshot = CaptureItemUndoSnapshot();
841 const zelda3::OverworldItem selected_identity = *selected_item;
842 const uint8_t deleted_item_id = selected_identity.id_;
843 absl::Status remove_status =
844 absl::FailedPreconditionError("Entity mutation service not initialized");
846 remove_status =
847 entity_mutation_service_->DeleteItem(selected_identity).status;
848 } else {
849 remove_status = RemoveItemByIdentity(&overworld_, selected_identity);
850 }
851 if (!remove_status.ok()) {
853 dependencies_.toast_manager->Show("Failed to delete selected item",
855 }
856 return false;
857 }
858
859 auto* nearest_item =
861 ? entity_mutation_service_->ResolveNextSelection(selected_identity)
862 : FindNearestItemForSelection(&overworld_, selected_identity);
863 if (nearest_item) {
864 selected_item_identity_ = *nearest_item;
865 current_entity_ = nearest_item;
866 if (auto* workbench = GetWorkbench()) {
867 workbench->SetActiveEntity(nearest_item);
868 }
869 } else {
871 }
872
873 PushItemUndoAction(std::move(before_snapshot),
874 absl::StrFormat("Delete overworld item 0x%02X",
875 static_cast<int>(deleted_item_id)));
876 rom_->set_dirty(true);
878 if (nearest_item) {
880 absl::StrFormat("Deleted item 0x%02X (selected nearest 0x%02X)",
881 static_cast<int>(deleted_item_id),
882 static_cast<int>(nearest_item->id_)),
884 } else {
886 absl::StrFormat("Deleted overworld item 0x%02X",
887 static_cast<int>(deleted_item_id)),
889 }
890 }
891 return true;
892}
893
895 tile_painting_->CheckForOverworldEdits();
896}
897
898absl::Status OverworldEditor::Copy() {
900 return absl::FailedPreconditionError("Clipboard unavailable");
901 }
902 // If a rectangle selection exists, copy its tile16 IDs into shared clipboard
904 !ow_map_canvas_.selected_points().empty()) {
905 std::vector<int> ids;
906 // selected_points are now stored in world coordinates
907 const auto start = ow_map_canvas_.selected_points()[0];
908 const auto end = ow_map_canvas_.selected_points()[1];
909 const int start_x =
910 static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
911 const int end_x =
912 static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
913 const int start_y =
914 static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
915 const int end_y =
916 static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
917 const int width = end_x - start_x + 1;
918 const int height = end_y - start_y + 1;
919 ids.reserve(width * height);
922 for (int y = start_y; y <= end_y; ++y) {
923 for (int x = start_x; x <= end_x; ++x) {
924 ids.push_back(overworld_.GetTile(x, y));
925 }
926 }
927
932 return absl::OkStatus();
933 }
934 // Single tile copy fallback
935 if (current_tile16_ >= 0) {
940 return absl::OkStatus();
941 }
942 return absl::FailedPreconditionError("Nothing selected to copy");
943}
944
947 return absl::FailedPreconditionError("Clipboard unavailable");
948 }
950 return absl::FailedPreconditionError("Clipboard empty");
951 }
952 if (ow_map_canvas_.points().empty() &&
954 return absl::FailedPreconditionError("No paste target");
955 }
956
957 // Determine paste anchor position (use current mouse drawn tile position)
958 // Unscale coordinates to get world position
959 const ImVec2 scaled_anchor = ow_map_canvas_.drawn_tile_position();
960 float scale = ow_map_canvas_.global_scale();
961 if (scale <= 0.0f)
962 scale = 1.0f;
963 const ImVec2 anchor =
964 ImVec2(scaled_anchor.x / scale, scaled_anchor.y / scale);
965
966 // Compute anchor in tile16 grid within the current map
967 const int tile16_x =
968 (static_cast<int>(anchor.x) % kOverworldMapSize) / kTile16Size;
969 const int tile16_y =
970 (static_cast<int>(anchor.y) % kOverworldMapSize) / kTile16Size;
971
972 auto& selected_world =
973 (current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world
974 : (current_world_ == 1) ? overworld_.mutable_map_tiles()->dark_world
975 : overworld_.mutable_map_tiles()->special_world;
976
977 const int world_offset = current_world_ * 0x40;
978 const int local_map = current_map_ - world_offset;
979 const int superY = local_map / 8;
980 const int superX = local_map % 8;
981
985
986 // Guard
987 if (width * height != static_cast<int>(ids.size())) {
988 return absl::InternalError("Clipboard dimensions mismatch");
989 }
990
991 bool any_changed = false;
992 for (int dy = 0; dy < height; ++dy) {
993 for (int dx = 0; dx < width; ++dx) {
994 const int id = ids[dy * width + dx];
995 const int gx = tile16_x + dx;
996 const int gy = tile16_y + dy;
997
998 const int global_x = superX * 32 + gx;
999 const int global_y = superY * 32 + gy;
1000 if (global_x < 0 || global_x >= 256 || global_y < 0 || global_y >= 256)
1001 continue;
1002 const int old_tile_id = selected_world[global_x][global_y];
1003 if (old_tile_id == id) {
1004 continue;
1005 }
1006 CreateUndoPoint(current_map_, current_world_, global_x, global_y,
1007 old_tile_id);
1008 selected_world[global_x][global_y] = id;
1009 any_changed = true;
1010 }
1011 }
1012
1013 if (any_changed) {
1015 rom_->set_dirty(true);
1017 }
1018 return absl::OkStatus();
1019}
1020
1022 if (canvas_nav_)
1023 return canvas_nav_->CheckForCurrentMap();
1024 return absl::OkStatus();
1025}
1026
1028 // Delegate to the existing GfxGroupEditor
1029 if (rom_ && rom_->is_loaded()) {
1030 return gfx_group_editor_.Update();
1031 } else {
1032 gui::CenterText("No ROM loaded");
1033 return absl::OkStatus();
1034 }
1035}
1036
1037// DrawV3Settings - now in OverworldCanvasRenderer
1038
1039// DrawMapProperties - now in OverworldCanvasRenderer
1040
1042 // HACK MANIFEST VALIDATION
1043 const bool saving_maps =
1045 if (saving_maps && dependencies_.project &&
1047 const auto& manifest = dependencies_.project->hack_manifest;
1048 const auto write_policy = dependencies_.project->rom_metadata.write_policy;
1049
1050 // Calculate memory ranges that would be written by overworld map saves.
1051 // `ranges` are PC offsets (ROM file offsets). The hack manifest is in SNES
1052 // address space (LoROM), so convert before analysis.
1053 auto ranges = overworld_.GetProjectedWriteRanges();
1055 manifest, write_policy, ranges, "overworld maps", "OverworldEditor",
1057 }
1058
1059 if (saving_maps) {
1064 }
1065 if (core::FeatureFlags::get().overworld.kSaveOverworldEntrances) {
1067 }
1068 if (core::FeatureFlags::get().overworld.kSaveOverworldExits) {
1070 }
1071 if (core::FeatureFlags::get().overworld.kSaveOverworldItems) {
1073 }
1074 if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) {
1077 }
1078 return absl::OkStatus();
1079}
1080
1081// ============================================================================
1082// Undo/Redo System Implementation
1083// ============================================================================
1084
1086 switch (world) {
1087 case 0:
1088 return overworld_.mutable_map_tiles()->light_world;
1089 case 1:
1090 return overworld_.mutable_map_tiles()->dark_world;
1091 default:
1092 return overworld_.mutable_map_tiles()->special_world;
1093 }
1094}
1095
1096void OverworldEditor::CreateUndoPoint(int map_id, int world, int x, int y,
1097 int old_tile_id) {
1098 auto now = std::chrono::steady_clock::now();
1099
1100 // Check if we should batch with current operation (same map, same world,
1101 // within timeout)
1102 if (current_paint_operation_.has_value() &&
1103 current_paint_operation_->map_id == map_id &&
1104 current_paint_operation_->world == world &&
1106 // Add to existing operation
1107 current_paint_operation_->tile_changes.emplace_back(std::make_pair(x, y),
1108 old_tile_id);
1109 } else {
1110 // Finalize any pending operation before starting a new one
1112
1113 // Start new operation
1115 OverworldUndoPoint{.map_id = map_id,
1116 .world = world,
1117 .tile_changes = {{{x, y}, old_tile_id}},
1118 .timestamp = now};
1119 }
1120
1121 last_paint_time_ = now;
1122}
1123
1125 if (!current_paint_operation_.has_value()) {
1126 return;
1127 }
1128
1129 // Push to the UndoManager (new framework path).
1130 auto& world_tiles = GetWorldTiles(current_paint_operation_->world);
1131 std::vector<OverworldTileChange> changes;
1132 changes.reserve(current_paint_operation_->tile_changes.size());
1133 for (const auto& [coords, old_tile_id] :
1134 current_paint_operation_->tile_changes) {
1135 auto [x, y] = coords;
1136 int new_tile_id = world_tiles[x][y];
1137 changes.push_back({x, y, old_tile_id, new_tile_id});
1138 }
1139 auto action = std::make_unique<OverworldTilePaintAction>(
1141 std::move(changes), &overworld_, [this]() { RefreshOverworldMap(); });
1142 undo_manager_.Push(std::move(action));
1143
1145}
1146
1148 // Finalize any pending paint operation first
1150 return undo_manager_.Undo();
1151}
1152
1154 return undo_manager_.Redo();
1155}
1156
1157// ============================================================================
1158
1160 gfx::ScopedTimer timer("LoadGraphics");
1161
1162 LOG_DEBUG("OverworldEditor", "Loading overworld.");
1163 // Load the Link to the Past overworld.
1164 {
1165 gfx::ScopedTimer load_timer("Overworld::Load");
1167 }
1169
1170 // Fix: Set transparency for the first color of each 16-color subpalette
1171 // This ensures the background color (backdrop) shows through
1172 for (size_t i = 0; i < palette_.size(); i += 16) {
1173 if (i < palette_.size()) {
1174 palette_[i].set_transparent(true);
1175 }
1176 }
1177
1178 LOG_DEBUG("OverworldEditor", "Loading overworld graphics (optimized).");
1179
1180 // Phase 1: Create bitmaps without textures for faster loading
1181 // This avoids blocking the main thread with GPU texture creation
1182 {
1183 gfx::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics");
1189 }
1190
1191 LOG_DEBUG("OverworldEditor",
1192 "Loading overworld tileset (deferred textures).");
1193 {
1194 gfx::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
1195 tile16_blockset_bmp_.Create(0x80, 0x2000, 0x08,
1200 }
1201 map_blockset_loaded_ = true;
1202
1203 // Copy the tile16 data into individual tiles.
1204 const auto& tile16_blockset_data = overworld_.tile16_blockset_data();
1205 LOG_DEBUG("OverworldEditor", "Loading overworld tile16 graphics.");
1206
1207 {
1208 gfx::ScopedTimer tilemap_timer("CreateTilemap");
1210 gfx::CreateTilemap(renderer_, tile16_blockset_data, 0x80, 0x2000,
1212
1213 // Queue texture creation for the tile16 blockset atlas
1218 }
1219 }
1220
1221 // Phase 2: reset map bitmaps and leave them fully on-demand.
1222 // The canvas/navigation path will materialize only the visible/current maps.
1223 for (auto& bitmap : maps_bmp_) {
1224 bitmap = gfx::Bitmap();
1225 }
1226
1227 if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
1228 {
1229 gfx::ScopedTimer sprites_timer("LoadSpriteGraphics");
1231 }
1232 }
1233
1234 return absl::OkStatus();
1235}
1236
1238 // Render the sprites for each Overworld map
1239 const int depth = 0x10;
1240 for (int i = 0; i < 3; i++)
1241 for (auto const& sprite : *overworld_.mutable_sprites(i)) {
1242 int width = sprite.width();
1243 int height = sprite.height();
1244 if (width == 0 || height == 0) {
1245 continue;
1246 }
1247 if (sprite_previews_.size() < sprite.id()) {
1248 sprite_previews_.resize(sprite.id() + 1);
1249 }
1250 sprite_previews_[sprite.id()].Create(width, height, depth,
1251 *sprite.preview_graphics());
1252 sprite_previews_[sprite.id()].SetPalette(palette_);
1255 &sprite_previews_[sprite.id()]);
1256 }
1257 return absl::OkStatus();
1258}
1259
1261 // Process queued texture commands via Arena's deferred system
1262 if (renderer_) {
1264 }
1265
1266 // Also process deferred map refreshes for modified maps
1267 int refresh_count = 0;
1268 const int max_refreshes_per_frame = 2;
1269
1270 auto try_refresh_map = [&](int map_index) {
1271 if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
1272 return;
1273 }
1274 if (refresh_count >= max_refreshes_per_frame) {
1275 return;
1276 }
1277 if (maps_bmp_[map_index].modified() && maps_bmp_[map_index].is_active()) {
1278 RefreshOverworldMapOnDemand(map_index);
1279 ++refresh_count;
1280 }
1281 };
1282
1283 // Highest priority: current map.
1284 try_refresh_map(current_map_);
1285
1286 // Then refresh maps in the active world only, avoiding a full-map scan each frame.
1287 const int world_start = current_world_ * 0x40;
1288 const int world_end = std::min(world_start + 0x40, zelda3::kNumOverworldMaps);
1289 for (int i = world_start;
1290 i < world_end && refresh_count < max_refreshes_per_frame; ++i) {
1291 if (i == current_map_) {
1292 continue;
1293 }
1294 try_refresh_map(i);
1295 }
1296}
1297
1299 if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
1300 return;
1301 }
1302
1303 // Ensure the map is built first (on-demand loading)
1304 auto status = overworld_.EnsureMapBuilt(map_index);
1305 if (!status.ok()) {
1306 LOG_ERROR("OverworldEditor", "Failed to build map %d: %s", map_index,
1307 status.message());
1308 return;
1309 }
1310
1311 auto& bitmap = maps_bmp_[map_index];
1312
1313 // If bitmap doesn't exist yet (non-essential map), create it now
1314 if (!bitmap.is_active()) {
1315 overworld_.set_current_map(map_index);
1316 const auto& palette = overworld_.current_area_palette();
1317 try {
1318 bitmap.Create(kOverworldMapSize, kOverworldMapSize, 0x80,
1320 bitmap.SetPalette(palette);
1321 } catch (const std::bad_alloc& e) {
1322 LOG_ERROR("OverworldEditor", "Error allocating bitmap for map %d: %s",
1323 map_index, e.what());
1324 return;
1325 }
1326 }
1327
1328 if (!bitmap.texture() && bitmap.is_active()) {
1329 // Queue texture creation for this map
1332 }
1333}
1334
1337 ctx.rom = rom_;
1338 ctx.overworld = &overworld_;
1339 ctx.maps_bmp = &maps_bmp_;
1343 ctx.palette = &palette_;
1344 ctx.renderer = renderer_;
1349 ctx.game_state = &game_state_;
1351 ctx.status = &status_;
1352 ctx.ensure_map_texture = [this](int map_index) {
1353 this->EnsureMapTexture(map_index);
1354 };
1355 map_refresh_ = std::make_unique<MapRefreshCoordinator>(ctx);
1356}
1357
1359 if (canvas_nav_)
1360 canvas_nav_->HandleMapInteraction();
1361}
1362
1364 if (canvas_nav_)
1365 canvas_nav_->ScrollBlocksetCanvasToCurrentTile();
1366}
1367
1369 if (tile_id < 0 || tile_id >= zelda3::kNumTile16Individual) {
1370 return;
1371 }
1372
1373 if (!rom_ || !rom_->is_loaded() || !all_gfx_loaded_ ||
1375 current_tile16_ = tile_id;
1377 return;
1378 }
1379
1381
1382 if (tile_id == tile16_editor_.current_tile16()) {
1383 current_tile16_ = tile_id;
1384 return;
1385 }
1386
1387 const int from_tile = tile16_editor_.current_tile16();
1388 const bool had_staged_on_current = tile16_editor_.is_tile_modified(from_tile);
1389 if (had_staged_on_current && dependencies_.window_manager) {
1390 const size_t session_id =
1396 }
1397
1400}
1401
1404
1405 // Unregister palette listener
1406 if (palette_listener_id_ >= 0) {
1409 }
1410
1412 current_graphics_set_.clear();
1413 all_gfx_loaded_ = false;
1414 map_blockset_loaded_ = false;
1415 return absl::OkStatus();
1416}
1417
1419 if (canvas_nav_)
1420 canvas_nav_->UpdateBlocksetSelectorState();
1421}
1422
1424 const int next =
1425 std::clamp(current_tile16_ + delta, 0, zelda3::kNumTile16Individual - 1);
1427}
1428
1430 if (!status_bar)
1431 return;
1432 const char* world_label = "LW";
1433 switch (current_world_) {
1434 case 0:
1435 world_label = "LW";
1436 break;
1437 case 1:
1438 world_label = "DW";
1439 break;
1440 case 2:
1441 world_label = "SW";
1442 break;
1443 default:
1444 world_label = "??";
1445 break;
1446 }
1447 status_bar->SetCustomSegment(
1448 "Map", absl::StrFormat("%s #%02X", world_label, current_map_ & 0xFF));
1449 status_bar->SetCustomSegment("Tile16",
1450 absl::StrFormat("0x%03X", current_tile16_));
1451
1452 const char* mode_label = "Draw";
1453 switch (entity_edit_mode_) {
1455 mode_label = "Entrances";
1456 break;
1458 mode_label = "Exits";
1459 break;
1461 mode_label = "Items";
1462 break;
1464 mode_label = "Sprites";
1465 break;
1467 mode_label = "Transports";
1468 break;
1470 mode_label = "Music";
1471 break;
1473 switch (current_mode) {
1474 case EditingMode::MOUSE:
1475 mode_label = "Mouse";
1476 break;
1478 mode_label = "Draw";
1479 break;
1481 mode_label = "Fill";
1482 break;
1483 }
1484 break;
1485 }
1486 status_bar->SetEditorMode(mode_label);
1487}
1488
1489} // namespace yaze::editor
void set_dirty(bool dirty)
Definition rom.h:134
bool is_loaded() const
Definition rom.h:132
static Flags & get()
Definition features.h:118
bool loaded() const
Check if the manifest has been loaded.
UndoManager undo_manager_
Definition editor.h:317
EditorDependencies dependencies_
Definition editor.h:316
void SetHostSurfaceHint(std::string hint)
Main UI class for editing overworld maps in A Link to the Past.
std::unique_ptr< UsageStatisticsCard > usage_stats_card_
absl::Status Clear() override
std::unique_ptr< MapPropertiesSystem > map_properties_system_
std::unique_ptr< OverworldCanvasRenderer > canvas_renderer_
OverworldEntityWorkbench * GetWorkbench()
Resolve the entity workbench window content (may be null).
zelda3::OverworldEntranceTileTypes entrance_tiletypes_
void HandleTile16Edit()
Handle tile16 editing from context menu (MOUSE mode) Gets the tile16 under the cursor and opens the T...
std::optional< OverworldUndoPoint > current_paint_operation_
void PushItemUndoAction(OverworldItemsSnapshot before, std::string description)
absl::Status CheckForCurrentMap()
Check for map changes and refresh if needed.
void CreateUndoPoint(int map_id, int world, int x, int y, int old_tile_id)
void ForceRefreshGraphics(int map_index)
void RestoreItemUndoSnapshot(const OverworldItemsSnapshot &snapshot)
std::vector< int > selected_tile16_ids_
void InitCanvasNavigationManager()
Initialize the canvas navigation manager (called during Initialize)
void InitMapRefreshCoordinator()
Initialize the map refresh coordinator (called during Initialize)
std::optional< zelda3::OverworldItem > selected_item_identity_
std::unique_ptr< zelda3::OverworldUpgradeSystem > upgrade_system_
void NotifyEntityModified(zelda3::GameEntity *entity)
Notify that an entity has been modified, marking ROM as dirty.
void HandleEntityInsertion(const std::string &entity_type)
Handle entity insertion from context menu.
Definition automation.cc:75
std::unique_ptr< MapRefreshCoordinator > map_refresh_
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > maps_bmp_
void CheckForOverworldEdits()
Check for tile edits - delegates to TilePaintingManager.
void ContributeStatus(StatusBar *status_bar) override
void RefreshSiblingMapGraphics(int map_index, bool include_self=false)
void RefreshOverworldMapOnDemand(int map_index)
std::unique_ptr< OverworldSidebar > sidebar_
std::chrono::steady_clock::time_point last_paint_time_
void InvalidateGraphicsCache(int map_id=-1)
Invalidate cached graphics for a specific map or all maps.
std::optional< int > pending_tile16_selection_after_gfx_
absl::Status SaveCurrentSelectionToScratch()
zelda3::OverworldItem * GetSelectedItem()
Resolve selected item identity to the current live item pointer.
bool DuplicateSelectedItem(int offset_x=16, int offset_y=0)
Duplicate selected item with optional pixel offset.
zelda3::Overworld & overworld()
Access the underlying Overworld data.
void SetCurrentEntity(zelda3::GameEntity *entity)
Set the currently active entity for editing.
std::unique_ptr< OverworldEntityRenderer > entity_renderer_
absl::Status Load() override
void EnsureMapTexture(int map_index)
Ensure a specific map has its texture created.
std::vector< gfx::Bitmap > sprite_previews_
std::unique_ptr< CanvasNavigationManager > canvas_nav_
absl::Status Copy() override
bool NudgeSelectedItem(int delta_x, int delta_y)
Move selected item by signed pixel deltas.
std::unique_ptr< OverworldToolbar > toolbar_
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.
static constexpr auto kPaintBatchTimeout
bool DeleteSelectedItem()
Delete selected item and preserve nearest-item selection continuity.
std::unique_ptr< TilePaintingManager > tile_painting_
absl::Status LoadGraphics()
Load the Bitmap objects for each OverworldMap.
OverworldItemsSnapshot CaptureItemUndoSnapshot() const
std::unique_ptr< DebugWindowCard > debug_window_card_
std::unique_ptr< OverworldInteractionCoordinator > interaction_coordinator_
zelda3::GameEntity * current_entity_
void HandleKeyboardShortcuts()
Handle overworld keyboard shortcuts and edit-mode hotkeys.
std::unique_ptr< EntityMutationService > entity_mutation_service_
void ClearSelectedItem()
Clear active item selection.
void InitTilePaintingManager()
Initialize the tile painting manager (called after graphics load)
bool SelectItemByIdentity(const zelda3::OverworldItem &item_identity)
Select an overworld item using value identity matching.
Authoritative component for entity editing state and UI.
void OpenContextMenuFor(zelda3::GameEntity *entity)
void SetActiveEntity(zelda3::GameEntity *entity)
A session-aware status bar displayed at the bottom of the application.
Definition status_bar.h:54
void SetCustomSegment(const std::string &key, const std::string &value)
Set a custom segment with key-value pair.
void SetEditorMode(const std::string &mode)
Set the current editor mode or tool.
bool has_pending_changes() const
Check if any tiles have uncommitted changes.
void RequestTileSwitch(int target_tile_id)
absl::Status Initialize(gfx::Bitmap &tile16_blockset_bmp, gfx::Bitmap &current_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
bool is_tile_modified(int tile_id) const
Check if a specific tile has pending changes.
void set_palette(const gfx::SnesPalette &palette)
void set_on_current_tile_changed(std::function< void(int)> callback)
void set_on_changes_committed(std::function< absl::Status()> callback)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
void Push(std::unique_ptr< UndoAction > action)
absl::Status Redo()
Redo the top action. Returns error if stack is empty.
absl::Status Undo()
Undo the top action. Returns error if stack is empty.
bool OpenWindow(size_t session_id, const std::string &base_window_id)
WindowContent * GetWindowContent(const std::string &window_id)
Get a WindowContent instance by ID.
bool ToggleWindow(size_t session_id, const std::string &base_window_id)
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
void MarkWindowRecentlyUsed(const std::string &window_id)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
int RegisterPaletteListener(PaletteChangeCallback callback)
Register a callback for palette change notifications.
Definition arena.cc:441
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:116
void UnregisterPaletteListener(int listener_id)
Unregister a palette change listener.
Definition arena.cc:448
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
Definition bitmap.cc:201
bool is_active() const
Definition bitmap.h:384
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
Definition bitmap.cc:384
SDL_Surface * surface() const
Definition bitmap.h:379
RAII timer for automatic timing management.
auto selected_tile_pos() const
Definition canvas.h:489
auto global_scale() const
Definition canvas.h:491
auto select_rect_active() const
Definition canvas.h:487
void SetUsageMode(CanvasUsage usage)
Definition canvas.cc:280
auto drawn_tile_position() const
Definition canvas.h:450
const ImVector< ImVec2 > & points() const
Definition canvas.h:439
auto selected_points() const
Definition canvas.h:553
Base class for all overworld and dungeon entities.
Definition common.h:31
enum yaze::zelda3::GameEntity::EntityType entity_type_
Represents an overworld exit that transitions from dungeon to overworld.
void set_current_world(int world)
Definition overworld.h:604
absl::Status Load(Rom *rom)
Load all overworld data from ROM.
Definition overworld.cc:36
const std::vector< uint8_t > & current_graphics() const
Definition overworld.h:558
absl::Status SaveMapProperties()
Save per-area graphics, palettes, and messages.
std::vector< std::pair< uint32_t, uint32_t > > GetProjectedWriteRanges() const
Get the projected write ranges (PC offsets) for overworld map saves.
absl::Status SaveMap32Tiles()
Save tile32 definitions to ROM.
const gfx::SnesPalette & current_area_palette() const
Definition overworld.h:574
absl::Status SaveMap16Tiles()
Save tile16 definitions to ROM.
auto all_items() const
Definition overworld.h:625
auto is_loaded() const
Definition overworld.h:597
absl::Status CreateTile32Tilemap()
Build tile32 tilemap from current tile16 data.
const std::vector< uint8_t > & tile16_blockset_data() const
Definition overworld.h:586
void set_current_map(int i)
Definition overworld.h:603
absl::Status SaveEntrances()
Save entrance warp points to ROM.
absl::Status SaveExits()
Save exit return points to ROM.
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
Definition overworld.cc:892
absl::Status SaveItems()
Save hidden overworld items to ROM.
uint16_t GetTile(int x, int y) const
Definition overworld.h:605
absl::Status SaveOverworldMaps()
Save compressed map tile data to ROM.
const std::vector< uint8_t > & current_map_bitmap_data() const
Definition overworld.h:580
auto mutable_sprites(int state)
Definition overworld.h:553
absl::Status SaveMusic()
Save per-area music IDs.
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_UPGRADE
Definition icons.h:2047
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:830
#define ICON_MD_LAYERS
Definition icons.h:1068
#define LOG_DEBUG(category, format,...)
Definition log.h:103
#define LOG_ERROR(category, format,...)
Definition log.h:109
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
bool ItemIdentityMatchesForUndo(const zelda3::OverworldItem &lhs, const zelda3::OverworldItem &rhs)
bool ItemSnapshotsEqual(const OverworldItemsSnapshot &lhs, const OverworldItemsSnapshot &rhs)
Editors are the view controllers for the application.
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
absl::Status RemoveItemByIdentity(zelda3::Overworld *overworld, const zelda3::OverworldItem &item_identity)
Remove an item by value identity instead of pointer identity.
constexpr unsigned int kOverworldMapSize
absl::Status NudgeItem(zelda3::OverworldItem *item, int delta_x, int delta_y)
Move an item by pixel deltas with overworld bounds clamping.
zelda3::OverworldItem * FindNearestItemForSelection(zelda3::Overworld *overworld, const zelda3::OverworldItem &anchor_identity)
Find the best next item to keep selection continuity after deletion.
zelda3::OverworldItem * FindItemByIdentity(zelda3::Overworld *overworld, const zelda3::OverworldItem &item_identity)
Find a live item by value identity.
constexpr int kTile16Size
absl::StatusOr< zelda3::OverworldItem * > DuplicateItemByIdentity(zelda3::Overworld *overworld, const zelda3::OverworldItem &item_identity, int offset_x, int offset_y)
Duplicate an existing item by identity with a positional offset.
Tilemap CreateTilemap(IRenderer *renderer, const std::vector< uint8_t > &data, int width, int height, int tile_size, int num_tiles, const SnesPalette &palette)
Definition tilemap.cc:14
void CenterText(const char *text)
constexpr int kNumTile16Individual
Definition overworld.h:239
constexpr int kNumOverworldMaps
Definition common.h:85
constexpr int OverworldCustomASMHasBeenApplied
Definition common.h:89
absl::StatusOr< OverworldEntranceTileTypes > LoadEntranceTileTypes(Rom *rom)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
struct yaze::core::FeatureFlags::Flags::Overworld overworld
Callbacks for operations that remain in the OverworldEditor.
std::function< absl::Status()> refresh_tile16_blockset
std::function< bool()> is_entity_hovered
Returns true if an entity is currently hovered (for pan suppression).
Shared state pointers that the navigation manager reads/writes.
std::unique_ptr< gui::TileSelectorWidget > * blockset_selector
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
project::YazeProject * project
Definition editor.h:168
SharedClipboard * shared_clipboard
Definition editor.h:181
gfx::IRenderer * renderer
Definition editor.h:186
WorkspaceWindowManager * window_manager
Definition editor.h:176
Context struct holding all data dependencies for map refresh operations. All pointers/references must...
std::function< void(int map_index)> ensure_map_texture
Callback to ensure a map texture is created (stays in OverworldEditor)
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
Sink for high-level commands emitted by the interaction coordinator.
std::function< void(zelda3::GameEntity *) on_entity_double_click)
std::function< void(zelda3::GameEntity *) on_entity_context_menu)
std::function< void(int, int, bool)> on_nudge_selected
std::function< void(EditingMode)> on_set_editor_mode
std::function< void(EntityEditMode)> on_set_entity_mode
Snapshot of overworld item list + current item selection.
std::vector< zelda3::OverworldItem > items
std::optional< zelda3::OverworldItem > selected_item_identity
static constexpr const char * kItemList
static constexpr const char * kTile16Editor
std::vector< int > overworld_tile16_ids
Definition editor.h:118
Callbacks for undo integration and map refresh.
std::function< void(int tile_id)> request_tile16_selection
When set, eyedropper routes here (guarded RequestTileSwitch path).
std::function< void(int map_index)> refresh_overworld_map_on_demand
std::function< void()> scroll_blockset_to_current_tile
std::function< void()> finalize_paint_operation
std::function< void(int map_id, int world, int x, int y, int old_tile_id)> create_undo_point
Shared state for the tile painting system.
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119
RomWritePolicy write_policy
Definition project.h:110
core::HackManifest hack_manifest
Definition project.h:204