yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
map_properties.cc
Go to the documentation of this file.
2
6#include "app/gui/canvas.h"
7#include "app/gui/color.h"
8#include "app/gui/icons.h"
9#include "app/gui/input.h"
11#include "imgui/imgui.h"
12
13namespace yaze {
14namespace editor {
15
16using ImGui::BeginTable;
17// HOVER_HINT is defined in util/macro.h
18using ImGui::Separator;
19using ImGui::TableNextColumn;
20using ImGui::Text;
21
22// Using centralized UI constants
23
25 int& current_world, int& current_map, bool& current_map_lock,
26 bool& show_map_properties_panel, bool& show_custom_bg_color_editor,
27 bool& show_overlay_editor, bool& show_overlay_preview, int& game_state,
28 int& current_mode) {
29 (void)show_overlay_editor; // Reserved for future use
30 (void)current_mode; // Reserved for future use
31 // Enhanced settings table with popup buttons for quick access and integrated toolset
32 if (BeginTable("SimplifiedMapSettings", 9,
33 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit,
34 ImVec2(0, 0), -1)) {
35 ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed,
37 ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed,
39 ImGui::TableSetupColumn("Area Size", ImGuiTableColumnFlags_WidthFixed,
41 ImGui::TableSetupColumn("Lock", ImGuiTableColumnFlags_WidthFixed,
43 ImGui::TableSetupColumn("Graphics", ImGuiTableColumnFlags_WidthFixed,
45 ImGui::TableSetupColumn("Palettes", ImGuiTableColumnFlags_WidthFixed,
47 ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed,
49 ImGui::TableSetupColumn("View", ImGuiTableColumnFlags_WidthFixed,
51 ImGui::TableSetupColumn("Quick", ImGuiTableColumnFlags_WidthFixed,
53
54 TableNextColumn();
55 ImGui::SetNextItemWidth(kComboWorldWidth);
56 ImGui::Combo("##world", &current_world, kWorldNames, 3);
57
58 TableNextColumn();
59 ImGui::Text("%d (0x%02X)", current_map, current_map);
60
61 TableNextColumn();
62 // IMPORTANT: Don't cache - read fresh to reflect ROM upgrades
63 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
64
65 // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall.
66 int current_area_size =
67 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
68 ImGui::SetNextItemWidth(kComboAreaSizeWidth);
69
70 if (asm_version >= 3 && asm_version != 0xFF) {
71 // v3+ ROM: Show all 4 area size options
72 if (ImGui::Combo("##AreaSize", &current_area_size, kAreaSizeNames, 4)) {
73 auto status = overworld_->ConfigureMultiAreaMap(
74 current_map,
75 static_cast<zelda3::AreaSizeEnum>(current_area_size));
76 if (status.ok()) {
77 RefreshSiblingMapGraphics(current_map, true);
79 }
80 }
81 } else {
82 // Vanilla/v1/v2 ROM: Show only Small/Large (first 2 options)
83 const char* limited_names[] = {"Small (1x1)", "Large (2x2)"};
84 int limited_size = (current_area_size == 0 || current_area_size == 1) ? current_area_size : 0;
85
86 if (ImGui::Combo("##AreaSize", &limited_size, limited_names, 2)) {
87 // limited_size is 0 (Small) or 1 (Large)
88 auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea
90 auto status = overworld_->ConfigureMultiAreaMap(current_map, size);
91 if (status.ok()) {
92 RefreshSiblingMapGraphics(current_map, true);
94 }
95 }
96
97 if (asm_version == 0xFF || asm_version < 3) {
98 HOVER_HINT("Small (1x1) and Large (2x2) maps. Wide/Tall require v3+");
99 }
100 }
101
102 TableNextColumn();
103 if (ImGui::Button(current_map_lock ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN,
104 ImVec2(40, 0))) {
105 current_map_lock = !current_map_lock;
106 }
107 HOVER_HINT(current_map_lock ? "Unlock Map" : "Lock Map");
108
109 TableNextColumn();
110 if (ImGui::Button(ICON_MD_IMAGE " GFX", ImVec2(kTableButtonGraphics, 0))) {
111 ImGui::OpenPopup("GraphicsPopup");
112 }
113 if (ImGui::IsItemHovered()) {
114 ImGui::SetTooltip(
115 "Graphics Settings\n\n"
116 "Configure:\n"
117 " • Area graphics (tileset)\n"
118 " • Sprite graphics sheets\n"
119 " • Animated graphics (v3+)\n"
120 " • Custom tile16 sheets (8 slots)");
121 }
122 DrawGraphicsPopup(current_map, game_state);
123
124 TableNextColumn();
125 if (ImGui::Button(ICON_MD_PALETTE " Palettes", ImVec2(kTableButtonPalettes, 0))) {
126 ImGui::OpenPopup("PalettesPopup");
127 }
128 if (ImGui::IsItemHovered()) {
129 ImGui::SetTooltip(
130 "Palette Settings\n\n"
131 "Configure:\n"
132 " • Area palette (background colors)\n"
133 " • Main palette (v2+)\n"
134 " • Sprite palettes\n"
135 " • Custom background colors");
136 }
137 DrawPalettesPopup(current_map, game_state, show_custom_bg_color_editor);
138
139 TableNextColumn();
140 if (ImGui::Button(ICON_MD_TUNE " Config", ImVec2(kTableButtonProperties, 0))) {
141 ImGui::OpenPopup("ConfigPopup");
142 }
143 if (ImGui::IsItemHovered()) {
144 ImGui::SetTooltip(
145 "Area Configuration\n\n"
146 "Quick access to:\n"
147 " • Message ID\n"
148 " • Game state settings\n"
149 " • Area size (v3+)\n"
150 " • Mosaic effects\n"
151 " • Visual effect overlays\n"
152 " • Map overlay info\n\n"
153 "Click 'Full Configuration Panel' for\n"
154 "comprehensive editing with all tabs.");
155 }
156 DrawPropertiesPopup(current_map, show_map_properties_panel,
157 show_overlay_preview, game_state);
158
159 TableNextColumn();
160 // View Controls
161 if (ImGui::Button(ICON_MD_VISIBILITY " View", ImVec2(kTableButtonView, 0))) {
162 ImGui::OpenPopup("ViewPopup");
163 }
164 if (ImGui::IsItemHovered()) {
165 ImGui::SetTooltip(
166 "View Controls\n\n"
167 "Canvas controls:\n"
168 " • Zoom in/out\n"
169 " • Toggle fullscreen\n"
170 " • Reset view");
171 }
173
174 TableNextColumn();
175 // Quick Access Tools
176 if (ImGui::Button(ICON_MD_BOLT " Quick", ImVec2(kTableButtonQuick, 0))) {
177 ImGui::OpenPopup("QuickPopup");
178 }
179 if (ImGui::IsItemHovered()) {
180 ImGui::SetTooltip(
181 "Quick Access Tools\n\n"
182 "Shortcuts to:\n"
183 " • Tile16 editor (Ctrl+T)\n"
184 " • Copy current map\n"
185 " • Lock/unlock map (Ctrl+L)");
186 }
188
189 ImGui::EndTable();
190 }
191}
192
194 int current_map, bool& show_map_properties_panel) {
195 (void)show_map_properties_panel; // Used by caller for window state
196 if (!overworld_->is_loaded()) {
197 Text("No overworld loaded");
198 return;
199 }
200
201 // Header with map info and lock status
202 ImGui::BeginGroup();
203 Text("Current Map: %d (0x%02X)", current_map, current_map);
204 ImGui::EndGroup();
205
206 Separator();
207
208 // Create tabs for different property categories
209 if (ImGui::BeginTabBar("MapPropertiesTabs",
210 ImGuiTabBarFlags_FittingPolicyScroll)) {
211
212 // Basic Properties Tab
213 if (ImGui::BeginTabItem("Basic Properties")) {
214 DrawBasicPropertiesTab(current_map);
215 ImGui::EndTabItem();
216 }
217
218 // Sprite Properties Tab
219 if (ImGui::BeginTabItem("Sprite Properties")) {
220 DrawSpritePropertiesTab(current_map);
221 ImGui::EndTabItem();
222 }
223
224 // Custom Overworld Features Tab
225 uint8_t asm_version =
227 if (asm_version != 0xFF && ImGui::BeginTabItem("Custom Features")) {
228 DrawCustomFeaturesTab(current_map);
229 ImGui::EndTabItem();
230 }
231
232 // Tile Graphics Tab
233 if (ImGui::BeginTabItem("Tile Graphics")) {
234 DrawTileGraphicsTab(current_map);
235 ImGui::EndTabItem();
236 }
237
238 // Music Tab
239 if (ImGui::BeginTabItem("Music")) {
240 DrawMusicTab(current_map);
241 ImGui::EndTabItem();
242 }
243
244 ImGui::EndTabBar();
245 }
246}
247
249 int current_map, bool& show_custom_bg_color_editor) {
250 (void)show_custom_bg_color_editor; // Used by caller for window state
251 if (!overworld_->is_loaded()) {
252 Text("No overworld loaded");
253 return;
254 }
255
256 uint8_t asm_version =
258 if (asm_version < 2) {
259 Text("Custom background colors require ZSCustomOverworld v2+");
260 return;
261 }
262
263 Text("Custom Background Color Editor");
264 Separator();
265
266 // Enable/disable area-specific background color
267 static bool use_area_specific_bg_color = false;
268 if (ImGui::Checkbox("Use Area-Specific Background Color",
269 &use_area_specific_bg_color)) {
270 // Update ROM data
272 use_area_specific_bg_color ? 1 : 0;
273 }
274
275 if (use_area_specific_bg_color) {
276 // Get current color
277 uint16_t current_color =
278 overworld_->overworld_map(current_map)->area_specific_bg_color();
279 gfx::SnesColor snes_color(current_color);
280
281 // Convert to ImVec4 for color picker
282 ImVec4 color_vec = gui::ConvertSnesColorToImVec4(snes_color);
283
284 if (ImGui::ColorPicker4(
285 "Background Color", (float*)&color_vec,
286 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex)) {
287 // Convert back to SNES color and update
288 gfx::SnesColor new_snes_color = gui::ConvertImVec4ToSnesColor(color_vec);
290 ->set_area_specific_bg_color(new_snes_color.snes());
291
292 // Update ROM
293 int rom_address =
295 (*rom_)[rom_address] = new_snes_color.snes() & 0xFF;
296 (*rom_)[rom_address + 1] = (new_snes_color.snes() >> 8) & 0xFF;
297 }
298
299 Text("SNES Color: 0x%04X", current_color);
300 }
301}
302
304 bool& show_overlay_editor) {
305 (void)show_overlay_editor; // Used by caller for window state
306 if (!overworld_->is_loaded()) {
307 Text("No overworld loaded");
308 return;
309 }
310
311 uint8_t asm_version =
313
314 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
315 ICON_MD_LAYERS " Visual Effects Configuration");
316 ImGui::Text("Map: 0x%02X", current_map);
317 Separator();
318
319 if (asm_version < 1) {
320 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.4f, 1.0f),
321 ICON_MD_WARNING " Subscreen overlays require ZSCustomOverworld v1+");
322 ImGui::Separator();
323 ImGui::TextWrapped(
324 "To use visual effect overlays, you need to upgrade your ROM to "
325 "ZSCustomOverworld. This feature allows you to add atmospheric effects "
326 "like fog, rain, forest canopy, and sky backgrounds to your maps.");
327 return;
328 }
329
330 // Help section
331 if (ImGui::CollapsingHeader(ICON_MD_HELP_OUTLINE " What are Visual Effects?",
332 ImGuiTreeNodeFlags_DefaultOpen)) {
333 ImGui::Indent();
334 ImGui::TextWrapped(
335 "Visual effects (subscreen overlays) are semi-transparent layers drawn "
336 "on top of or behind your map. They reference special area maps (0x80-0x9F) "
337 "for their tile16 graphics data.");
338 ImGui::Spacing();
339 ImGui::Text("Common uses:");
340 ImGui::BulletText("Fog effects (Lost Woods, Skull Woods)");
341 ImGui::BulletText("Rain (Misery Mire)");
342 ImGui::BulletText("Forest canopy (Lost Woods)");
343 ImGui::BulletText("Sky backgrounds (Death Mountain)");
344 ImGui::BulletText("Under bridge views");
345 ImGui::Unindent();
346 ImGui::Separator();
347 }
348
349 // Enable/disable subscreen overlay
350 static bool use_subscreen_overlay = false;
351 if (ImGui::Checkbox(ICON_MD_VISIBILITY " Enable Visual Effect for This Area",
352 &use_subscreen_overlay)) {
353 // Update ROM data
355 use_subscreen_overlay ? 1 : 0;
356 }
357 if (ImGui::IsItemHovered()) {
358 ImGui::SetTooltip("Enable/disable visual effect overlay for this map area");
359 }
360
361 if (use_subscreen_overlay) {
362 ImGui::Spacing();
363 uint16_t current_overlay =
364 overworld_->overworld_map(current_map)->subscreen_overlay();
365 if (gui::InputHexWord(ICON_MD_PHOTO " Visual Effect Map ID", &current_overlay,
366 kInputFieldSize + 30)) {
368 ->set_subscreen_overlay(current_overlay);
369
370 // Update ROM
371 int rom_address =
373 (*rom_)[rom_address] = current_overlay & 0xFF;
374 (*rom_)[rom_address + 1] = (current_overlay >> 8) & 0xFF;
375 }
376 if (ImGui::IsItemHovered()) {
377 ImGui::SetTooltip(
378 "ID of the special area map (0x80-0x9F) to use for\n"
379 "visual effects. That map's tile16 data will be drawn\n"
380 "as a semi-transparent layer on this area.");
381 }
382
383 // Show description
384 std::string overlay_desc = GetOverlayDescription(current_overlay);
385 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
386 ICON_MD_INFO " %s", overlay_desc.c_str());
387
388 ImGui::Separator();
389 if (ImGui::CollapsingHeader(ICON_MD_LIGHTBULB " Common Visual Effect IDs")) {
390 ImGui::Indent();
391 ImGui::BulletText("0x0093 - Triforce Room Curtain");
392 ImGui::BulletText("0x0094 - Under the Bridge");
393 ImGui::BulletText("0x0095 - Sky Background (LW Death Mountain)");
394 ImGui::BulletText("0x0096 - Pyramid Background");
395 ImGui::BulletText("0x0097 - Fog Overlay (Master Sword Area)");
396 ImGui::BulletText("0x009C - Lava Background (DW Death Mountain)");
397 ImGui::BulletText("0x009D - Fog Overlay (Lost/Skull Woods)");
398 ImGui::BulletText("0x009E - Tree Canopy (Forest)");
399 ImGui::BulletText("0x009F - Rain Effect (Misery Mire)");
400 ImGui::BulletText("0x00FF - No Overlay (Disabled)");
401 ImGui::Unindent();
402 }
403 } else {
404 ImGui::Spacing();
405 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
406 ICON_MD_BLOCK " No visual effects enabled for this area");
407 }
408}
409
411 gui::Canvas& canvas, int current_map, bool current_map_lock,
412 bool& show_map_properties_panel, bool& show_custom_bg_color_editor,
413 bool& show_overlay_editor) {
414 (void)current_map; // Used for future context-sensitive menu items
415 // Clear any existing context menu items
416 canvas.ClearContextMenuItems();
417
418 // Add overworld-specific context menu items
420 lock_item.label = current_map_lock ? "Unlock Map" : "Lock to This Map";
421 lock_item.callback = [&current_map_lock]() {
422 current_map_lock = !current_map_lock;
423 };
424 canvas.AddContextMenuItem(lock_item);
425
426 // Area Configuration
427 gui::Canvas::ContextMenuItem properties_item;
428 properties_item.label = ICON_MD_TUNE " Area Configuration";
429 properties_item.callback = [&show_map_properties_panel]() {
430 show_map_properties_panel = true;
431 };
432 canvas.AddContextMenuItem(properties_item);
433
434 // Custom overworld features (only show if v3+)
435 uint8_t asm_version =
437 if (asm_version >= 3 && asm_version != 0xFF) {
438 // Custom Background Color
439 gui::Canvas::ContextMenuItem bg_color_item;
440 bg_color_item.label = ICON_MD_FORMAT_COLOR_FILL " Custom Background Color";
441 bg_color_item.callback = [&show_custom_bg_color_editor]() {
442 show_custom_bg_color_editor = true;
443 };
444 canvas.AddContextMenuItem(bg_color_item);
445
446 // Visual Effects Editor
447 gui::Canvas::ContextMenuItem overlay_item;
448 overlay_item.label = ICON_MD_LAYERS " Visual Effects";
449 overlay_item.callback = [&show_overlay_editor]() {
450 show_overlay_editor = true;
451 };
452 canvas.AddContextMenuItem(overlay_item);
453 }
454
455 // Canvas controls
456 gui::Canvas::ContextMenuItem reset_view_item;
457 reset_view_item.label = ICON_MD_RESTORE " Reset View";
458 reset_view_item.callback = [&canvas]() {
459 canvas.set_global_scale(1.0f);
460 canvas.set_scrolling(ImVec2(0, 0));
461 };
462 canvas.AddContextMenuItem(reset_view_item);
463
464 gui::Canvas::ContextMenuItem zoom_in_item;
465 zoom_in_item.label = ICON_MD_ZOOM_IN " Zoom In";
466 zoom_in_item.callback = [&canvas]() {
467 float scale = std::min(2.0f, canvas.global_scale() + 0.25f);
468 canvas.set_global_scale(scale);
469 };
470 canvas.AddContextMenuItem(zoom_in_item);
471
472 gui::Canvas::ContextMenuItem zoom_out_item;
473 zoom_out_item.label = ICON_MD_ZOOM_OUT " Zoom Out";
474 zoom_out_item.callback = [&canvas]() {
475 float scale = std::max(0.25f, canvas.global_scale() - 0.25f);
476 canvas.set_global_scale(scale);
477 };
478 canvas.AddContextMenuItem(zoom_out_item);
479
480 // Entity Operations submenu will be added in future iteration
481 // For now, users can use keyboard shortcuts (3-8) to activate entity editing
482}
483
484// Private method implementations
485void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
486 if (ImGui::BeginPopup("GraphicsPopup")) {
487 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
489 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
491
492 ImGui::Text("Graphics Settings");
493 ImGui::Separator();
494
495 // Area Graphics
496 if (gui::InputHexByte(ICON_MD_IMAGE " Area Graphics",
498 ->mutable_area_graphics(),
500 // CORRECT ORDER: Properties first, then graphics reload
501
502 // 1. Propagate properties to siblings FIRST (calls LoadAreaGraphics on siblings)
504
505 // 2. Force immediate refresh of current map
506 (*maps_bmp_)[current_map].set_modified(true);
507 overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
508
509 // 3. Refresh siblings immediately
510 RefreshSiblingMapGraphics(current_map);
511
512 // 4. Update tile selector
514
515 // 5. Final refresh
517 }
518 HOVER_HINT("Main tileset graphics for this map area");
519
520 // Sprite Graphics
522 absl::StrFormat(ICON_MD_PETS " Sprite GFX (%s)", kGameStateNames[game_state])
523 .c_str(),
525 ->mutable_sprite_graphics(game_state),
527 ForceRefreshGraphics(current_map);
530 }
531 HOVER_HINT("Sprite graphics sheet for current game state");
532
533 uint8_t asm_version =
535 if (asm_version >= 3) {
536 if (gui::InputHexByte(ICON_MD_ANIMATION " Animated GFX",
538 ->mutable_animated_gfx(),
540 ForceRefreshGraphics(current_map);
544 }
545 HOVER_HINT("Animated tile graphics (water, lava, etc.)");
546 }
547
548 // Custom Tile Graphics - Only available for v1+ ROMs
549 if (asm_version >= 1 && asm_version != 0xFF) {
550 ImGui::Separator();
551 ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics");
552 ImGui::Separator();
553
554 // Show the 8 custom graphics IDs in a 2-column layout for density
555 if (BeginTable("CustomTileGraphics", 2,
556 ImGuiTableFlags_SizingFixedFit)) {
557 for (int i = 0; i < 8; i++) {
558 TableNextColumn();
559 std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i);
560 if (gui::InputHexByte(label.c_str(),
562 ->mutable_custom_tileset(i),
563 90.f)) {
564 ForceRefreshGraphics(current_map);
568 }
569 if (ImGui::IsItemHovered()) {
570 ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
571 }
572 }
573 ImGui::EndTable();
574 }
575 } else if (asm_version == 0xFF) {
576 ImGui::Separator();
577 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
578 ICON_MD_INFO " Custom Tile Graphics");
579 ImGui::TextWrapped(
580 "Custom tile graphics require ZSCustomOverworld v1+.\n"
581 "Upgrade your ROM to access 8 customizable graphics sheets.");
582 }
583
584 ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
585 ImGui::EndPopup();
586 }
587}
588
589void MapPropertiesSystem::DrawPalettesPopup(int current_map, int game_state,
590 bool& show_custom_bg_color_editor) {
591 if (ImGui::BeginPopup("PalettesPopup")) {
592 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
594 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
596
597 ImGui::Text("Palette Settings");
598 ImGui::Separator();
599
600 // Area Palette
601 if (gui::InputHexByte(ICON_MD_PALETTE " Area Palette",
603 ->mutable_area_palette(),
606 auto status = RefreshMapPalette();
608 }
609 HOVER_HINT("Main color palette for background tiles");
610
611 // Read fresh to reflect ROM upgrades
612 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
613 if (asm_version >= 2) {
614 if (gui::InputHexByte(ICON_MD_COLOR_LENS " Main Palette",
616 ->mutable_main_palette(),
619 auto status = RefreshMapPalette();
621 }
622 HOVER_HINT("Extended main palette (ZSCustomOverworld v2+)");
623 }
624
625 // Sprite Palette
627 absl::StrFormat(ICON_MD_COLORIZE " Sprite Pal (%s)", kGameStateNames[game_state])
628 .c_str(),
630 ->mutable_sprite_palette(game_state),
634 }
635 HOVER_HINT("Color palette for sprites in current game state");
636
637 ImGui::Separator();
638 if (ImGui::Button(ICON_MD_FORMAT_COLOR_FILL " Custom Background Color",
639 ImVec2(-1, 0))) {
640 show_custom_bg_color_editor = !show_custom_bg_color_editor;
641 }
642 HOVER_HINT("Open custom background color editor (v2+)");
643
644 ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
645 ImGui::EndPopup();
646 }
647}
648
650 bool& show_map_properties_panel,
651 bool& show_overlay_preview,
652 int& game_state) {
653 if (ImGui::BeginPopup("ConfigPopup")) {
654 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
656 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
658
659 ImGui::Text(ICON_MD_TUNE " Area Configuration");
660 ImGui::Separator();
661
662 // Basic Properties in 2-column layout for density
663 if (BeginTable("BasicProps", 2, ImGuiTableFlags_SizingFixedFit)) {
664 // Message ID
665 TableNextColumn();
666 ImGui::Text(ICON_MD_MESSAGE " Message");
667 TableNextColumn();
668 if (gui::InputHexWordCustom("##MsgId",
670 ->mutable_message_id(),
674 }
675 if (ImGui::IsItemHovered()) {
676 ImGui::SetTooltip("Message ID shown when entering this area");
677 }
678
679 // Game State
680 TableNextColumn();
681 ImGui::Text(ICON_MD_GAMEPAD " Game State");
682 TableNextColumn();
683 ImGui::SetNextItemWidth(kComboGameStateWidth);
684 if (ImGui::Combo("##GameState", &game_state, kGameStateNames, 3)) {
687 }
688 if (ImGui::IsItemHovered()) {
689 ImGui::SetTooltip("Affects sprite graphics/palettes based on story progress");
690 }
691
692 ImGui::EndTable();
693 }
694
695 // Area Configuration Section
696 ImGui::Separator();
697 ImGui::Text(ICON_MD_ASPECT_RATIO " Area Configuration");
698 ImGui::Separator();
699
700 // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall.
701 uint8_t asm_version =
703
704 int current_area_size =
705 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
706 ImGui::SetNextItemWidth(kComboAreaSizeWidth);
707
708 if (asm_version >= 3 && asm_version != 0xFF) {
709 // v3+ ROM: Show all 4 area size options
710 if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", &current_area_size, kAreaSizeNames, 4)) {
711 auto status = overworld_->ConfigureMultiAreaMap(
712 current_map,
713 static_cast<zelda3::AreaSizeEnum>(current_area_size));
714 if (status.ok()) {
715 RefreshSiblingMapGraphics(current_map, true);
717 }
718 }
719 HOVER_HINT("Map area size (1x1, 2x2, 2x1, 1x2 screens)");
720 } else {
721 // Vanilla/v1/v2 ROM: Show only Small/Large
722 const char* limited_names[] = {"Small (1x1)", "Large (2x2)"};
723 int limited_size = (current_area_size == 0 || current_area_size == 1) ? current_area_size : 0;
724
725 if (ImGui::Combo(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Size", &limited_size, limited_names, 2)) {
726 auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea
728 auto status = overworld_->ConfigureMultiAreaMap(current_map, size);
729 if (status.ok()) {
730 RefreshSiblingMapGraphics(current_map, true);
732 }
733 }
734 HOVER_HINT("Small (1x1) and Large (2x2) maps. Wide/Tall require v3+");
735 }
736
737 // Visual Effects Section
738 ImGui::Separator();
739 ImGui::Text(ICON_MD_AUTO_FIX_HIGH " Visual Effects");
740 ImGui::Separator();
741
742 DrawMosaicControls(current_map);
743 DrawOverlayControls(current_map, show_overlay_preview);
744
745 // Advanced Options Section
746 ImGui::Separator();
747 if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Full Configuration Panel",
748 ImVec2(-1, 0))) {
749 show_map_properties_panel = true;
750 ImGui::CloseCurrentPopup();
751 }
752 HOVER_HINT("Open detailed area configuration with all settings tabs");
753
754 ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
755 ImGui::EndPopup();
756 }
757}
758
760 if (BeginTable("BasicProperties", 2,
761 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
762 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
763 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
764
765 TableNextColumn();
766 ImGui::Text(ICON_MD_IMAGE " Area Graphics");
767 TableNextColumn();
768 if (gui::InputHexByte("##AreaGfx",
770 ->mutable_area_graphics(),
772 // CORRECT ORDER: Properties first, then graphics reload
774 (*maps_bmp_)[current_map].set_modified(true);
775 overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
776 RefreshSiblingMapGraphics(current_map);
779 }
780 if (ImGui::IsItemHovered()) {
781 ImGui::SetTooltip("Main tileset graphics for this map area");
782 }
783
784 TableNextColumn();
785 ImGui::Text(ICON_MD_PALETTE " Area Palette");
786 TableNextColumn();
787 if (gui::InputHexByte("##AreaPal",
789 ->mutable_area_palette(),
792 auto status = RefreshMapPalette();
794 }
795 if (ImGui::IsItemHovered()) {
796 ImGui::SetTooltip("Color palette for background tiles");
797 }
798
799 TableNextColumn();
800 ImGui::Text(ICON_MD_MESSAGE " Message ID");
801 TableNextColumn();
802 if (gui::InputHexWord("##MsgId",
804 ->mutable_message_id(),
805 kInputFieldSize + 20)) {
808 }
809 if (ImGui::IsItemHovered()) {
810 ImGui::SetTooltip("Message displayed when entering this area");
811 }
812
813 TableNextColumn();
814 ImGui::Text(ICON_MD_BLUR_ON " Mosaic Effect");
815 TableNextColumn();
816 if (ImGui::Checkbox(
817 "##mosaic",
818 overworld_->mutable_overworld_map(current_map)->mutable_mosaic())) {
821 }
822 if (ImGui::IsItemHovered()) {
823 ImGui::SetTooltip("Enable pixelated mosaic transition effect");
824 }
825
826 // Add music editing controls with icons
827 TableNextColumn();
828 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Beginning)");
829 TableNextColumn();
830 if (gui::InputHexByte("##Music0",
832 ->mutable_area_music(0),
835 }
836 if (ImGui::IsItemHovered()) {
837 ImGui::SetTooltip("Music track before rescuing Zelda");
838 }
839
840 TableNextColumn();
841 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Zelda)");
842 TableNextColumn();
843 if (gui::InputHexByte("##Music1",
845 ->mutable_area_music(1),
848 }
849 if (ImGui::IsItemHovered()) {
850 ImGui::SetTooltip("Music track after rescuing Zelda");
851 }
852
853 TableNextColumn();
854 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Master Sword)");
855 TableNextColumn();
856 if (gui::InputHexByte("##Music2",
858 ->mutable_area_music(2),
861 }
862 if (ImGui::IsItemHovered()) {
863 ImGui::SetTooltip("Music track after obtaining Master Sword");
864 }
865
866 TableNextColumn();
867 ImGui::Text(ICON_MD_MUSIC_NOTE " Music (Agahnim)");
868 TableNextColumn();
869 if (gui::InputHexByte("##Music3",
871 ->mutable_area_music(3),
874 }
875 if (ImGui::IsItemHovered()) {
876 ImGui::SetTooltip("Music track after defeating Agahnim (Dark World)");
877 }
878
879 ImGui::EndTable();
880 }
881}
882
884 if (BeginTable("SpriteProperties", 2,
885 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
886 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
887 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
888
889 TableNextColumn();
890 ImGui::Text(ICON_MD_GAMEPAD " Game State");
891 TableNextColumn();
892 static int game_state = 0;
893 ImGui::SetNextItemWidth(120.f);
894 if (ImGui::Combo("##GameState", &game_state, kGameStateNames, 3)) {
897 }
898 if (ImGui::IsItemHovered()) {
899 ImGui::SetTooltip("Affects which sprite graphics/palettes are used");
900 }
901
902 TableNextColumn();
903 ImGui::Text(ICON_MD_PETS " Sprite Graphics 1");
904 TableNextColumn();
905 if (gui::InputHexByte("##SprGfx1",
907 ->mutable_sprite_graphics(1),
911 }
912 if (ImGui::IsItemHovered()) {
913 ImGui::SetTooltip("First sprite graphics sheet for Zelda rescued state");
914 }
915
916 TableNextColumn();
917 ImGui::Text(ICON_MD_PETS " Sprite Graphics 2");
918 TableNextColumn();
919 if (gui::InputHexByte("##SprGfx2",
921 ->mutable_sprite_graphics(2),
925 }
926 if (ImGui::IsItemHovered()) {
927 ImGui::SetTooltip("Second sprite graphics sheet for Master Sword obtained state");
928 }
929
930 TableNextColumn();
931 ImGui::Text(ICON_MD_COLORIZE " Sprite Palette 1");
932 TableNextColumn();
933 if (gui::InputHexByte("##SprPal1",
935 ->mutable_sprite_palette(1),
939 }
940 if (ImGui::IsItemHovered()) {
941 ImGui::SetTooltip("Color palette for sprites - Zelda rescued state");
942 }
943
944 TableNextColumn();
945 ImGui::Text(ICON_MD_COLORIZE " Sprite Palette 2");
946 TableNextColumn();
947 if (gui::InputHexByte("##SprPal2",
949 ->mutable_sprite_palette(2),
953 }
954 if (ImGui::IsItemHovered()) {
955 ImGui::SetTooltip("Color palette for sprites - Master Sword obtained state");
956 }
957
958 ImGui::EndTable();
959 }
960}
961
963 if (BeginTable("CustomFeatures", 2,
964 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
965 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
966 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
967
968 TableNextColumn();
969 ImGui::Text(ICON_MD_PHOTO_SIZE_SELECT_LARGE " Area Size");
970 TableNextColumn();
971 // ALL ROMs support Small/Large. Only v3+ supports Wide/Tall.
972 uint8_t asm_version =
974
975 int current_area_size =
976 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
977 ImGui::SetNextItemWidth(130.f);
978
979 if (asm_version >= 3 && asm_version != 0xFF) {
980 // v3+ ROM: Show all 4 area size options
981 static const char* all_sizes[] = {"Small (1x1)", "Large (2x2)",
982 "Wide (2x1)", "Tall (1x2)"};
983 if (ImGui::Combo("##AreaSize", &current_area_size, all_sizes, 4)) {
984 auto status = overworld_->ConfigureMultiAreaMap(
985 current_map,
986 static_cast<zelda3::AreaSizeEnum>(current_area_size));
987 if (status.ok()) {
988 RefreshSiblingMapGraphics(current_map, true);
990 }
991 }
992 if (ImGui::IsItemHovered()) {
993 ImGui::SetTooltip("Map size: Small (1x1), Large (2x2), Wide (2x1), Tall (1x2)");
994 }
995 } else {
996 // Vanilla/v1/v2 ROM: Show only Small/Large
997 static const char* limited_sizes[] = {"Small (1x1)", "Large (2x2)"};
998 int limited_size = (current_area_size == 0 || current_area_size == 1) ? current_area_size : 0;
999
1000 if (ImGui::Combo("##AreaSize", &limited_size, limited_sizes, 2)) {
1001 auto size = (limited_size == 1) ? zelda3::AreaSizeEnum::LargeArea
1003 auto status = overworld_->ConfigureMultiAreaMap(current_map, size);
1004 if (status.ok()) {
1005 RefreshSiblingMapGraphics(current_map, true);
1007 }
1008 }
1009 if (ImGui::IsItemHovered()) {
1010 ImGui::SetTooltip("Map size: Small (1x1), Large (2x2). Wide/Tall require v3+");
1011 }
1012 }
1013
1014 if (asm_version >= 2) {
1015 TableNextColumn();
1016 ImGui::Text(ICON_MD_COLOR_LENS " Main Palette");
1017 TableNextColumn();
1018 if (gui::InputHexByte("##MainPal",
1019 overworld_->mutable_overworld_map(current_map)
1020 ->mutable_main_palette(),
1021 kInputFieldSize)) {
1023 auto status = RefreshMapPalette();
1025 }
1026 if (ImGui::IsItemHovered()) {
1027 ImGui::SetTooltip("Extended main palette (ZSCustomOverworld v2+)");
1028 }
1029 }
1030
1031 if (asm_version >= 3) {
1032 TableNextColumn();
1033 ImGui::Text(ICON_MD_ANIMATION " Animated GFX");
1034 TableNextColumn();
1035 if (gui::InputHexByte("##AnimGfx",
1036 overworld_->mutable_overworld_map(current_map)
1037 ->mutable_animated_gfx(),
1038 kInputFieldSize)) {
1041 }
1042 if (ImGui::IsItemHovered()) {
1043 ImGui::SetTooltip("Animated tile graphics ID (water, lava, etc.)");
1044 }
1045
1046 TableNextColumn();
1047 ImGui::Text(ICON_MD_LAYERS " Subscreen Overlay");
1048 TableNextColumn();
1049 if (gui::InputHexWord("##SubOverlay",
1050 overworld_->mutable_overworld_map(current_map)
1051 ->mutable_subscreen_overlay(),
1052 kInputFieldSize + 20)) {
1055 }
1056 if (ImGui::IsItemHovered()) {
1057 ImGui::SetTooltip("Visual effects overlay ID (fog, rain, backgrounds)");
1058 }
1059 }
1060
1061 ImGui::EndTable();
1062 }
1063}
1064
1066 uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
1067
1068 // Only show custom tile graphics for v1+ ROMs
1069 if (asm_version >= 1 && asm_version != 0xFF) {
1070 ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics (8 sheets)");
1071 Separator();
1072
1073 if (BeginTable("TileGraphics", 2,
1074 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1075 ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
1076 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
1077
1078 for (int i = 0; i < 8; i++) {
1079 TableNextColumn();
1080 ImGui::Text(ICON_MD_LAYERS " Sheet %d", i);
1081 TableNextColumn();
1082 if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(),
1083 overworld_->mutable_overworld_map(current_map)
1084 ->mutable_custom_tileset(i),
1085 kInputFieldSize)) {
1086 overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
1087 ForceRefreshGraphics(current_map);
1088 RefreshSiblingMapGraphics(current_map);
1092 }
1093 if (ImGui::IsItemHovered()) {
1094 ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
1095 }
1096 }
1097
1098 ImGui::EndTable();
1099 }
1100
1101 Separator();
1102 ImGui::TextWrapped("These 8 sheets allow custom tile graphics per map. "
1103 "Each sheet references a graphics ID loaded into VRAM.");
1104 } else {
1105 // Vanilla ROM - show info message
1106 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1107 ICON_MD_INFO " Custom Tile Graphics");
1108 ImGui::Separator();
1109 ImGui::TextWrapped(
1110 "Custom tile graphics are not available in vanilla ROMs.\n\n"
1111 "To enable this feature, upgrade your ROM to ZSCustomOverworld v1+, "
1112 "which provides 8 customizable graphics sheets per map for advanced "
1113 "tileset customization.");
1114 }
1115}
1116
1118 ImGui::Text(ICON_MD_MUSIC_NOTE " Music Settings for Game States");
1119 Separator();
1120
1121 if (BeginTable("MusicSettings", 2,
1122 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1123 ImGui::TableSetupColumn("Game State", ImGuiTableColumnFlags_WidthFixed,
1124 220);
1125 ImGui::TableSetupColumn("Music Track ID",
1126 ImGuiTableColumnFlags_WidthStretch);
1127
1128 const char* music_state_names[] = {
1129 ICON_MD_PLAY_ARROW " Beginning (Pre-Zelda)",
1130 ICON_MD_FAVORITE " Zelda Rescued",
1131 ICON_MD_OFFLINE_BOLT " Master Sword Obtained",
1132 ICON_MD_CASTLE " Agahnim Defeated"};
1133
1134 const char* music_descriptions[] = {
1135 "Music before rescuing Zelda from the castle",
1136 "Music after rescuing Zelda from Hyrule Castle",
1137 "Music after obtaining the Master Sword from the Lost Woods",
1138 "Music after defeating Agahnim (Dark World music)"};
1139
1140 for (int i = 0; i < 4; i++) {
1141 TableNextColumn();
1142 ImGui::Text("%s", music_state_names[i]);
1143
1144 TableNextColumn();
1145 if (gui::InputHexByte(absl::StrFormat("##Music%d", i).c_str(),
1146 overworld_->mutable_overworld_map(current_map)
1147 ->mutable_area_music(i),
1148 kInputFieldSize)) {
1150
1151 // Update the ROM directly since music is not automatically saved
1152 int music_address = 0;
1153 switch (i) {
1154 case 0:
1155 music_address = zelda3::kOverworldMusicBeginning + current_map;
1156 break;
1157 case 1:
1158 music_address = zelda3::kOverworldMusicZelda + current_map;
1159 break;
1160 case 2:
1161 music_address = zelda3::kOverworldMusicMasterSword + current_map;
1162 break;
1163 case 3:
1164 music_address = zelda3::kOverworldMusicAgahnim + current_map;
1165 break;
1166 }
1167
1168 if (music_address > 0) {
1169 (*rom_)[music_address] =
1170 *overworld_->mutable_overworld_map(current_map)
1171 ->mutable_area_music(i);
1172 }
1173 }
1174 if (ImGui::IsItemHovered()) {
1175 ImGui::SetTooltip("%s", music_descriptions[i]);
1176 }
1177 }
1178
1179 ImGui::EndTable();
1180 }
1181
1182 Separator();
1183 ImGui::TextWrapped("Music tracks control the background music for different "
1184 "game progression states on this overworld map.");
1185
1186 // Show common music track IDs for reference in a collapsing section
1187 Separator();
1188 if (ImGui::CollapsingHeader(ICON_MD_HELP_OUTLINE " Common Music Track IDs",
1189 ImGuiTreeNodeFlags_DefaultOpen)) {
1190 ImGui::Indent();
1191 ImGui::BulletText("0x02 - Overworld Theme");
1192 ImGui::BulletText("0x05 - Kakariko Village");
1193 ImGui::BulletText("0x07 - Lost Woods");
1194 ImGui::BulletText("0x09 - Dark World Theme");
1195 ImGui::BulletText("0x0F - Ganon's Tower");
1196 ImGui::BulletText("0x11 - Death Mountain");
1197 ImGui::Unindent();
1198 }
1199}
1200
1206
1212
1215 return refresh_map_palette_();
1216 }
1217 return absl::OkStatus();
1218}
1219
1222 return refresh_tile16_blockset_();
1223 }
1224 return absl::OkStatus();
1225}
1226
1229 force_refresh_graphics_(map_index);
1230 }
1231}
1232
1233void MapPropertiesSystem::RefreshSiblingMapGraphics(int map_index, bool include_self) {
1234 if (!overworld_ || !maps_bmp_ || map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
1235 return;
1236 }
1237
1238 auto* map = overworld_->mutable_overworld_map(map_index);
1239 if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) {
1240 return; // No siblings for small areas
1241 }
1242
1243 int parent_id = map->parent();
1244 std::vector<int> siblings;
1245
1246 switch (map->area_size()) {
1248 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1249 break;
1251 siblings = {parent_id, parent_id + 1};
1252 break;
1254 siblings = {parent_id, parent_id + 8};
1255 break;
1256 default:
1257 return;
1258 }
1259
1260 for (int sibling : siblings) {
1261 if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
1262 // Skip self unless include_self is true
1263 if (sibling == map_index && !include_self) {
1264 continue;
1265 }
1266
1267 // Mark as modified FIRST
1268 (*maps_bmp_)[sibling].set_modified(true);
1269
1270 // Load graphics from ROM
1271 overworld_->mutable_overworld_map(sibling)->LoadAreaGraphics();
1272
1273 // CRITICAL FIX: Force immediate refresh on the sibling
1274 // This will trigger the callback to OverworldEditor's RefreshChildMapOnDemand
1275 ForceRefreshGraphics(sibling);
1276 }
1277 }
1278
1279 // After marking all siblings, trigger a refresh
1280 // This ensures all marked maps get processed
1282}
1283
1285 uint8_t asm_version =
1287 if (asm_version >= 2) {
1288 ImGui::Separator();
1289 ImGui::Text("Mosaic Effects (per direction):");
1290
1291 auto* current_map_ptr = overworld_->mutable_overworld_map(current_map);
1292 std::array<bool, 4> mosaic_expanded = current_map_ptr->mosaic_expanded();
1293 const char* direction_names[] = {"North", "South", "East", "West"};
1294
1295 for (int i = 0; i < 4; i++) {
1296 if (ImGui::Checkbox(direction_names[i], &mosaic_expanded[i])) {
1297 current_map_ptr->set_mosaic_expanded(i, mosaic_expanded[i]);
1300 }
1301 }
1302 } else {
1303 if (ImGui::Checkbox(
1304 "Mosaic Effect",
1305 overworld_->mutable_overworld_map(current_map)->mutable_mosaic())) {
1308 }
1309 }
1310}
1311
1313 bool& show_overlay_preview) {
1314 uint8_t asm_version =
1316
1317 // Determine if this is a special overworld map (0x80-0x9F)
1318 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
1319
1320 if (is_special_overworld_map) {
1321 // Special overworld maps (0x80-0x9F) serve as visual effect sources
1322 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
1323 ICON_MD_INFO " Special Area Map (0x%02X)", current_map);
1324 ImGui::Separator();
1325 ImGui::TextWrapped(
1326 "This is a special area map (0x80-0x9F) used as a visual effect "
1327 "source. These maps provide the graphics data for subscreen overlays "
1328 "like fog, rain, forest canopy, and sky backgrounds that appear on "
1329 "normal maps (0x00-0x7F).");
1330 ImGui::Spacing();
1331 ImGui::TextWrapped(
1332 "You can edit the tile16 data here to customize how the visual effects "
1333 "appear when referenced by other maps.");
1334 } else {
1335 // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen overlays
1336
1337 // Comprehensive help section
1338 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
1339 ICON_MD_HELP_OUTLINE " Visual Effects Overview");
1340 ImGui::SameLine();
1341 if (ImGui::Button(ICON_MD_INFO "##HelpButton")) {
1342 ImGui::OpenPopup("OverlayTypesHelp");
1343 }
1344
1345 if (ImGui::BeginPopup("OverlayTypesHelp")) {
1346 ImGui::Text(ICON_MD_HELP " Understanding Overlay Types");
1347 ImGui::Separator();
1348
1349 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
1350 ICON_MD_LAYERS " 1. Subscreen Overlays (Visual Effects)");
1351 ImGui::Indent();
1352 ImGui::BulletText("Displayed as semi-transparent layers");
1353 ImGui::BulletText("Reference special area maps (0x80-0x9F)");
1354 ImGui::BulletText("Examples: fog, rain, forest canopy, sky");
1355 ImGui::BulletText("Purely visual - don't affect collision");
1356 ImGui::Unindent();
1357
1358 ImGui::Spacing();
1359 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
1360 ICON_MD_EDIT_NOTE " 2. Map Overlays (Interactive)");
1361 ImGui::Indent();
1362 ImGui::BulletText("Dynamic tile16 changes on the map");
1363 ImGui::BulletText("Used for bridges appearing, holes opening");
1364 ImGui::BulletText("Stored as tile16 ID arrays");
1365 ImGui::BulletText("Affect collision and interaction");
1366 ImGui::BulletText("Triggered by game events/progression");
1367 ImGui::Unindent();
1368
1369 ImGui::Separator();
1370 ImGui::TextWrapped(
1371 "Note: Subscreen overlays are what you configure here. "
1372 "Map overlays are event-driven and edited separately.");
1373
1374 ImGui::EndPopup();
1375 }
1376 ImGui::Separator();
1377
1378 // Subscreen Overlay Section
1379 ImGui::Text(ICON_MD_LAYERS " Subscreen Overlay (Visual Effects)");
1380
1381 uint16_t current_overlay =
1382 overworld_->mutable_overworld_map(current_map)->subscreen_overlay();
1383 if (gui::InputHexWord("Visual Effect Map ID", &current_overlay,
1384 kInputFieldSize + 20)) {
1385 overworld_->mutable_overworld_map(current_map)
1386 ->set_subscreen_overlay(current_overlay);
1389 }
1390 if (ImGui::IsItemHovered()) {
1391 ImGui::SetTooltip(
1392 "References a special area map (0x80-0x9F) for visual effects.\n"
1393 "The referenced map's tile16 data is drawn as a semi-transparent\n"
1394 "layer on top of or behind this area for atmospheric effects.");
1395 }
1396
1397 // Show subscreen overlay description with color coding
1398 std::string overlay_desc = GetOverlayDescription(current_overlay);
1399 if (current_overlay == 0x00FF) {
1400 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1401 ICON_MD_CHECK " %s", overlay_desc.c_str());
1402 } else if (current_overlay >= 0x80 && current_overlay < 0xA0) {
1403 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
1404 ICON_MD_VISIBILITY " %s", overlay_desc.c_str());
1405 } else {
1406 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
1407 ICON_MD_HELP_OUTLINE " %s", overlay_desc.c_str());
1408 }
1409
1410 // Preview checkbox with better labeling
1411 ImGui::Spacing();
1412 if (ImGui::Checkbox(ICON_MD_PREVIEW " Preview Visual Effect on Map",
1413 &show_overlay_preview)) {
1414 // Toggle subscreen overlay preview
1415 }
1416 if (ImGui::IsItemHovered()) {
1417 ImGui::SetTooltip(
1418 "Shows a semi-transparent preview of the visual effect overlay\n"
1419 "drawn on top of the current map in the editor canvas.\n\n"
1420 "This preview shows how the subscreen overlay will appear in-game.");
1421 }
1422
1423 ImGui::Separator();
1424
1425 // Interactive/Dynamic Map Overlay Section (for vanilla ROMs)
1426 if (asm_version == 0xFF) {
1427 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
1428 ICON_MD_EDIT_NOTE " Map Overlay (Interactive)");
1429 ImGui::SameLine();
1430 if (ImGui::Button(ICON_MD_INFO "##MapOverlayHelp")) {
1431 ImGui::OpenPopup("InteractiveOverlayHelp");
1432 }
1433 if (ImGui::BeginPopup("InteractiveOverlayHelp")) {
1434 ImGui::Text(ICON_MD_HELP " Map Overlays (Interactive Tile Changes)");
1435 ImGui::Separator();
1436 ImGui::TextWrapped(
1437 "Map overlays are different from visual effect overlays. "
1438 "They contain tile16 data that dynamically replaces tiles on "
1439 "the map based on game events or progression.");
1440 ImGui::Spacing();
1441 ImGui::Text("Common uses:");
1442 ImGui::BulletText("Bridges appearing over water");
1443 ImGui::BulletText("Holes revealing secret passages");
1444 ImGui::BulletText("Rocks/bushes being moved");
1445 ImGui::BulletText("Environmental changes from story events");
1446 ImGui::Spacing();
1447 ImGui::TextWrapped(
1448 "These are triggered by game code and stored as separate "
1449 "tile data arrays in the ROM. ZSCustomOverworld v3+ provides "
1450 "extended control over these features.");
1451 ImGui::EndPopup();
1452 }
1453
1454 auto* current_map_ptr = overworld_->overworld_map(current_map);
1455 ImGui::Spacing();
1456 if (current_map_ptr->has_overlay()) {
1457 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
1458 ICON_MD_CHECK " Overlay ID: 0x%04X",
1459 current_map_ptr->overlay_id());
1460 ImGui::Text(ICON_MD_STORAGE " Data Size: %d bytes",
1461 static_cast<int>(current_map_ptr->overlay_data().size()));
1462 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.4f, 1.0f),
1463 ICON_MD_INFO " Read-only in vanilla ROM");
1464 } else {
1465 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1466 ICON_MD_BLOCK " No map overlay data for this area");
1467 }
1468 }
1469
1470 // Show version and capability info
1471 ImGui::Separator();
1472 if (asm_version == 0xFF) {
1473 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
1474 ICON_MD_INFO " Vanilla ROM");
1475 ImGui::BulletText("Visual effects use maps 0x80-0x9F");
1476 ImGui::BulletText("Map overlays are read-only");
1477 } else {
1478 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
1479 ICON_MD_UPGRADE " ZSCustomOverworld v%d", asm_version);
1480 ImGui::BulletText("Enhanced visual effect control");
1481 if (asm_version >= 3) {
1482 ImGui::BulletText("Extended overlay system");
1483 ImGui::BulletText("Custom area sizes support");
1484 }
1485 }
1486 }
1487}
1488
1489std::string MapPropertiesSystem::GetOverlayDescription(uint16_t overlay_id) {
1490 if (overlay_id == 0x0093) {
1491 return "Triforce Room Curtain";
1492 } else if (overlay_id == 0x0094) {
1493 return "Under the Bridge";
1494 } else if (overlay_id == 0x0095) {
1495 return "Sky Background (LW Death Mountain)";
1496 } else if (overlay_id == 0x0096) {
1497 return "Pyramid Background";
1498 } else if (overlay_id == 0x0097) {
1499 return "First Fog Overlay (Master Sword Area)";
1500 } else if (overlay_id == 0x009C) {
1501 return "Lava Background (DW Death Mountain)";
1502 } else if (overlay_id == 0x009D) {
1503 return "Second Fog Overlay (Lost Woods/Skull Woods)";
1504 } else if (overlay_id == 0x009E) {
1505 return "Tree Canopy (Forest)";
1506 } else if (overlay_id == 0x009F) {
1507 return "Rain Effect (Misery Mire)";
1508 } else if (overlay_id == 0x00FF) {
1509 return "No Overlay";
1510 } else {
1511 return "Custom overlay";
1512 }
1513}
1514
1516 int current_world,
1517 bool show_overlay_preview) {
1518 gfx::ScopedTimer timer("map_properties_draw_overlay_preview");
1519
1520 if (!show_overlay_preview || !maps_bmp_ || !canvas_)
1521 return;
1522
1523 // Get subscreen overlay information based on ROM version and map type
1524 uint16_t overlay_id = 0x00FF;
1525 bool has_subscreen_overlay = false;
1526
1527 uint8_t asm_version =
1529 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
1530
1531 if (is_special_overworld_map) {
1532 // Special overworld maps (0x80-0x9F) do not support subscreen overlays
1533 return;
1534 }
1535
1536 // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps support subscreen overlays for all versions
1537 overlay_id = overworld_->overworld_map(current_map)->subscreen_overlay();
1538 has_subscreen_overlay = (overlay_id != 0x00FF);
1539
1540 if (!has_subscreen_overlay)
1541 return;
1542
1543 // Map subscreen overlay ID to special area map for bitmap
1544 int overlay_map_index = -1;
1545 if (overlay_id >= 0x80 && overlay_id < 0xA0) {
1546 overlay_map_index = overlay_id;
1547 }
1548
1549 if (overlay_map_index < 0 || overlay_map_index >= zelda3::kNumOverworldMaps)
1550 return;
1551
1552 // Get the subscreen overlay map's bitmap
1553 const auto& overlay_bitmap = (*maps_bmp_)[overlay_map_index];
1554 if (!overlay_bitmap.is_active())
1555 return;
1556
1557 // Calculate position for subscreen overlay preview on the current map
1558 int current_map_x = current_map % 8;
1559 int current_map_y = current_map / 8;
1560 if (current_world == 1) {
1561 current_map_x = (current_map - 0x40) % 8;
1562 current_map_y = (current_map - 0x40) / 8;
1563 } else if (current_world == 2) {
1564 current_map_x = (current_map - 0x80) % 8;
1565 current_map_y = (current_map - 0x80) / 8;
1566 }
1567
1568 int scale = static_cast<int>(canvas_->global_scale());
1569 int map_x = current_map_x * kOverworldMapSize * scale;
1570 int map_y = current_map_y * kOverworldMapSize * scale;
1571
1572 // Determine if this is a background or foreground subscreen overlay
1573 bool is_background_overlay =
1574 (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C);
1575
1576 // Set alpha for semi-transparent preview
1577 ImU32 overlay_color =
1578 is_background_overlay ? IM_COL32(255, 255, 255, 128)
1579 : // Background subscreen overlays - lighter
1580 IM_COL32(255, 255, 255,
1581 180); // Foreground subscreen overlays - more opaque
1582
1583 // Draw the subscreen overlay bitmap with semi-transparency
1584 canvas_->draw_list()->AddImage(
1585 (ImTextureID)(intptr_t)overlay_bitmap.texture(), ImVec2(map_x, map_y),
1586 ImVec2(map_x + kOverworldMapSize * scale,
1587 map_y + kOverworldMapSize * scale),
1588 ImVec2(0, 0), ImVec2(1, 1), overlay_color);
1589}
1590
1592 if (ImGui::BeginPopup("ViewPopup")) {
1593 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
1595 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
1597
1598 ImGui::Text("View Controls");
1599 ImGui::Separator();
1600
1601 // Horizontal layout for view controls
1602 if (ImGui::Button(ICON_MD_ZOOM_OUT, ImVec2(kIconButtonWidth, 0))) {
1603 // This would need to be connected to the canvas zoom function
1604 // For now, just show the option
1605 }
1606 HOVER_HINT("Zoom out on the canvas");
1607 ImGui::SameLine();
1608 if (ImGui::Button(ICON_MD_ZOOM_IN, ImVec2(kIconButtonWidth, 0))) {
1609 // This would need to be connected to the canvas zoom function
1610 // For now, just show the option
1611 }
1612 HOVER_HINT("Zoom in on the canvas");
1613 ImGui::SameLine();
1614 if (ImGui::Button(ICON_MD_OPEN_IN_FULL, ImVec2(kIconButtonWidth, 0))) {
1615 // This would need to be connected to the fullscreen toggle
1616 // For now, just show the option
1617 }
1618 HOVER_HINT("Toggle fullscreen canvas (F11)");
1619
1620 ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
1621 ImGui::EndPopup();
1622 }
1623}
1624
1626 if (ImGui::BeginPopup("QuickPopup")) {
1627 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
1629 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
1631
1632 ImGui::Text("Quick Access");
1633 ImGui::Separator();
1634
1635 // Horizontal layout for quick access buttons
1636 if (ImGui::Button(ICON_MD_GRID_VIEW, ImVec2(kIconButtonWidth, 0))) {
1637 // This would need to be connected to the Tile16 editor toggle
1638 // For now, just show the option
1639 }
1640 HOVER_HINT("Open Tile16 Editor (Ctrl+T)");
1641 ImGui::SameLine();
1642
1643 if (ImGui::Button(ICON_MD_CONTENT_COPY, ImVec2(kIconButtonWidth, 0))) {
1644 // This would need to be connected to the copy map function
1645 // For now, just show the option
1646 }
1647 HOVER_HINT("Copy current map to clipboard");
1648 ImGui::SameLine();
1649
1650 if (ImGui::Button(ICON_MD_LOCK, ImVec2(kIconButtonWidth, 0))) {
1651 // This would need to be connected to the map lock toggle
1652 // For now, just show the option
1653 }
1654 HOVER_HINT("Lock/unlock current map (Ctrl+L)");
1655
1656 ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
1657 ImGui::EndPopup();
1658 }
1659}
1660
1661} // namespace editor
1662} // namespace yaze
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > * maps_bmp_
void DrawOverlayEditor(int current_map, bool &show_overlay_editor)
void DrawPropertiesPopup(int current_map, bool &show_map_properties_panel, bool &show_overlay_preview, int &game_state)
RefreshPaletteCallback refresh_tile16_blockset_
void DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview)
void DrawMosaicControls(int current_map)
void DrawMapPropertiesPanel(int current_map, bool &show_map_properties_panel)
void DrawSpritePropertiesTab(int current_map)
RefreshPaletteCallback refresh_map_palette_
void DrawBasicPropertiesTab(int current_map)
void DrawCustomFeaturesTab(int current_map)
void DrawPalettesPopup(int current_map, int game_state, bool &show_custom_bg_color_editor)
void DrawCustomBackgroundColorEditor(int current_map, bool &show_custom_bg_color_editor)
void SetupCanvasContextMenu(gui::Canvas &canvas, int current_map, bool current_map_lock, bool &show_map_properties_panel, bool &show_custom_bg_color_editor, bool &show_overlay_editor)
void RefreshSiblingMapGraphics(int map_index, bool include_self=false)
void DrawTileGraphicsTab(int current_map)
ForceRefreshGraphicsCallback force_refresh_graphics_
std::string GetOverlayDescription(uint16_t overlay_id)
void DrawSimplifiedMapSettings(int &current_world, int &current_map, bool &current_map_lock, bool &show_map_properties_panel, bool &show_custom_bg_color_editor, bool &show_overlay_editor, bool &show_overlay_preview, int &game_state, int &current_mode)
void DrawOverlayControls(int current_map, bool &show_overlay_preview)
void DrawGraphicsPopup(int current_map, int game_state)
RAII timer for automatic timing management.
SNES Color container.
Definition snes_color.h:38
constexpr uint16_t snes() const
Definition snes_color.h:76
Modern, robust canvas for drawing and manipulating graphics.
Definition canvas.h:54
void set_scrolling(ImVec2 scroll)
Definition canvas.h:312
auto global_scale() const
Definition canvas.h:345
auto draw_list() const
Definition canvas.h:309
void ClearContextMenuItems()
Definition canvas.cc:616
void set_global_scale(float scale)
Definition canvas.h:315
void AddContextMenuItem(const ContextMenuItem &item)
Definition canvas.cc:612
auto is_loaded() const
Definition overworld.h:287
auto overworld_map(int i) const
Definition overworld.h:258
auto mutable_overworld_map(int i)
Definition overworld.h:259
absl::Status ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size)
Configure a multi-area map structure (Large/Wide/Tall)
Definition overworld.cc:257
#define ICON_MD_BLOCK
Definition icons.h:267
#define ICON_MD_GRID_VIEW
Definition icons.h:895
#define ICON_MD_COLORIZE
Definition icons.h:439
#define ICON_MD_INFO
Definition icons.h:991
#define ICON_MD_STORAGE
Definition icons.h:1863
#define ICON_MD_WARNING
Definition icons.h:2121
#define ICON_MD_PETS
Definition icons.h:1429
#define ICON_MD_UPGRADE
Definition icons.h:2045
#define ICON_MD_LOCK_OPEN
Definition icons.h:1140
#define ICON_MD_LOCK
Definition icons.h:1138
#define ICON_MD_LIGHTBULB
Definition icons.h:1081
#define ICON_MD_PHOTO_SIZE_SELECT_LARGE
Definition icons.h:1457
#define ICON_MD_OFFLINE_BOLT
Definition icons.h:1342
#define ICON_MD_PLAY_ARROW
Definition icons.h:1477
#define ICON_MD_CHECK
Definition icons.h:395
#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_MESSAGE
Definition icons.h:1199
#define ICON_MD_VISIBILITY
Definition icons.h:2099
#define ICON_MD_FORMAT_COLOR_FILL
Definition icons.h:828
#define ICON_MD_CASTLE
Definition icons.h:378
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:216
#define ICON_MD_MUSIC_NOTE
Definition icons.h:1262
#define ICON_MD_RESTORE
Definition icons.h:1603
#define ICON_MD_ASPECT_RATIO
Definition icons.h:190
#define ICON_MD_LAYERS
Definition icons.h:1066
#define ICON_MD_ANIMATION
Definition icons.h:155
#define ICON_MD_BOLT
Definition icons.h:280
#define ICON_MD_BLUR_ON
Definition icons.h:279
#define ICON_MD_IMAGE
Definition icons.h:980
#define ICON_MD_PREVIEW
Definition icons.h:1510
#define ICON_MD_FAVORITE
Definition icons.h:725
#define ICON_MD_HELP_OUTLINE
Definition icons.h:933
#define ICON_MD_ZOOM_IN
Definition icons.h:2192
#define ICON_MD_EDIT_NOTE
Definition icons.h:648
#define ICON_MD_PALETTE
Definition icons.h:1368
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1352
#define ICON_MD_CONTENT_COPY
Definition icons.h:463
#define ICON_MD_COLOR_LENS
Definition icons.h:438
#define ICON_MD_PHOTO
Definition icons.h:1449
#define ICON_MD_GAMEPAD
Definition icons.h:864
#define ICON_MD_HELP
Definition icons.h:931
#define HOVER_HINT(string)
Definition macro.h:24
constexpr const char * kAreaSizeNames[]
constexpr float kCompactItemSpacing
constexpr float kTableColumnView
constexpr unsigned int kOverworldMapSize
constexpr float kTableColumnQuick
constexpr float kTableColumnWorld
constexpr float kTableButtonView
constexpr float kTableColumnGraphics
constexpr const char * kWorldNames[]
constexpr float kHexByteInputWidth
constexpr float kTableButtonQuick
constexpr float kTableColumnPalettes
constexpr float kTableButtonProperties
constexpr float kComboWorldWidth
constexpr float kTableColumnMap
constexpr float kCompactFramePadding
constexpr float kTableColumnAreaSize
constexpr float kTableButtonPalettes
constexpr float kComboGameStateWidth
constexpr float kInputFieldSize
Definition entity.cc:19
constexpr float kTableButtonGraphics
constexpr const char * kGameStateNames[]
Definition ui_constants.h:8
constexpr float kIconButtonWidth
constexpr float kHexWordInputWidth
constexpr float kComboAreaSizeWidth
constexpr float kTableColumnLock
constexpr float kTableColumnProperties
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
Definition input.cc:175
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Definition color.cc:10
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
Definition input.cc:492
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Definition color.cc:15
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
Definition input.cc:189
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kNumOverworldMaps
Definition overworld.h:119
constexpr int kOverworldMusicBeginning
Definition overworld.h:43
constexpr int OverworldCustomASMHasBeenApplied
constexpr int kOverworldMusicAgahnim
Definition overworld.h:46
constexpr int kOverworldMusicMasterSword
Definition overworld.h:45
constexpr int kOverworldMusicZelda
Definition overworld.h:44
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int OverworldCustomSubscreenOverlayArray
Main namespace for the application.
SNES color in 15-bit RGB format (BGR555)
Definition yaze.h:208
std::function< void()> callback
Definition canvas.h:155