9#include "absl/status/statusor.h"
10#include "absl/strings/str_format.h"
24#include "imgui/imgui.h"
32using ImGui::BeginTable;
34using ImGui::Separator;
35using ImGui::TableNextColumn;
53 const int parent = map->parent();
63 return absl::FailedPreconditionError(
"ROM is not loaded");
71 return absl::FailedPreconditionError(
72 "Wide and Tall areas require ZSCustomOverworld v3+");
74 return absl::OkStatus();
79 return absl::FailedPreconditionError(
80 "This field requires ZSCustomOverworld v2+");
82 return absl::OkStatus();
85 return absl::FailedPreconditionError(
86 "Animated GFX requires ZSCustomOverworld v3+");
88 return absl::OkStatus();
91 return absl::FailedPreconditionError(
92 "Custom tile graphics require ZSCustomOverworld v1+");
94 return absl::OkStatus();
97 return absl::FailedPreconditionError(
98 "Visual effect editing requires ZSCustomOverworld v1+");
100 return absl::OkStatus();
102 return absl::OkStatus();
130 if (!rom || address < 0 ||
static_cast<size_t>(address) >= rom->
size()) {
133 (*rom)[address] = value;
138 if (!rom || address < 0 ||
static_cast<size_t>(address + 1) >= rom->
size()) {
141 (*rom)[address] = value & 0xFF;
142 (*rom)[address + 1] = (value >> 8) & 0xFF;
157 int source_map_id = map_id;
159 source_map_id = selected_map->parent();
166 clipboard.
valid =
true;
168 clipboard.
area_size =
static_cast<int>(map->area_size());
178 for (
int i = 0; i < 3; ++i) {
182 for (
int i = 0; i < 4; ++i) {
183 clipboard.
music[i] = map->area_music(i);
185 for (
int i = 0; i < 8; ++i) {
202 const int clamped_state = std::clamp(game_state, 0, 2);
221 int& current_world,
int& current_map,
bool& current_map_lock,
222 bool& show_map_properties_panel,
bool& show_custom_bg_color_editor,
223 bool& show_overlay_editor,
bool& show_overlay_preview,
int& game_state,
225 (void)show_overlay_editor;
226 (void)show_custom_bg_color_editor;
228 (void)show_overlay_preview;
231 if (BeginTable(
"MapPropertiesCanvasToolbar", 7,
232 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit,
234 ImGui::TableSetupColumn(
"World", ImGuiTableColumnFlags_WidthFixed,
236 ImGui::TableSetupColumn(
"Map", ImGuiTableColumnFlags_WidthFixed,
238 ImGui::TableSetupColumn(
"Area Size", ImGuiTableColumnFlags_WidthFixed,
240 ImGui::TableSetupColumn(
"Lock", ImGuiTableColumnFlags_WidthFixed,
242 ImGui::TableSetupColumn(
"Mode", ImGuiTableColumnFlags_WidthFixed,
244 ImGui::TableSetupColumn(
245 "Entity", ImGuiTableColumnFlags_WidthStretch);
246 ImGui::TableSetupColumn(
"Sidebar", ImGuiTableColumnFlags_WidthFixed, 40.0f);
249 const int clamped_world = std::clamp(current_world, 0, 2);
250 ImGui::TextDisabled(
"%s",
kWorldNames[clamped_world]);
253 ImGui::Text(tr(
"%d (0x%02X)"), current_map, current_map);
260 int current_area_size =
266 if (ImGui::Combo(
"##AreaSize", ¤t_area_size,
kAreaSizeNames, 4)) {
272 const char* limited_names[] = {
"Small (1x1)",
"Large (2x2)"};
273 int limited_size = (current_area_size == 0 || current_area_size == 1)
277 if (ImGui::Combo(
"##AreaSize", &limited_size, limited_names, 2)) {
284 HOVER_HINT(
"Small (1x1) and Large (2x2) maps. Wide/Tall require v3+");
291 current_map_lock = !current_map_lock;
293 HOVER_HINT(current_map_lock ?
"Unlock Map" :
"Lock Map");
304 HOVER_HINT(
"Mouse Mode (1)\nNavigate, pan, and manage entities");
313 HOVER_HINT(
"Tile Paint Mode (2)\nDraw tiles on the map");
319 const char* entity_icon =
"";
320 const char* entity_label =
"";
321 switch (entity_edit_mode) {
324 entity_label =
"Entrances";
328 entity_label =
"Exits";
332 entity_label =
"Items";
336 entity_label =
"Sprites";
340 entity_label =
"Transports";
344 entity_label =
"Music";
349 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%s %s", entity_icon,
356 show_map_properties_panel = !show_map_properties_panel;
365 int current_map,
bool& show_map_properties_panel) {
366 (void)show_map_properties_panel;
368 Text(tr(
"No overworld loaded"));
374 Text(tr(
"Current Map: %d (0x%02X)"), current_map, current_map);
381 ImGuiTabBarFlags_FittingPolicyScroll)) {
383 if (ImGui::BeginTabItem(tr(
"Basic Properties"))) {
389 if (ImGui::BeginTabItem(tr(
"Sprite Properties"))) {
397 ImGui::BeginTabItem(tr(
"Custom Features"))) {
403 if (ImGui::BeginTabItem(tr(
"Tile Graphics"))) {
409 if (ImGui::BeginTabItem(tr(
"Music"))) {
419 int current_map,
bool& show_custom_bg_color_editor) {
420 (void)show_custom_bg_color_editor;
422 Text(tr(
"No overworld loaded"));
428 Text(tr(
"Custom background colors require ZSCustomOverworld v2+"));
432 Text(tr(
"Custom Background Color Editor"));
436 bool use_area_specific_bg_color =
438 if (ImGui::Checkbox(tr(
"Use Area-Specific Background Color"),
439 &use_area_specific_bg_color)) {
442 use_area_specific_bg_color ? 0x01 : 0x00;
445 if (use_area_specific_bg_color) {
447 uint16_t current_color =
454 if (ImGui::ColorPicker4(
455 "Background Color", (
float*)&color_vec,
456 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex)) {
461 new_snes_color.
snes()});
464 Text(tr(
"SNES Color: 0x%04X"), current_color);
469 bool& show_overlay_editor) {
470 (void)show_overlay_editor;
472 Text(tr(
"No overworld loaded"));
478 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
480 ImGui::Text(tr(
"Map: 0x%02X"), current_map);
486 " Enhanced overlay editing requires ZSCustomOverworld v1+");
488 ImGui::TextWrapped(tr(
489 "Subscreen overlays are a vanilla feature used for atmospheric effects "
490 "like fog, rain, and forest canopy. ZSCustomOverworld expands this by "
491 "allowing per-area overlay configuration and additional "
498 ImGuiTreeNodeFlags_DefaultOpen)) {
500 ImGui::TextWrapped(tr(
501 "Visual effects (subscreen overlays) are semi-transparent layers drawn "
502 "on top of or behind your map. They reference special area maps "
504 "for their tile16 graphics data."));
506 ImGui::Text(tr(
"Common uses:"));
507 ImGui::BulletText(tr(
"Fog effects (Lost Woods, Skull Woods)"));
508 ImGui::BulletText(tr(
"Rain (Misery Mire)"));
509 ImGui::BulletText(tr(
"Forest canopy (Lost Woods)"));
510 ImGui::BulletText(tr(
"Sky backgrounds (Death Mountain)"));
511 ImGui::BulletText(tr(
"Under bridge views"));
517 bool use_subscreen_overlay =
520 &use_subscreen_overlay)) {
523 use_subscreen_overlay ? 0x01 : 0x00;
525 if (ImGui::IsItemHovered()) {
527 tr(
"Enable/disable visual effect overlay for this map area"));
530 if (use_subscreen_overlay) {
532 uint16_t current_overlay =
537 0, current_overlay});
539 if (ImGui::IsItemHovered()) {
541 tr(
"ID of the special area map (0x80-0x9F) to use for\n"
542 "visual effects. That map's tile16 data will be drawn\n"
543 "as a semi-transparent layer on this area."));
548 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
ICON_MD_INFO " %s",
549 overlay_desc.c_str());
553 " Common Visual Effect IDs")) {
555 ImGui::BulletText(tr(
"0x0093 - Triforce Room Curtain"));
556 ImGui::BulletText(tr(
"0x0094 - Under the Bridge"));
557 ImGui::BulletText(tr(
"0x0095 - Sky Background (LW Death Mountain)"));
558 ImGui::BulletText(tr(
"0x0096 - Pyramid Background"));
559 ImGui::BulletText(tr(
"0x0097 - Fog Overlay (Master Sword Area)"));
560 ImGui::BulletText(tr(
"0x009C - Lava Background (DW Death Mountain)"));
561 ImGui::BulletText(tr(
"0x009D - Fog Overlay (Lost/Skull Woods)"));
562 ImGui::BulletText(tr(
"0x009E - Tree Canopy (Forest)"));
563 ImGui::BulletText(tr(
"0x009F - Rain Effect (Misery Mire)"));
564 ImGui::BulletText(tr(
"0x00FF - No Overlay (Disabled)"));
569 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
ICON_MD_BLOCK
570 " No visual effects enabled for this area");
575 gui::Canvas& canvas,
int current_map,
bool& current_map_lock,
576 bool& show_map_properties_panel,
bool& show_custom_bg_color_editor,
618 const int parent = IsValidMapId(current_map_ptr->parent())
619 ? current_map_ptr->parent()
624 if (parent != current_map) {
626 select_parent_item.
label =
627 absl::StrFormat(
"Select Parent Map 0x%02X", parent);
628 select_parent_item.
callback = [
this, parent]() {
633 related_maps_menu.
subitems.push_back(select_parent_item);
637 if (map_id == current_map ||
638 (map_id == parent && parent != current_map)) {
642 if (!sibling || sibling->parent() != parent) {
649 ? absl::StrFormat(
"Select Parent Map 0x%02X", map_id)
650 : absl::StrFormat(
"Select Sibling Map 0x%02X", map_id);
651 sibling_item.
callback = [
this, map_id]() {
656 related_maps_menu.
subitems.push_back(sibling_item);
659 if (!related_maps_menu.
subitems.empty()) {
664 if (shared_clipboard) {
666 metadata_actions_menu.
label =
677 const char* copy_label,
678 const char* paste_label) {
680 copy_item.
label = copy_label;
681 copy_item.
callback = [
this, current_map, shared_clipboard, scope]() {
682 shared_clipboard->overworld_map_metadata =
683 CaptureMapMetadataClipboard(*
overworld_, current_map);
684 shared_clipboard->overworld_map_metadata.scope = scope;
685 shared_clipboard->has_overworld_map_metadata =
686 shared_clipboard->overworld_map_metadata.valid;
688 metadata_actions_menu.
subitems.push_back(copy_item);
691 paste_item.
label = paste_label;
693 return shared_clipboard->has_overworld_map_metadata &&
695 shared_clipboard->overworld_map_metadata, scope);
697 paste_item.
callback = [
this, current_map, shared_clipboard, scope]() {
699 current_map, shared_clipboard->overworld_map_metadata, scope);
703 "Paste %s from 0x%02X",
705 shared_clipboard->overworld_map_metadata.source_map_id));
709 metadata_actions_menu.
subitems.push_back(paste_item);
724 " Paste Music/Message Metadata");
730 const std::string popup_id =
731 absl::StrFormat(
"RenameOverworldMapLabelContext%02X", current_map);
734 [
this, project, current_map, initial_label = metadata.map_name,
736 static std::array<char, 128> label_buffer{};
737 static int active_map = -1;
738 static std::string error_message;
739 if (active_map != current_map) {
740 active_map = current_map;
741 error_message.clear();
742 std::strncpy(label_buffer.data(), initial_label.c_str(),
743 label_buffer.size() - 1);
744 label_buffer[label_buffer.size() - 1] =
'\0';
747 if (ImGui::BeginPopup(popup_id.c_str())) {
749 ImGui::TextDisabled(tr(
"Map 0x%02X"), current_map);
750 ImGui::SetNextItemWidth(260.0f);
751 ImGui::InputText(
"##OverworldContextMapLabel",
752 label_buffer.data(), label_buffer.size());
758 "overworld_map", current_map, label_buffer.data())
761 label_buffer.data());
764 ImGui::CloseCurrentPopup();
766 error_message = std::string(status.message());
779 ImGui::CloseCurrentPopup();
781 error_message = std::string(status.message());
784 if (!error_message.empty()) {
785 ImGui::TextWrapped(
"%s", error_message.c_str());
796 select_item.
callback = [
this, current_map]() {
797 if (map_selection_callback_) {
798 map_selection_callback_(current_map,
false);
801 canvas.AddContextMenuItem(select_item);
804 if (current_mode == 0 && entity_insert_callback_) {
805 gui::CanvasMenuItem entity_menu;
809 gui::CanvasMenuItem entrance_item;
811 entrance_item.callback = [
this]() {
812 if (entity_insert_callback_) {
813 entity_insert_callback_(
"entrance");
816 entity_menu.subitems.push_back(entrance_item);
819 gui::CanvasMenuItem hole_item;
821 hole_item.callback = [
this]() {
822 if (entity_insert_callback_) {
823 entity_insert_callback_(
"hole");
826 entity_menu.subitems.push_back(hole_item);
829 gui::CanvasMenuItem exit_item;
831 exit_item.callback = [
this]() {
832 if (entity_insert_callback_) {
833 entity_insert_callback_(
"exit");
836 entity_menu.subitems.push_back(exit_item);
839 gui::CanvasMenuItem item_item;
841 item_item.callback = [
this]() {
842 if (entity_insert_callback_) {
843 entity_insert_callback_(
"item");
846 entity_menu.subitems.push_back(item_item);
849 gui::CanvasMenuItem sprite_item;
851 sprite_item.callback = [
this]() {
852 if (entity_insert_callback_) {
853 entity_insert_callback_(
"sprite");
856 entity_menu.subitems.push_back(sprite_item);
858 canvas.AddContextMenuItem(entity_menu);
860 if (sample_tile16_callback_) {
861 gui::CanvasMenuItem tile16_sample_item;
863 tile16_sample_item.callback = [
this]() {
864 if (sample_tile16_callback_) {
865 (void)sample_tile16_callback_();
868 canvas.AddContextMenuItem(tile16_sample_item);
871 if (edit_tile16_callback_) {
872 gui::CanvasMenuItem tile16_edit_item;
874 tile16_edit_item.callback = [
this]() {
875 if (edit_tile16_callback_) {
876 edit_tile16_callback_();
879 canvas.AddContextMenuItem(tile16_edit_item);
884 gui::CanvasMenuItem lock_item;
887 lock_item.callback = [
this, current_map, ¤t_map_lock]() {
888 if (current_map_lock) {
889 current_map_lock =
false;
892 if (map_selection_callback_) {
893 map_selection_callback_(current_map,
false);
895 current_map_lock =
true;
897 canvas.AddContextMenuItem(lock_item);
900 gui::CanvasMenuItem properties_item;
901 properties_item.label =
ICON_MD_TUNE " Area Configuration";
902 properties_item.callback = [&show_map_properties_panel]() {
903 show_map_properties_panel =
true;
905 canvas.AddContextMenuItem(properties_item);
911 gui::CanvasMenuItem bg_color_item;
913 bg_color_item.callback = [&show_custom_bg_color_editor]() {
914 show_custom_bg_color_editor =
true;
916 canvas.AddContextMenuItem(bg_color_item);
919 gui::CanvasMenuItem overlay_item;
921 overlay_item.callback = [&show_overlay_editor]() {
922 show_overlay_editor =
true;
924 canvas.AddContextMenuItem(overlay_item);
928 gui::CanvasMenuItem reset_view_item;
930 reset_view_item.callback = [&canvas]() {
931 canvas.set_global_scale(1.0f);
932 canvas.set_scrolling(ImVec2(0, 0));
934 canvas.AddContextMenuItem(reset_view_item);
936 gui::CanvasMenuItem zoom_in_item;
938 zoom_in_item.callback = [&canvas]() {
941 canvas.set_global_scale(scale);
943 canvas.AddContextMenuItem(zoom_in_item);
945 gui::CanvasMenuItem zoom_out_item;
947 zoom_out_item.callback = [&canvas]() {
950 canvas.set_global_scale(scale);
952 canvas.AddContextMenuItem(zoom_out_item);
956void MapPropertiesSystem::DrawGraphicsPopup(
int current_map,
int game_state) {
957 const int current_game_state = CurrentGameState(game_state);
961 ImGui::PushID(
"GraphicsPopup");
967 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
968 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
970 ImGui::Text(tr(
"Graphics Settings"));
974 uint8_t area_gfx = overworld_->overworld_map(current_map)->area_graphics();
978 {current_map, OverworldPropertyField::kAreaGraphics, 0, area_gfx});
980 HOVER_HINT(
"Main tileset graphics for this map area");
983 uint8_t sprite_gfx = overworld_->overworld_map(current_map)
984 ->sprite_graphics(current_game_state);
989 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpriteGraphics,
990 current_game_state, sprite_gfx});
992 HOVER_HINT(
"Sprite graphics sheet for current game state");
996 uint8_t animated_gfx =
997 overworld_->overworld_map(current_map)->animated_gfx();
1000 ApplyPropertyEdit({current_map,
1001 OverworldPropertyField::kAnimatedGraphics, 0,
1004 HOVER_HINT(
"Animated tile graphics (water, lava, etc.)");
1015 if (BeginTable(
"CustomTileGraphics", 2, ImGuiTableFlags_SizingFixedFit)) {
1016 for (
int i = 0; i < 8; i++) {
1018 std::string label = absl::StrFormat(
ICON_MD_LAYERS " Sheet %d", i);
1019 uint8_t custom_gfx =
1020 overworld_->overworld_map(current_map)->custom_tileset(i);
1022 ApplyPropertyEdit({current_map,
1023 OverworldPropertyField::kCustomTileset, i,
1026 if (ImGui::IsItemHovered()) {
1027 ImGui::SetTooltip(tr(
"Custom graphics sheet %d (0x00-0xFF)"), i);
1034 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1037 tr(
"Custom tile graphics require ZSCustomOverworld v1+.\n"
1038 "Upgrade your ROM to access 8 customizable graphics sheets."));
1045void MapPropertiesSystem::DrawPalettesPopup(
int current_map,
int game_state,
1046 bool& show_custom_bg_color_editor) {
1047 const int current_game_state = CurrentGameState(game_state);
1051 ImGui::PushID(
"PalettesPopup");
1057 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
1058 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
1060 ImGui::Text(tr(
"Palette Settings"));
1064 uint8_t area_palette =
1065 overworld_->overworld_map(current_map)->area_palette();
1069 {current_map, OverworldPropertyField::kAreaPalette, 0, area_palette});
1071 HOVER_HINT(
"Main color palette for background tiles");
1077 uint8_t main_palette =
1078 overworld_->overworld_map(current_map)->main_palette();
1081 ApplyPropertyEdit({current_map, OverworldPropertyField::kMainPalette, 0,
1084 HOVER_HINT(
"Extended main palette (ZSCustomOverworld v2+)");
1088 uint8_t sprite_palette = overworld_->overworld_map(current_map)
1089 ->sprite_palette(current_game_state);
1094 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpritePalette,
1095 current_game_state, sprite_palette});
1097 HOVER_HINT(
"Color palette for sprites in current game state");
1102 show_custom_bg_color_editor = !show_custom_bg_color_editor;
1104 HOVER_HINT(
"Open custom background color editor (v2+)");
1110void MapPropertiesSystem::DrawPropertiesPopup(
int current_map,
1111 bool& show_map_properties_panel,
1112 bool& show_overlay_preview,
1117 ImGui::PushID(
"ConfigPopup");
1123 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
1124 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
1130 if (BeginTable(
"BasicProps", 2, ImGuiTableFlags_SizingFixedFit)) {
1135 uint16_t message_id =
1136 overworld_->overworld_map(current_map)->message_id();
1139 {current_map, OverworldPropertyField::kMessageId, 0, message_id});
1141 if (ImGui::IsItemHovered()) {
1142 ImGui::SetTooltip(tr(
"Message ID shown when entering this area"));
1150 int current_game_state = CurrentGameState(game_state);
1153 game_state = current_game_state;
1154 SetCurrentGameState(current_game_state);
1155 RefreshMapProperties();
1156 RefreshOverworldMap();
1158 if (ImGui::IsItemHovered()) {
1160 tr(
"Affects sprite graphics/palettes based on story progress"));
1174 int current_area_size =
1175 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
1178 if (asm_version >= 3 && asm_version != 0xFF) {
1182 ApplyPropertyEdit({current_map, OverworldPropertyField::kAreaSize, 0,
1183 current_area_size});
1185 HOVER_HINT(
"Map area size (1x1, 2x2, 2x1, 1x2 screens)");
1188 const char* limited_names[] = {
"Small (1x1)",
"Large (2x2)"};
1189 int limited_size = (current_area_size == 0 || current_area_size == 1)
1194 limited_names, 2)) {
1196 {current_map, OverworldPropertyField::kAreaSize, 0, limited_size});
1198 HOVER_HINT(
"Small (1x1) and Large (2x2) maps. Wide/Tall require v3+");
1206 DrawMosaicControls(current_map);
1207 DrawOverlayControls(current_map, show_overlay_preview);
1213 show_map_properties_panel =
true;
1214 ImGui::CloseCurrentPopup();
1216 HOVER_HINT(
"Open detailed area configuration with all settings tabs");
1223void MapPropertiesSystem::DrawBasicPropertiesTab(
int current_map) {
1224 if (BeginTable(
"BasicProperties", 2,
1225 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1226 ImGui::TableSetupColumn(
"Property", ImGuiTableColumnFlags_WidthFixed, 180);
1227 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
1232 uint8_t area_gfx = overworld_->overworld_map(current_map)->area_graphics();
1235 {current_map, OverworldPropertyField::kAreaGraphics, 0, area_gfx});
1237 if (ImGui::IsItemHovered()) {
1238 ImGui::SetTooltip(tr(
"Main tileset graphics for this map area"));
1244 uint8_t area_pal = overworld_->overworld_map(current_map)->area_palette();
1247 {current_map, OverworldPropertyField::kAreaPalette, 0, area_pal});
1249 if (ImGui::IsItemHovered()) {
1250 ImGui::SetTooltip(tr(
"Color palette for background tiles"));
1256 uint16_t message_id = overworld_->overworld_map(current_map)->message_id();
1259 {current_map, OverworldPropertyField::kMessageId, 0, message_id});
1261 if (ImGui::IsItemHovered()) {
1262 ImGui::SetTooltip(tr(
"Message displayed when entering this area"));
1269 *overworld_->mutable_overworld_map(current_map)->mutable_mosaic();
1270 if (ImGui::Checkbox(
"##mosaic", &mosaic)) {
1272 {current_map, OverworldPropertyField::kMosaic, 0, mosaic ? 1 : 0});
1274 if (ImGui::IsItemHovered()) {
1275 ImGui::SetTooltip(tr(
"Enable pixelated mosaic transition effect"));
1282 uint8_t music0 = overworld_->overworld_map(current_map)->area_music(0);
1285 {current_map, OverworldPropertyField::kMusic, 0, music0});
1287 if (ImGui::IsItemHovered()) {
1288 ImGui::SetTooltip(tr(
"Music track before rescuing Zelda"));
1294 uint8_t music1 = overworld_->overworld_map(current_map)->area_music(1);
1297 {current_map, OverworldPropertyField::kMusic, 1, music1});
1299 if (ImGui::IsItemHovered()) {
1300 ImGui::SetTooltip(tr(
"Music track after rescuing Zelda"));
1306 uint8_t music2 = overworld_->overworld_map(current_map)->area_music(2);
1309 {current_map, OverworldPropertyField::kMusic, 2, music2});
1311 if (ImGui::IsItemHovered()) {
1312 ImGui::SetTooltip(tr(
"Music track after obtaining Master Sword"));
1318 uint8_t music3 = overworld_->overworld_map(current_map)->area_music(3);
1321 {current_map, OverworldPropertyField::kMusic, 3, music3});
1323 if (ImGui::IsItemHovered()) {
1324 ImGui::SetTooltip(tr(
"Music track after defeating Agahnim (Dark World)"));
1331void MapPropertiesSystem::DrawSpritePropertiesTab(
int current_map) {
1332 if (BeginTable(
"SpriteProperties", 2,
1333 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1334 ImGui::TableSetupColumn(
"Property", ImGuiTableColumnFlags_WidthFixed, 180);
1335 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
1340 int game_state = CurrentGameState();
1341 ImGui::SetNextItemWidth(120.f);
1343 SetCurrentGameState(game_state);
1344 RefreshMapProperties();
1345 RefreshOverworldMap();
1347 if (ImGui::IsItemHovered()) {
1348 ImGui::SetTooltip(tr(
"Affects which sprite graphics/palettes are used"));
1354 uint8_t sprite_gfx1 =
1355 overworld_->overworld_map(current_map)->sprite_graphics(1);
1357 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpriteGraphics,
1360 if (ImGui::IsItemHovered()) {
1362 tr(
"First sprite graphics sheet for Zelda rescued state"));
1368 uint8_t sprite_gfx2 =
1369 overworld_->overworld_map(current_map)->sprite_graphics(2);
1371 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpriteGraphics,
1374 if (ImGui::IsItemHovered()) {
1376 tr(
"Second sprite graphics sheet for Master Sword obtained state"));
1382 uint8_t sprite_pal1 =
1383 overworld_->overworld_map(current_map)->sprite_palette(1);
1385 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpritePalette, 1,
1388 if (ImGui::IsItemHovered()) {
1389 ImGui::SetTooltip(tr(
"Color palette for sprites - Zelda rescued state"));
1395 uint8_t sprite_pal2 =
1396 overworld_->overworld_map(current_map)->sprite_palette(2);
1398 ApplyPropertyEdit({current_map, OverworldPropertyField::kSpritePalette, 2,
1401 if (ImGui::IsItemHovered()) {
1403 tr(
"Color palette for sprites - Master Sword obtained state"));
1410void MapPropertiesSystem::DrawCustomFeaturesTab(
int current_map) {
1411 if (BeginTable(
"CustomFeatures", 2,
1412 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1413 ImGui::TableSetupColumn(
"Property", ImGuiTableColumnFlags_WidthFixed, 180);
1414 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
1422 int current_area_size =
1423 static_cast<int>(overworld_->overworld_map(current_map)->area_size());
1424 ImGui::SetNextItemWidth(130.f);
1428 static const char* all_sizes[] = {
"Small (1x1)",
"Large (2x2)",
1429 "Wide (2x1)",
"Tall (1x2)"};
1430 if (ImGui::Combo(
"##AreaSize", ¤t_area_size, all_sizes, 4)) {
1431 ApplyPropertyEdit({current_map, OverworldPropertyField::kAreaSize, 0,
1432 current_area_size});
1434 if (ImGui::IsItemHovered()) {
1436 tr(
"Map size: Small (1x1), Large (2x2), Wide (2x1), Tall (1x2)"));
1440 static const char* limited_sizes[] = {
"Small (1x1)",
"Large (2x2)"};
1441 int limited_size = (current_area_size == 0 || current_area_size == 1)
1445 if (ImGui::Combo(
"##AreaSize", &limited_size, limited_sizes, 2)) {
1448 ApplyPropertyEdit({current_map, OverworldPropertyField::kAreaSize, 0,
1449 static_cast<int>(size)});
1451 if (ImGui::IsItemHovered()) {
1453 tr(
"Map size: Small (1x1), Large (2x2). Wide/Tall require v3+"));
1458 rom_version_basic)) {
1462 uint8_t main_palette =
1463 overworld_->overworld_map(current_map)->main_palette();
1465 ApplyPropertyEdit({current_map, OverworldPropertyField::kMainPalette, 0,
1468 if (ImGui::IsItemHovered()) {
1469 ImGui::SetTooltip(tr(
"Extended main palette (ZSCustomOverworld v2+)"));
1474 rom_version_basic)) {
1478 uint8_t animated_gfx =
1479 overworld_->overworld_map(current_map)->animated_gfx();
1481 ApplyPropertyEdit({current_map,
1482 OverworldPropertyField::kAnimatedGraphics, 0,
1485 if (ImGui::IsItemHovered()) {
1486 ImGui::SetTooltip(tr(
"Animated tile graphics ID (water, lava, etc.)"));
1492 uint16_t subscreen_overlay =
1493 overworld_->overworld_map(current_map)->subscreen_overlay();
1496 ApplyPropertyEdit({current_map,
1497 OverworldPropertyField::kSubscreenOverlay, 0,
1498 subscreen_overlay});
1500 if (ImGui::IsItemHovered()) {
1502 tr(
"Visual effects overlay ID (fog, rain, backgrounds)"));
1510void MapPropertiesSystem::DrawTileGraphicsTab(
int current_map) {
1518 if (BeginTable(
"TileGraphics", 2,
1519 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1520 ImGui::TableSetupColumn(
"Property", ImGuiTableColumnFlags_WidthFixed,
1522 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
1524 for (
int i = 0; i < 8; i++) {
1528 uint8_t custom_tileset =
1529 overworld_->overworld_map(current_map)->custom_tileset(i);
1532 ApplyPropertyEdit({current_map,
1533 OverworldPropertyField::kCustomTileset, i,
1536 if (ImGui::IsItemHovered()) {
1537 ImGui::SetTooltip(tr(
"Custom graphics sheet %d (0x00-0xFF)"), i);
1546 tr(
"These 8 sheets allow custom tile graphics per map. "
1547 "Each sheet references a graphics ID loaded into VRAM."));
1550 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1554 tr(
"Custom tile graphics are not available in vanilla ROMs.\n\n"
1555 "To enable this feature, upgrade your ROM to ZSCustomOverworld v1+, "
1556 "which provides 8 customizable graphics sheets per map for advanced "
1557 "tileset customization."));
1561void MapPropertiesSystem::DrawMusicTab(
int current_map) {
1565 if (BeginTable(
"MusicSettings", 2,
1566 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1567 ImGui::TableSetupColumn(
"Game State", ImGuiTableColumnFlags_WidthFixed,
1569 ImGui::TableSetupColumn(
"Music Track ID",
1570 ImGuiTableColumnFlags_WidthStretch);
1572 const char* music_state_names[] = {
1578 const char* music_descriptions[] = {
1579 "Music before rescuing Zelda from the castle",
1580 "Music after rescuing Zelda from Hyrule Castle",
1581 "Music after obtaining the Master Sword from the Lost Woods",
1582 "Music after defeating Agahnim (Dark World music)"};
1584 for (
int i = 0; i < 4; i++) {
1586 ImGui::Text(
"%s", music_state_names[i]);
1589 uint8_t music = overworld_->overworld_map(current_map)->area_music(i);
1593 {current_map, OverworldPropertyField::kMusic, i, music});
1595 if (ImGui::IsItemHovered()) {
1596 ImGui::SetTooltip(
"%s", music_descriptions[i]);
1605 tr(
"Music tracks control the background music for different "
1606 "game progression states on this overworld map."));
1611 ImGuiTreeNodeFlags_DefaultOpen)) {
1613 ImGui::BulletText(tr(
"0x02 - Overworld Theme"));
1614 ImGui::BulletText(tr(
"0x05 - Kakariko Village"));
1615 ImGui::BulletText(tr(
"0x07 - Lost Woods"));
1616 ImGui::BulletText(tr(
"0x09 - Dark World Theme"));
1617 ImGui::BulletText(tr(
"0x0F - Ganon's Tower"));
1618 ImGui::BulletText(tr(
"0x11 - Death Mountain"));
1623absl::Status MapPropertiesSystem::ApplyPropertyEdit(
1625 if (property_edit_callback_) {
1626 return property_edit_callback_(edit);
1628 return ApplyPropertyEditDirect(edit);
1631absl::Status MapPropertiesSystem::ApplyPropertyEdits(
1632 const std::vector<OverworldPropertyEdit>& edits,
1633 const std::string& description) {
1634 if (property_edit_batch_callback_) {
1635 return property_edit_batch_callback_(edits, description);
1637 for (
const auto& edit : edits) {
1638 if (!CheckPropertyEditSupported(edit).ok()) {
1643 return absl::OkStatus();
1646absl::Status MapPropertiesSystem::CheckPropertyEditSupported(
1648 return CheckFieldSupported(rom_, edit);
1651absl::StatusOr<int> MapPropertiesSystem::ReadPropertyValue(
1654 return absl::FailedPreconditionError(
"Overworld is not available");
1656 const int map_id = EffectivePropertyMapId(
1658 if (!IsValidMapId(map_id)) {
1659 return absl::InvalidArgumentError(
1660 absl::StrFormat(
"Invalid overworld map: %d", edit.
map_id));
1662 const auto* map = overworld_->overworld_map(map_id);
1664 return absl::NotFoundError(
1665 absl::StrFormat(
"Overworld map 0x%02X is unavailable", map_id));
1668 switch (edit.
field) {
1669 case OverworldPropertyField::kAreaSize:
1670 return static_cast<int>(map->area_size());
1671 case OverworldPropertyField::kAreaGraphics:
1672 return map->area_graphics();
1673 case OverworldPropertyField::kAreaPalette:
1674 return map->area_palette();
1675 case OverworldPropertyField::kMainPalette:
1676 return map->main_palette();
1677 case OverworldPropertyField::kSpriteGraphics:
1678 return map->sprite_graphics(std::clamp(edit.
index, 0, 2));
1679 case OverworldPropertyField::kSpritePalette:
1680 return map->sprite_palette(std::clamp(edit.
index, 0, 2));
1681 case OverworldPropertyField::kAnimatedGraphics:
1682 return map->animated_gfx();
1683 case OverworldPropertyField::kCustomTileset:
1684 return map->custom_tileset(std::clamp(edit.
index, 0, 7));
1685 case OverworldPropertyField::kMessageId:
1686 return map->message_id();
1687 case OverworldPropertyField::kMusic:
1688 return map->area_music(std::clamp(edit.
index, 0, 3));
1689 case OverworldPropertyField::kMosaic:
1691 case OverworldPropertyField::kMosaicExpanded:
1693 case OverworldPropertyField::kAreaSpecificBgColor:
1694 return map->area_specific_bg_color();
1695 case OverworldPropertyField::kSubscreenOverlay:
1696 return map->subscreen_overlay();
1698 return absl::InvalidArgumentError(
"Unknown overworld property field");
1701absl::Status MapPropertiesSystem::ApplyPropertyEditDirect(
1703 if (!overworld_ || !rom_) {
1704 return absl::FailedPreconditionError(
1705 "Overworld property editing requires loaded overworld data and ROM");
1707 const int map_id = EffectivePropertyMapId(overworld_, edit.
map_id);
1708 if (!IsValidMapId(map_id)) {
1709 return absl::InvalidArgumentError(
1710 absl::StrFormat(
"Invalid overworld map: %d", edit.
map_id));
1712 auto* map = overworld_->mutable_overworld_map(map_id);
1714 return absl::NotFoundError(
1715 absl::StrFormat(
"Overworld map 0x%02X is unavailable", map_id));
1718 const absl::Status supported = CheckFieldSupported(rom_, edit);
1719 if (!supported.ok()) {
1723 const int game_state = std::clamp(edit.
index, 0, 2);
1724 switch (edit.
field) {
1725 case OverworldPropertyField::kAreaSize: {
1727 auto status = overworld_->ConfigureMultiAreaMap(map_id, size);
1731 RefreshSiblingMapGraphics(map_id,
true);
1732 RefreshOverworldMap();
1735 case OverworldPropertyField::kAreaGraphics:
1736 map->set_area_graphics(
static_cast<uint8_t
>(edit.
value));
1737 RefreshMapProperties();
1739 (*maps_bmp_)[map_id].set_modified(
true);
1741 PrepareMapForGraphicsRefresh(map_id);
1742 map->LoadAreaGraphics();
1743 RefreshSiblingMapGraphics(map_id);
1744 RefreshTile16Blockset();
1745 RefreshOverworldMap();
1747 case OverworldPropertyField::kAreaPalette:
1748 map->set_area_palette(
static_cast<uint8_t
>(edit.
value));
1749 RefreshMapProperties();
1750 RefreshMapPalette();
1751 RefreshOverworldMap();
1753 case OverworldPropertyField::kMainPalette:
1754 map->set_main_palette(
static_cast<uint8_t
>(edit.
value));
1755 RefreshMapProperties();
1756 RefreshMapPalette();
1757 RefreshOverworldMap();
1759 case OverworldPropertyField::kSpriteGraphics:
1760 map->set_sprite_graphics(game_state,
static_cast<uint8_t
>(edit.
value));
1761 ForceRefreshGraphics(map_id);
1762 RefreshMapProperties();
1763 RefreshOverworldMap();
1765 case OverworldPropertyField::kSpritePalette:
1766 map->set_sprite_palette(game_state,
static_cast<uint8_t
>(edit.
value));
1767 RefreshMapProperties();
1768 RefreshOverworldMap();
1770 case OverworldPropertyField::kAnimatedGraphics:
1771 map->set_animated_gfx(
static_cast<uint8_t
>(edit.
value));
1772 ForceRefreshGraphics(map_id);
1773 RefreshMapProperties();
1774 RefreshTile16Blockset();
1775 RefreshOverworldMap();
1777 case OverworldPropertyField::kCustomTileset: {
1778 const int slot = std::clamp(edit.
index, 0, 7);
1779 map->set_custom_tileset(slot,
static_cast<uint8_t
>(edit.
value));
1780 PrepareMapForGraphicsRefresh(map_id);
1781 map->LoadAreaGraphics();
1782 ForceRefreshGraphics(map_id);
1783 RefreshSiblingMapGraphics(map_id);
1784 RefreshMapProperties();
1785 RefreshTile16Blockset();
1786 RefreshOverworldMap();
1789 case OverworldPropertyField::kMessageId:
1790 map->set_message_id(
static_cast<uint16_t
>(edit.
value));
1791 RefreshMapProperties();
1792 RefreshOverworldMap();
1794 case OverworldPropertyField::kMusic: {
1795 const int music_state = std::clamp(edit.
index, 0, 3);
1796 *map->mutable_area_music(music_state) =
static_cast<uint8_t
>(edit.
value);
1797 WriteRomByteIfValid(rom_, MusicRomAddressForMapState(map_id, music_state),
1798 static_cast<uint8_t
>(edit.
value));
1799 RefreshMapProperties();
1802 case OverworldPropertyField::kMosaic:
1803 *map->mutable_mosaic() = edit.
value != 0;
1804 RefreshMapProperties();
1805 RefreshOverworldMap();
1807 case OverworldPropertyField::kMosaicExpanded:
1808 map->set_mosaic_expanded(std::clamp(edit.
index, 0, 3), edit.
value != 0);
1809 RefreshMapProperties();
1810 RefreshOverworldMap();
1812 case OverworldPropertyField::kAreaSpecificBgColor:
1813 map->set_area_specific_bg_color(
static_cast<uint16_t
>(edit.
value));
1814 WriteRomWordIfValid(
1816 static_cast<uint16_t
>(edit.
value));
1817 RefreshMapProperties();
1818 RefreshOverworldMap();
1820 case OverworldPropertyField::kSubscreenOverlay:
1821 map->set_subscreen_overlay(
static_cast<uint16_t
>(edit.
value));
1822 WriteRomWordIfValid(
1824 static_cast<uint16_t
>(edit.
value));
1825 RefreshMapProperties();
1826 RefreshOverworldMap();
1830 rom_->set_dirty(
true);
1831 return absl::OkStatus();
1834void MapPropertiesSystem::RefreshMapProperties() {
1835 if (refresh_map_properties_) {
1836 refresh_map_properties_();
1840void MapPropertiesSystem::RefreshOverworldMap() {
1841 if (refresh_overworld_map_) {
1842 refresh_overworld_map_();
1846absl::Status MapPropertiesSystem::RefreshMapPalette() {
1847 if (refresh_map_palette_) {
1848 return refresh_map_palette_();
1850 return absl::OkStatus();
1853absl::Status MapPropertiesSystem::RefreshTile16Blockset() {
1854 if (refresh_tile16_blockset_) {
1855 return refresh_tile16_blockset_();
1857 return absl::OkStatus();
1860void MapPropertiesSystem::ForceRefreshGraphics(
int map_index) {
1861 if (force_refresh_graphics_) {
1862 force_refresh_graphics_(map_index);
1866void MapPropertiesSystem::RefreshSiblingMapGraphics(
int map_index,
1867 bool include_self) {
1868 if (!overworld_ || !maps_bmp_ || map_index < 0 ||
1873 auto* map = overworld_->mutable_overworld_map(map_index);
1878 int parent_id = map->parent();
1879 std::vector<int> siblings;
1881 switch (map->area_size()) {
1883 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
1886 siblings = {parent_id, parent_id + 1};
1889 siblings = {parent_id, parent_id + 8};
1895 for (
int sibling : siblings) {
1898 if (sibling == map_index && !include_self) {
1903 (*maps_bmp_)[sibling].set_modified(
true);
1906 PrepareMapForGraphicsRefresh(sibling);
1907 overworld_->mutable_overworld_map(sibling)->LoadAreaGraphics();
1912 ForceRefreshGraphics(sibling);
1918 RefreshOverworldMap();
1921void MapPropertiesSystem::DrawMosaicControls(
int current_map) {
1925 ImGui::Text(tr(
"Mosaic Effects (per direction):"));
1927 auto* current_map_ptr = overworld_->mutable_overworld_map(current_map);
1928 std::array<bool, 4> mosaic_expanded = current_map_ptr->mosaic_expanded();
1929 const char* direction_names[] = {
"North",
"South",
"East",
"West"};
1931 for (
int i = 0; i < 4; i++) {
1932 if (ImGui::Checkbox(direction_names[i], &mosaic_expanded[i])) {
1933 ApplyPropertyEdit({current_map, OverworldPropertyField::kMosaicExpanded,
1934 i, mosaic_expanded[i] ? 1 : 0});
1939 *overworld_->mutable_overworld_map(current_map)->mutable_mosaic();
1940 if (ImGui::Checkbox(tr(
"Mosaic Effect"), &mosaic)) {
1942 {current_map, OverworldPropertyField::kMosaic, 0, mosaic ? 1 : 0});
1947void MapPropertiesSystem::DrawOverlayControls(
int current_map,
1948 bool& show_overlay_preview) {
1952 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
1954 if (is_special_overworld_map) {
1956 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
1957 ICON_MD_INFO " Special Area Map (0x%02X)", current_map);
1959 ImGui::TextWrapped(tr(
1960 "This is a special area map (0x80-0x9F) used as a visual effect "
1961 "source. These maps provide the graphics data for subscreen overlays "
1962 "like fog, rain, forest canopy, and sky backgrounds that appear on "
1963 "normal maps (0x00-0x7F)."));
1965 ImGui::TextWrapped(tr(
1966 "You can edit the tile16 data here to customize how the visual effects "
1967 "appear when referenced by other maps."));
1973 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
1985 ImGui::Text(
ICON_MD_HELP " Understanding Overlay Types");
1988 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
ICON_MD_LAYERS
1989 " 1. Subscreen Overlays (Visual Effects)");
1991 ImGui::BulletText(tr(
"Displayed as semi-transparent layers"));
1992 ImGui::BulletText(tr(
"Reference special area maps (0x80-0x9F)"));
1993 ImGui::BulletText(tr(
"Examples: fog, rain, forest canopy, sky"));
1994 ImGui::BulletText(tr(
"Purely visual - don't affect collision"));
1998 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
2001 ImGui::BulletText(tr(
"Dynamic tile16 changes on the map"));
2002 ImGui::BulletText(tr(
"Used for bridges appearing, holes opening"));
2003 ImGui::BulletText(tr(
"Stored as tile16 ID arrays"));
2004 ImGui::BulletText(tr(
"Affect collision and interaction"));
2005 ImGui::BulletText(tr(
"Triggered by game events/progression"));
2010 tr(
"Note: Subscreen overlays are what you configure here. "
2011 "Map overlays are event-driven and edited separately."));
2018 ImGui::Text(
ICON_MD_LAYERS " Subscreen Overlay (Visual Effects)");
2020 uint16_t current_overlay =
2021 overworld_->overworld_map(current_map)->subscreen_overlay();
2024 ApplyPropertyEdit({current_map, OverworldPropertyField::kSubscreenOverlay,
2025 0, current_overlay});
2027 if (ImGui::IsItemHovered()) {
2029 tr(
"References a special area map (0x80-0x9F) for visual effects.\n"
2030 "The referenced map's tile16 data is drawn as a semi-transparent\n"
2031 "layer on top of or behind this area for atmospheric effects."));
2035 std::string overlay_desc = GetOverlayDescription(current_overlay);
2036 if (current_overlay == 0x00FF) {
2037 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
ICON_MD_CHECK " %s",
2038 overlay_desc.c_str());
2039 }
else if (current_overlay >= 0x80 && current_overlay < 0xA0) {
2040 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
2043 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
2050 &show_overlay_preview)) {
2053 if (ImGui::IsItemHovered()) {
2054 ImGui::SetTooltip(tr(
2055 "Shows a semi-transparent preview of the visual effect overlay\n"
2056 "drawn on top of the current map in the editor canvas.\n\n"
2057 "This preview shows how the subscreen overlay will appear in-game."));
2064 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.4f, 1.0f),
2073 if (ImGui::BeginPopup(
2077 ImGui::Text(
ICON_MD_HELP " Map Overlays (Interactive Tile Changes)");
2080 tr(
"Map overlays are different from visual effect overlays. "
2081 "They contain tile16 data that dynamically replaces tiles on "
2082 "the map based on game events or progression."));
2084 ImGui::Text(tr(
"Common uses:"));
2085 ImGui::BulletText(tr(
"Bridges appearing over water"));
2086 ImGui::BulletText(tr(
"Holes revealing secret passages"));
2087 ImGui::BulletText(tr(
"Rocks/bushes being moved"));
2088 ImGui::BulletText(tr(
"Environmental changes from story events"));
2091 tr(
"These are triggered by game code and stored as separate "
2092 "tile data arrays in the ROM. ZSCustomOverworld v3+ provides "
2093 "extended control over these features."));
2097 auto* current_map_ptr = overworld_->overworld_map(current_map);
2099 if (current_map_ptr->has_overlay()) {
2100 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
2102 current_map_ptr->overlay_id());
2104 static_cast<int>(current_map_ptr->overlay_data().size()));
2105 ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.4f, 1.0f),
2108 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
2116 ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
2118 ImGui::BulletText(tr(
"Visual effects use maps 0x80-0x9F"));
2119 ImGui::BulletText(tr(
"Map overlays are read-only"));
2121 const char* version_name =
2123 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
ICON_MD_UPGRADE " %s",
2125 ImGui::BulletText(tr(
"Enhanced visual effect control"));
2127 ImGui::BulletText(tr(
"Extended overlay system"));
2128 ImGui::BulletText(tr(
"Custom area sizes support"));
2134std::string MapPropertiesSystem::GetOverlayDescription(uint16_t overlay_id) {
2135 if (overlay_id == 0x0093) {
2136 return "Triforce Room Curtain";
2137 }
else if (overlay_id == 0x0094) {
2138 return "Under the Bridge";
2139 }
else if (overlay_id == 0x0095) {
2140 return "Sky Background (LW Death Mountain)";
2141 }
else if (overlay_id == 0x0096) {
2142 return "Pyramid Background";
2143 }
else if (overlay_id == 0x0097) {
2144 return "First Fog Overlay (Master Sword Area)";
2145 }
else if (overlay_id == 0x009C) {
2146 return "Lava Background (DW Death Mountain)";
2147 }
else if (overlay_id == 0x009D) {
2148 return "Second Fog Overlay (Lost Woods/Skull Woods)";
2149 }
else if (overlay_id == 0x009E) {
2150 return "Tree Canopy (Forest)";
2151 }
else if (overlay_id == 0x009F) {
2152 return "Rain Effect (Misery Mire)";
2153 }
else if (overlay_id == 0x00FF) {
2154 return "No Overlay";
2156 return "Custom overlay";
2160void MapPropertiesSystem::DrawOverlayPreviewOnMap(
int current_map,
2162 bool show_overlay_preview) {
2165 if (!show_overlay_preview || !maps_bmp_ || !canvas_)
2169 uint16_t overlay_id = 0x00FF;
2170 bool has_subscreen_overlay =
false;
2172 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
2174 if (is_special_overworld_map) {
2181 overlay_id = overworld_->overworld_map(current_map)->subscreen_overlay();
2182 has_subscreen_overlay = (overlay_id != 0x00FF);
2184 if (!has_subscreen_overlay)
2188 int overlay_map_index = -1;
2189 if (overlay_id >= 0x80 && overlay_id < 0xA0) {
2190 overlay_map_index = overlay_id;
2197 const auto& overlay_bitmap = (*maps_bmp_)[overlay_map_index];
2198 if (!overlay_bitmap.is_active() || !overlay_bitmap.texture())
2202 int current_map_x = current_map % 8;
2203 int current_map_y = current_map / 8;
2204 if (current_world == 1) {
2205 current_map_x = (current_map - 0x40) % 8;
2206 current_map_y = (current_map - 0x40) / 8;
2207 }
else if (current_world == 2) {
2208 current_map_x = (current_map - 0x80) % 8;
2209 current_map_y = (current_map - 0x80) / 8;
2212 int scale =
static_cast<int>(canvas_->global_scale());
2217 bool is_background_overlay =
2218 (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C);
2221 ImU32 overlay_color =
2222 is_background_overlay ? IM_COL32(255, 255, 255, 128)
2224 IM_COL32(255, 255, 255,
2228 canvas_->draw_list()->AddImage(
2229 (ImTextureID)(intptr_t)overlay_bitmap.texture(), ImVec2(map_x, map_y),
2232 ImVec2(0, 0), ImVec2(1, 1), overlay_color);
2235void MapPropertiesSystem::DrawViewPopup() {
2239 ImGui::PushID(
"ViewPopup");
2245 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
2246 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
2248 ImGui::Text(tr(
"View Controls"));
2255 canvas_->set_global_scale(new_scale);
2262 canvas_->set_global_scale(new_scale);
2267 canvas_->set_global_scale(1.0f);
2276void MapPropertiesSystem::DrawQuickAccessPopup() {
2280 ImGui::PushID(
"QuickPopup");
2286 {{ImGuiStyleVar_ItemSpacing, ImVec2(spacing, spacing)},
2287 {ImGuiStyleVar_FramePadding, ImVec2(padding, padding)}});
2289 ImGui::Text(tr(
"Quick Access"));
2311 HOVER_HINT(
"Lock/unlock current map (Ctrl+L)");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
ResourceLabelEditCallback resource_label_edit_callback_
void PrepareMapForGraphicsRefresh(int map_index)
absl::Status ApplyPropertyEdit(const OverworldPropertyEdit &edit)
void DrawOverlayEditor(int current_map, bool &show_overlay_editor)
std::function< void(int, bool)> map_selection_callback_
absl::Status ApplyPropertyEdits(const std::vector< OverworldPropertyEdit > &edits, const std::string &description={})
void DrawMapPropertiesPanel(int current_map, bool &show_map_properties_panel)
void DrawSpritePropertiesTab(int current_map)
void DrawMusicTab(int current_map)
void DrawBasicPropertiesTab(int current_map)
void DrawCustomFeaturesTab(int current_map)
int CurrentGameState() const
void SetCurrentGameState(int game_state)
void SetupCanvasContextMenu(gui::Canvas &canvas, int current_map, bool ¤t_map_lock, bool &show_map_properties_panel, bool &show_custom_bg_color_editor, bool &show_overlay_editor, int current_mode=0, project::YazeProject *project=nullptr, SharedClipboard *shared_clipboard=nullptr)
void DrawCustomBackgroundColorEditor(int current_map, bool &show_custom_bg_color_editor)
void DrawCanvasToolbar(int ¤t_world, int ¤t_map, bool ¤t_map_lock, bool &show_map_properties_panel, bool &show_custom_bg_color_editor, bool &show_overlay_editor, bool &show_overlay_preview, int &game_state, EditingMode ¤t_mode, EntityEditMode &entity_edit_mode)
zelda3::Overworld * overworld_
void DrawTileGraphicsTab(int current_map)
std::string GetOverlayDescription(uint16_t overlay_id)
RAII timer for automatic timing management.
constexpr uint16_t snes() const
Get SNES 15-bit color.
Modern, robust canvas for drawing and manipulating graphics.
void SetUsageMode(CanvasUsage usage)
void ClearContextMenuItems()
void AddContextMenuItem(const gui::CanvasMenuItem &item)
static float GetButtonPadding()
static float GetStandardSpacing()
RAII guard for ImGui style vars.
Represents a single Overworld map screen.
const std::array< bool, 4 > & mosaic_expanded() const
static bool SupportsCustomBGColors(OverworldVersion version)
Check if ROM supports custom background colors per area (v2+)
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
static bool SupportsAnimatedGFX(OverworldVersion version)
Check if ROM supports animated GFX selection (v3+)
static bool SupportsExpandedSpace(OverworldVersion version)
Check if ROM uses expanded ROM space for overworld data.
static const char * GetVersionName(OverworldVersion version)
Get human-readable version name for display/logging.
Represents the full Overworld data, light and dark world.
auto overworld_map(int i) const
auto mutable_overworld_map(int i)
#define ICON_MD_GRID_VIEW
#define ICON_MD_ACCOUNT_TREE
#define ICON_MD_LOCK_OPEN
#define ICON_MD_LIGHTBULB
#define ICON_MD_PHOTO_SIZE_SELECT_LARGE
#define ICON_MD_OFFLINE_BOLT
#define ICON_MD_PLAY_ARROW
#define ICON_MD_OPEN_IN_FULL
#define ICON_MD_VISIBILITY
#define ICON_MD_FORMAT_COLOR_FILL
#define ICON_MD_DOOR_BACK
#define ICON_MD_AUTO_FIX_HIGH
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_CONTENT_PASTE
#define ICON_MD_ASPECT_RATIO
#define ICON_MD_ANIMATION
#define ICON_MD_DOOR_FRONT
#define ICON_MD_HELP_OUTLINE
#define ICON_MD_ADD_LOCATION
#define ICON_MD_EDIT_NOTE
#define ICON_MD_OPEN_IN_NEW
#define ICON_MD_PEST_CONTROL_RODENT
#define ICON_MD_CONTENT_COPY
#define ICON_MD_COLOR_LENS
#define HOVER_HINT(string)
void WriteRomWordIfValid(Rom *rom, int address, uint16_t value)
int EffectivePropertyMapId(zelda3::Overworld *overworld, int map_id)
absl::Status CheckFieldSupported(const Rom *rom, const OverworldPropertyEdit &edit)
OverworldMapMetadataClipboard CaptureMapMetadataClipboard(const zelda3::Overworld &overworld, int map_id)
bool WriteRomByteIfValid(Rom *rom, int address, uint8_t value)
bool IsValidMapId(int map_id)
int MusicRomAddressForMapState(int map_id, int state)
std::vector< OverworldPropertyEdit > BuildOverworldMetadataPasteEdits(int target_map_id, const OverworldMapMetadataClipboard &clipboard, OverworldMapMetadataClipboardScope requested_scope)
absl::Status RenameProjectResourceLabel(project::YazeProject *project, const std::string &type, int id, const std::string &label)
constexpr const char * kAreaSizeNames[]
bool CanPasteOverworldMapMetadata(const OverworldMapMetadataClipboard &clipboard, OverworldMapMetadataClipboardScope requested_scope)
constexpr unsigned int kOverworldMapSize
constexpr float kOverworldMaxZoom
constexpr float kTableColumnWorld
constexpr float kOverworldMinZoom
OverworldMapMetadataClipboardScope
constexpr const char * kWorldNames[]
constexpr float kHexByteInputWidth
const char * OverworldMapMetadataClipboardScopeName(OverworldMapMetadataClipboardScope scope)
constexpr float kTableColumnMap
OverworldMapMetadata BuildOverworldMapMetadata(const zelda3::Overworld &overworld, const Rom *rom, const project::YazeProject *project, int map_id, int game_state)
constexpr float kTableColumnAreaSize
constexpr float kOverworldZoomStep
constexpr float kComboGameStateWidth
constexpr float kInputFieldSize
constexpr const char * kGameStateNames[]
constexpr float kIconButtonWidth
std::string DescribeOverworldMapMetadataClipboard(const OverworldMapMetadataClipboard &clipboard)
constexpr float kHexWordInputWidth
constexpr float kComboAreaSizeWidth
constexpr float kTableColumnLock
constexpr const char * kOverworld
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
bool ToggleButton(const char *label, bool active, const ImVec2 &size)
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
bool InputHexWordCustom(const char *label, uint16_t *data, float input_width)
std::string MakePopupId(size_t session_id, const std::string &editor_name, const std::string &popup_name)
Generate session-aware popup IDs to prevent conflicts in multi-editor layouts.
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
bool InputHexByte(const char *label, uint8_t *data, float input_width, bool no_step)
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kSpecialWorldMapIdStart
constexpr int kNumOverworldMaps
constexpr int kOverworldMusicBeginning
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kOverworldMusicDarkWorld
constexpr int OverworldCustomASMHasBeenApplied
constexpr int kOverworldMusicAgahnim
@ kVanilla
0xFF in ROM, no ZScream ASM applied
constexpr int kOverworldMusicMasterSword
constexpr int kOverworldMusicZelda
constexpr int kDarkWorldMapIdStart
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int OverworldCustomSubscreenOverlayArray
#define RETURN_IF_ERROR(expr)
SNES color in 15-bit RGB format (BGR555)
OverworldPropertyField field
bool has_overworld_map_metadata
OverworldMapMetadataClipboard overworld_map_metadata
Modern project structure with comprehensive settings consolidation.
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)