From 912cc3fb5694301eba074c548152006b202e7267 Mon Sep 17 00:00:00 2001 From: scawful Date: Wed, 24 Sep 2025 20:16:53 -0400 Subject: [PATCH] Update overworld map properties and overlay functionality - Changed the included assembly file for custom overworld to version 3 for improved features. - Enhanced documentation in the overworld loading guide to clarify overlay effects and configurations. - Refactored MapPropertiesSystem to support overlay previews and improved mosaic controls. - Added functionality for loading vanilla overlays and displaying overlay descriptions in the editor. - Updated UI components in OverworldEditor to integrate new overlay settings and preview options. --- assets/asm/yaze.asm | 2 +- docs/overworld_loading_guide.md | 54 +- src/app/editor/overworld/map_properties.cc | 292 +++++-- src/app/editor/overworld/map_properties.h | 24 +- src/app/editor/overworld/overworld_editor.cc | 848 +++++++++---------- src/app/editor/overworld/overworld_editor.h | 8 + src/app/zelda3/overworld/overworld_map.cc | 133 +++ src/app/zelda3/overworld/overworld_map.h | 25 + 8 files changed, 860 insertions(+), 526 deletions(-) diff --git a/assets/asm/yaze.asm b/assets/asm/yaze.asm index 1bfd2eb2..47430ba0 100644 --- a/assets/asm/yaze.asm +++ b/assets/asm/yaze.asm @@ -15,7 +15,7 @@ endif !ZS_CUSTOM_OVERWORLD = 1 if !ZS_CUSTOM_OVERWORLD != 0 - incsrc "ZSCustomOverworld.asm" + incsrc "ZSCustomOverworld_v3.asm" endif } diff --git a/docs/overworld_loading_guide.md b/docs/overworld_loading_guide.md index 91b14ea7..ad1b8dfd 100644 --- a/docs/overworld_loading_guide.md +++ b/docs/overworld_loading_guide.md @@ -50,7 +50,7 @@ constexpr int OverworldCustomASMHasBeenApplied = 0x140145; | Custom Tile Graphics | ❌ | ❌ | ✅ | | Vanilla Overlays | ✅ | ✅ | ✅ | -**Note:** Subscreen overlays are a shared concept from vanilla ROMs. ZSCustomOverworld v2+ expands on this by adding support for special overworld areas and custom overlay configurations. +**Note:** Subscreen overlays are visual effects (fog, rain, backgrounds, etc.) that are shared between vanilla ROMs and ZSCustomOverworld. ZSCustomOverworld v2+ expands on this by adding support for custom overlay configurations and additional overlay types. ## Overworld Map Structure @@ -96,31 +96,42 @@ class OverworldMap { ### Understanding Overlays -Overlays in Zelda 3 are **overworld maps from special areas** (maps 0x80-0x9F) that are displayed semi-transparently on top of other maps. This is a shared concept from vanilla ROMs that ZSCustomOverworld expands upon. +Overlays in Zelda 3 are **visual effects** that are displayed over or behind the main overworld map. They include effects like fog, rain, canopy, backgrounds, and other atmospheric elements. Overlays are collections of tile positions and tile IDs that specify where to place specific graphics on the map. ### Special Area Maps (0x80-0x9F) -Special area maps are the overworld maps past the dark world (maps 0x80-0x9F). These include: +The special area maps (0x80-0x9F) contain the actual tile data for overlays. These maps store the graphics that overlays reference and use to create visual effects: -- **0x80-0x8A**: Various special areas (Lost Woods, Skull Woods, etc.) -- **0x8B-0x8F**: Additional special areas -- **0x90-0x9F**: More special areas including Death Mountain variants +- **0x80-0x8F**: Various special area maps containing overlay graphics +- **0x90-0x9F**: Additional special area maps including more overlay graphics ### Overlay ID Mappings -Common overlay IDs reference specific special area maps: +Overlay IDs directly correspond to special area map indices. Common overlay mappings: | Overlay ID | Special Area Map | Description | |------------|------------------|-------------| -| 0x009D | 0x8D | Fog 2 (Lost Woods/Skull Woods) | -| 0x0095 | 0x85 | Sky Background (Death Mountain) | -| 0x009C | 0x8C | Lava (Dark World Death Mountain) | -| 0x0096 | 0x86 | Pyramid Background | -| 0x0097 | 0x87 | Fog 1 (Master Sword Area) | -| 0x0093 | 0x83 | Triforce Room Curtains | +| 0x0093 | 0x93 | Triforce Room Curtain | +| 0x0094 | 0x94 | Under the Bridge | +| 0x0095 | 0x95 | Sky Background (LW Death Mountain) | +| 0x0096 | 0x96 | Pyramid Background | +| 0x0097 | 0x97 | First Fog Overlay (Master Sword Area) | +| 0x009C | 0x9C | Lava Background (DW Death Mountain) | +| 0x009D | 0x9D | Second Fog Overlay (Lost Woods/Skull Woods) | +| 0x009E | 0x9E | Tree Canopy (Forest) | +| 0x009F | 0x9F | Rain Effect (Misery Mire) | + +### Drawing Order + +Overlays are drawn in a specific order based on their type: + +- **Background Overlays** (0x95, 0x96, 0x9C): Drawn behind the main map tiles +- **Foreground Overlays** (0x9D, 0x97, 0x93, 0x94, 0x9E, 0x9F): Drawn on top of the main map tiles with transparency ### Vanilla Overlay Loading +In vanilla ROMs, overlays are loaded by parsing SNES assembly-like commands that specify tile positions and IDs: + ```cpp absl::Status LoadVanillaOverlay() { uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; @@ -136,9 +147,14 @@ absl::Status LoadVanillaOverlay() { ((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) + (*rom_)[kOverlayPointers + (index_ * 2)]; - // Parse overlay commands (SNES assembly-like) - // Commands like LDA, LDX, STA, JMP, END (0x60) - // These commands configure which special area map to use as overlay + // Parse overlay commands: + // LDA #$xxxx - Load tile ID into accumulator + // LDX #$xxxx - Load position into X register + // STA $xxxx - Store tile at position + // STA $xxxx,x - Store tile at position + X + // INC A - Increment accumulator (for sequential tiles) + // JMP $xxxx - Jump to another overlay routine + // END (0x60) - End of overlay data return absl::OkStatus(); } @@ -387,11 +403,11 @@ class OverworldMap { **Solution:** - Verify overlay pointer addresses and SNES-to-PC conversion - Ensure special area maps (0x80-0x9F) are properly loaded with correct graphics -- Check that overlay ID mappings are correct (e.g., 0x009D → map 0x8D) +- Check that overlay ID mappings are correct (e.g., 0x009D → map 0x9D) - Verify that overlay preview shows the actual bitmap of the referenced special area map -**Problem:** Overlay preview showing hex data instead of bitmap -**Solution:** Implement proper overlay preview that displays the bitmap of the referenced special area map, not just the overlay command data +**Problem:** Overlay preview showing incorrect information +**Solution:** Ensure overlay preview correctly maps overlay IDs to special area map indices and displays the appropriate bitmap from the special area maps (0x80-0x9F) ### 5. Large Map Problems diff --git a/src/app/editor/overworld/map_properties.cc b/src/app/editor/overworld/map_properties.cc index 391509e7..a9e81436 100644 --- a/src/app/editor/overworld/map_properties.cc +++ b/src/app/editor/overworld/map_properties.cc @@ -1,9 +1,9 @@ #include "app/editor/overworld/map_properties.h" #include "app/gui/canvas.h" +#include "app/gui/color.h" #include "app/gui/icons.h" #include "app/gui/input.h" -#include "app/gui/style.h" #include "app/zelda3/overworld/overworld_map.h" #include "imgui/imgui.h" @@ -11,11 +11,7 @@ namespace yaze { namespace editor { using ImGui::BeginTable; -using ImGui::Button; -using ImGui::Checkbox; -using ImGui::EndTable; // HOVER_HINT is defined in util/macro.h -using ImGui::SameLine; using ImGui::Separator; using ImGui::TableNextColumn; using ImGui::Text; @@ -26,7 +22,8 @@ constexpr const char* kGamePartComboString[] = {"Light World", "Dark World", "Sp void MapPropertiesSystem::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_custom_bg_color_editor, bool& show_overlay_editor, + bool& show_overlay_preview, int& game_state) { // Enhanced settings table with popup buttons for quick access if (BeginTable("SimplifiedMapSettings", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0), -1)) { ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed, 100); @@ -90,19 +87,14 @@ void MapPropertiesSystem::DrawSimplifiedMapSettings(int& current_world, int& cur HOVER_HINT("Palette Settings"); DrawPalettesPopup(current_map, show_custom_bg_color_editor); - TableNextColumn(); - if (ImGui::Button("Overlays", ImVec2(80, 0))) { - ImGui::OpenPopup("OverlaysPopup"); - } - HOVER_HINT("Overlay Settings"); - DrawOverlaysPopup(current_map, show_overlay_editor); + // Overlays are now integrated into Properties popup TableNextColumn(); if (ImGui::Button("Properties", ImVec2(90, 0))) { ImGui::OpenPopup("PropertiesPopup"); } HOVER_HINT("Map Properties"); - DrawPropertiesPopup(current_map, show_map_properties_panel); + DrawPropertiesPopup(current_map, show_map_properties_panel, show_overlay_preview, game_state); ImGui::EndTable(); } @@ -336,8 +328,17 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map) { } ImGui::Separator(); - if (ImGui::Button("Tile Graphics (8 sheets)")) { - // This would open the full properties panel + ImGui::Text("Custom Tile Graphics (8 sheets):"); + + // Show the 8 custom graphics IDs in a more accessible way + for (int i = 0; i < 8; i++) { + std::string label = absl::StrFormat("Sheet %d", i); + if (gui::InputHexByte(label.c_str(), + overworld_->mutable_overworld_map(current_map)->mutable_custom_tileset(i), + 80.f)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } } ImGui::EndPopup(); @@ -353,7 +354,7 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, bool& show_custom_b overworld_->mutable_overworld_map(current_map)->mutable_area_palette(), kInputFieldSize)) { RefreshMapProperties(); - RefreshMapPalette(); + auto status = RefreshMapPalette(); RefreshOverworldMap(); } @@ -363,7 +364,7 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, bool& show_custom_b overworld_->mutable_overworld_map(current_map)->mutable_main_palette(), kInputFieldSize)) { RefreshMapProperties(); - RefreshMapPalette(); + auto status = RefreshMapPalette(); RefreshOverworldMap(); } } @@ -391,73 +392,56 @@ void MapPropertiesSystem::DrawPalettesPopup(int current_map, bool& show_custom_b } } -void MapPropertiesSystem::DrawOverlaysPopup(int current_map, bool& show_overlay_editor) { - if (ImGui::BeginPopup("OverlaysPopup")) { - ImGui::Text("Overlay Settings"); - ImGui::Separator(); - - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - if (asm_version >= 3) { - if (gui::InputHexWord("Subscreen Overlay", - overworld_->mutable_overworld_map(current_map)->mutable_subscreen_overlay(), - kInputFieldSize + 20)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - ImGui::Separator(); - if (ImGui::Button("Overlay Settings")) { - show_overlay_editor = !show_overlay_editor; - } - } else { - ImGui::Text("Overlays require ZSCustomOverworld v3+"); - } - - ImGui::EndPopup(); - } -} -void MapPropertiesSystem::DrawPropertiesPopup(int current_map, bool& show_map_properties_panel) { +void MapPropertiesSystem::DrawPropertiesPopup(int current_map, bool& show_map_properties_panel, + bool& show_overlay_preview, int& game_state) { if (ImGui::BeginPopup("PropertiesPopup")) { - ImGui::Text("Map Properties"); + ImGui::Text("Map Properties & Overlays"); ImGui::Separator(); - - if (gui::InputHexWord("Message ID", - overworld_->mutable_overworld_map(current_map)->mutable_message_id(), + + // Basic properties + if (gui::InputHexWord("Message ID", + overworld_->mutable_overworld_map(current_map) + ->mutable_message_id(), kInputFieldSize + 20)) { RefreshMapProperties(); RefreshOverworldMap(); } - - if (ImGui::Checkbox("Mosaic Effect", - overworld_->mutable_overworld_map(current_map)->mutable_mosaic())) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - static int game_state = 0; // This should be passed in or stored + + // Mosaic controls with directional support for v2+ + DrawMosaicControls(current_map); + + // Overlay controls - always accessible, behavior changes by version + ImGui::Separator(); + ImGui::Text("Overlay Settings:"); + DrawOverlayControls(current_map, show_overlay_preview); + ImGui::SetNextItemWidth(100.f); if (ImGui::Combo("Game State", &game_state, kGamePartComboString, 3)) { RefreshMapProperties(); RefreshOverworldMap(); } - + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; if (asm_version != 0xFF) { - static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; - int current_area_size = static_cast(overworld_->overworld_map(current_map)->area_size()); + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", + "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast( + overworld_->overworld_map(current_map)->area_size()); ImGui::SetNextItemWidth(120.f); if (ImGui::Combo("Area Size", ¤t_area_size, area_size_names, 4)) { - overworld_->mutable_overworld_map(current_map)->SetAreaSize(static_cast(current_area_size)); + overworld_->mutable_overworld_map(current_map) + ->SetAreaSize( + static_cast(current_area_size)); RefreshOverworldMap(); } } - + ImGui::Separator(); if (ImGui::Button("Full Properties Panel")) { show_map_properties_panel = true; } - + ImGui::EndPopup(); } } @@ -482,7 +466,7 @@ void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) { overworld_->mutable_overworld_map(current_map)->mutable_area_palette(), kInputFieldSize)) { RefreshMapProperties(); - RefreshMapPalette(); + auto status = RefreshMapPalette(); RefreshOverworldMap(); } @@ -585,7 +569,7 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) { overworld_->mutable_overworld_map(current_map)->mutable_main_palette(), kInputFieldSize)) { RefreshMapProperties(); - RefreshMapPalette(); + auto status = RefreshMapPalette(); RefreshOverworldMap(); } } @@ -663,5 +647,187 @@ absl::Status MapPropertiesSystem::RefreshMapPalette() { return absl::OkStatus(); } +void MapPropertiesSystem::DrawMosaicControls(int current_map) { + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version >= 2) { + ImGui::Separator(); + ImGui::Text("Mosaic Effects (per direction):"); + + auto* current_map_ptr = overworld_->mutable_overworld_map(current_map); + std::array mosaic_expanded = current_map_ptr->mosaic_expanded(); + const char* direction_names[] = {"North", "South", "East", "West"}; + + for (int i = 0; i < 4; i++) { + if (ImGui::Checkbox(direction_names[i], &mosaic_expanded[i])) { + current_map_ptr->set_mosaic_expanded(i, mosaic_expanded[i]); + RefreshMapProperties(); + RefreshOverworldMap(); + } + } + } else { + if (ImGui::Checkbox("Mosaic Effect", + overworld_->mutable_overworld_map(current_map) + ->mutable_mosaic())) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } +} + +void MapPropertiesSystem::DrawOverlayControls(int current_map, bool& show_overlay_preview) { + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + + // Determine if this is a special overworld map (0x80-0x9F) + bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0); + + if (asm_version == 0xFF) { + // Vanilla ROM - read-only overlay info + auto *current_map_ptr = overworld_->overworld_map(current_map); + if (current_map_ptr->has_vanilla_overlay()) { + ImGui::Text("Vanilla Overlay: 0x%04X", current_map_ptr->vanilla_overlay_id()); + + // Show overlay description + uint16_t overlay_id = current_map_ptr->vanilla_overlay_id(); + std::string overlay_desc = GetOverlayDescription(overlay_id); + ImGui::Text("Description: %s", overlay_desc.c_str()); + + // Preview checkbox for vanilla + if (ImGui::Checkbox("Preview Overlay on Map", &show_overlay_preview)) { + // Toggle overlay preview + } + } else { + ImGui::Text("No vanilla overlay for this map"); + } + } else if (is_special_overworld_map && asm_version < 3) { + // Special overworld maps (0x80-0x9F) require ZSCustomOverworld v3+ + ImGui::Text("Special overworld maps require ZSCustomOverworld v3+"); + ImGui::Text("Current version: v%d", asm_version); + ImGui::Text("Map 0x%02X is a special overworld map", current_map); + } else { + // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps always support overlays + // Special overworld maps (0x80-0x9F) support overlays with v3+ + uint16_t current_overlay = overworld_->mutable_overworld_map(current_map)->subscreen_overlay(); + if (gui::InputHexWord("Subscreen Overlay", ¤t_overlay, kInputFieldSize + 20)) { + overworld_->mutable_overworld_map(current_map)->set_subscreen_overlay(current_overlay); + RefreshMapProperties(); + RefreshOverworldMap(); + } + + // Show overlay description + std::string overlay_desc = GetOverlayDescription(current_overlay); + ImGui::Text("Description: %s", overlay_desc.c_str()); + + // Preview checkbox + if (ImGui::Checkbox("Preview Overlay on Map", &show_overlay_preview)) { + // Toggle overlay preview + } + + // Show version info for special maps + if (is_special_overworld_map) { + ImGui::Text("Special overworld map (v%d)", asm_version); + } + } +} + +std::string MapPropertiesSystem::GetOverlayDescription(uint16_t overlay_id) { + if (overlay_id == 0x0093) { + return "Triforce Room Curtain"; + } else if (overlay_id == 0x0094) { + return "Under the Bridge"; + } else if (overlay_id == 0x0095) { + return "Sky Background (LW Death Mountain)"; + } else if (overlay_id == 0x0096) { + return "Pyramid Background"; + } else if (overlay_id == 0x0097) { + return "First Fog Overlay (Master Sword Area)"; + } else if (overlay_id == 0x009C) { + return "Lava Background (DW Death Mountain)"; + } else if (overlay_id == 0x009D) { + return "Second Fog Overlay (Lost Woods/Skull Woods)"; + } else if (overlay_id == 0x009E) { + return "Tree Canopy (Forest)"; + } else if (overlay_id == 0x009F) { + return "Rain Effect (Misery Mire)"; + } else if (overlay_id == 0x00FF) { + return "No Overlay"; + } else { + return "Custom overlay"; + } +} + +void MapPropertiesSystem::DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview) { + if (!show_overlay_preview || !maps_bmp_ || !canvas_) return; + + // Get overlay information based on ROM version and map type + uint16_t overlay_id = 0x00FF; + bool has_overlay = false; + + static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0); + + if (asm_version == 0xFF) { + // Vanilla ROM - use vanilla overlay + auto *current_map_ptr = overworld_->overworld_map(current_map); + if (current_map_ptr->has_vanilla_overlay()) { + overlay_id = current_map_ptr->vanilla_overlay_id(); + has_overlay = true; + } + } else if (is_special_overworld_map && asm_version < 3) { + // Special overworld maps require v3+ - no overlay support + return; + } else { + // Light World (0x00-0x3F) and Dark World (0x40-0x7F) maps always support overlays + // Special overworld maps (0x80-0x9F) support overlays with v3+ + overlay_id = overworld_->overworld_map(current_map)->subscreen_overlay(); + has_overlay = (overlay_id != 0x00FF); + } + + if (!has_overlay) return; + + // Map overlay ID to special area map for bitmap + int overlay_map_index = -1; + if (overlay_id >= 0x80 && overlay_id < 0xA0) { + overlay_map_index = overlay_id; + } + + if (overlay_map_index < 0 || overlay_map_index >= zelda3::kNumOverworldMaps) return; + + // Get the overlay map's bitmap + const auto &overlay_bitmap = (*maps_bmp_)[overlay_map_index]; + if (!overlay_bitmap.is_active()) return; + + // Calculate position for overlay preview on the current map + int current_map_x = current_map % 8; + int current_map_y = current_map / 8; + if (current_world == 1) { + current_map_x = (current_map - 0x40) % 8; + current_map_y = (current_map - 0x40) / 8; + } else if (current_world == 2) { + current_map_x = (current_map - 0x80) % 8; + current_map_y = (current_map - 0x80) / 8; + } + + int scale = static_cast(canvas_->global_scale()); + int map_x = current_map_x * kOverworldMapSize * scale; + int map_y = current_map_y * kOverworldMapSize * scale; + + // Determine if this is a background or foreground overlay + bool is_background_overlay = (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C); + + // Set alpha for semi-transparent preview + ImU32 overlay_color = is_background_overlay ? + IM_COL32(255, 255, 255, 128) : // Background overlays - lighter + IM_COL32(255, 255, 255, 180); // Foreground overlays - more opaque + + // Draw the overlay bitmap with semi-transparency + canvas_->draw_list()->AddImage( + (ImTextureID)(intptr_t)overlay_bitmap.texture(), + ImVec2(map_x, map_y), + ImVec2(map_x + kOverworldMapSize * scale, map_y + kOverworldMapSize * scale), + ImVec2(0, 0), + ImVec2(1, 1), + overlay_color); +} + } // namespace editor } // namespace yaze diff --git a/src/app/editor/overworld/map_properties.h b/src/app/editor/overworld/map_properties.h index c29d2e98..6245a87c 100644 --- a/src/app/editor/overworld/map_properties.h +++ b/src/app/editor/overworld/map_properties.h @@ -4,20 +4,22 @@ #include "app/zelda3/overworld/overworld.h" #include "app/rom.h" #include "app/gui/canvas.h" -#include "imgui/imgui.h" namespace yaze { namespace editor { class MapPropertiesSystem { public: - explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom) - : overworld_(overworld), rom_(rom) {} + explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom, + std::array* maps_bmp = nullptr, + gui::Canvas* canvas = nullptr) + : overworld_(overworld), rom_(rom), maps_bmp_(maps_bmp), canvas_(canvas) {} // Main interface methods 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_custom_bg_color_editor, bool& show_overlay_editor, + bool& show_overlay_preview, int& game_state); void DrawMapPropertiesPanel(int current_map, bool& show_map_properties_panel); @@ -25,6 +27,9 @@ class MapPropertiesSystem { void DrawOverlayEditor(int current_map, bool& show_overlay_editor); + // Overlay preview functionality + void DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview); + // Context menu integration void SetupCanvasContextMenu(gui::Canvas& canvas, int current_map, bool current_map_lock, bool& show_map_properties_panel, bool& show_custom_bg_color_editor, @@ -34,8 +39,13 @@ class MapPropertiesSystem { // Property category drawers void DrawGraphicsPopup(int current_map); void DrawPalettesPopup(int current_map, bool& show_custom_bg_color_editor); - void DrawOverlaysPopup(int current_map, bool& show_overlay_editor); - void DrawPropertiesPopup(int current_map, bool& show_map_properties_panel); + void DrawPropertiesPopup(int current_map, bool& show_map_properties_panel, + bool& show_overlay_preview, int& game_state); + + // Overlay and mosaic functionality + void DrawMosaicControls(int current_map); + void DrawOverlayControls(int current_map, bool& show_overlay_preview); + std::string GetOverlayDescription(uint16_t overlay_id); // Tab content drawers void DrawBasicPropertiesTab(int current_map); @@ -50,6 +60,8 @@ class MapPropertiesSystem { zelda3::Overworld* overworld_; Rom* rom_; + std::array* maps_bmp_; + gui::Canvas* canvas_; // Static constants static constexpr float kInputFieldSize = 30.f; diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index e224c39d..3c85c4b7 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -12,6 +12,7 @@ #include "app/core/window.h" #include "app/editor/graphics/palette_editor.h" #include "app/editor/overworld/entity.h" +#include "app/editor/overworld/map_properties.h" #include "app/gfx/arena.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" @@ -41,9 +42,12 @@ constexpr float kInputFieldSize = 30.f; void OverworldEditor::Initialize() { layout_node_ = gui::zeml::Parse(gui::zeml::LoadFile("overworld.zeml")); + // Initialize MapPropertiesSystem with canvas and bitmap data + map_properties_system_ = std::make_unique(&overworld_, rom_, &maps_bmp_, &ow_map_canvas_); + gui::zeml::Bind(std::to_address(layout_node_.GetNode("OverworldCanvas")), [this]() { DrawOverworldCanvas(); }); - + // Setup overworld canvas context menu SetupOverworldCanvasContextMenu(); gui::zeml::Bind( @@ -462,12 +466,12 @@ void OverworldEditor::DrawCustomOverworldMapSettings() { ImGui::EndTable(); } } - + // Add additional v3 features if (asm_version >= 3 && asm_version != 0xFF) { Separator(); Text("ZSCustomOverworld v3 Features:"); - + // Main Palette if (gui::InputHexByte("Main Palette", overworld_.mutable_overworld_map(current_map_) @@ -477,7 +481,7 @@ void OverworldEditor::DrawCustomOverworldMapSettings() { status_ = RefreshMapPalette(); RefreshOverworldMap(); } - + // Animated GFX if (gui::InputHexByte("Animated GFX", overworld_.mutable_overworld_map(current_map_) @@ -486,7 +490,7 @@ void OverworldEditor::DrawCustomOverworldMapSettings() { RefreshMapProperties(); RefreshOverworldMap(); } - + // Subscreen Overlay if (gui::InputHexWord("Subscreen Overlay", overworld_.mutable_overworld_map(current_map_) @@ -501,236 +505,6 @@ void OverworldEditor::DrawCustomOverworldMapSettings() { } } -void OverworldEditor::DrawSimplifiedMapSettings() { - // Enhanced settings table with popup buttons for quick access - if (BeginTable("SimplifiedMapSettings", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0), -1)) { - ImGui::TableSetupColumn("World", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("Map", ImGuiTableColumnFlags_WidthFixed, 60); - ImGui::TableSetupColumn("Area Size", ImGuiTableColumnFlags_WidthFixed, 100); - ImGui::TableSetupColumn("Lock", ImGuiTableColumnFlags_WidthFixed, 60); - ImGui::TableSetupColumn("Graphics", ImGuiTableColumnFlags_WidthFixed, 70); - ImGui::TableSetupColumn("Palettes", ImGuiTableColumnFlags_WidthFixed, 70); - ImGui::TableSetupColumn("Overlays", ImGuiTableColumnFlags_WidthFixed, 70); - ImGui::TableSetupColumn("Properties", ImGuiTableColumnFlags_WidthFixed, 80); - - TableNextColumn(); - ImGui::SetNextItemWidth(70.f); - if (ImGui::Combo("##world", ¤t_world_, kWorldList.data(), 3)) { - // World changed, update current map if needed - if (current_map_ >= 0x40 && current_world_ == 0) { - current_map_ -= 0x40; - } else if (current_map_ < 0x40 && current_world_ == 1) { - current_map_ += 0x40; - } else if (current_map_ < 0x80 && current_world_ == 2) { - current_map_ += 0x80; - } else if (current_map_ >= 0x80 && current_world_ != 2) { - current_map_ -= 0x80; - } - } - - TableNextColumn(); - ImGui::Text("%d (0x%02X)", current_map_, current_map_); - - TableNextColumn(); - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - if (asm_version != 0xFF) { - static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; - int current_area_size = static_cast(overworld_.overworld_map(current_map_)->area_size()); - ImGui::SetNextItemWidth(90.f); - if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { - overworld_.mutable_overworld_map(current_map_)->SetAreaSize(static_cast(current_area_size)); - RefreshOverworldMap(); - } - } else { - ImGui::Text("N/A"); - } - - TableNextColumn(); - if (ImGui::Button(current_map_lock_ ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN, ImVec2(30, 0))) { - current_map_lock_ = !current_map_lock_; - // Refresh context menu to update lock/unlock text - SetupOverworldCanvasContextMenu(); - } - HOVER_HINT(current_map_lock_ ? "Unlock Map" : "Lock Map"); - - TableNextColumn(); - if (ImGui::Button("Graphics", ImVec2(60, 0))) { - ImGui::OpenPopup("GraphicsPopup"); - } - HOVER_HINT("Graphics Settings"); - if (ImGui::BeginPopup("GraphicsPopup")) { - ImGui::Text("Graphics Settings"); - ImGui::Separator(); - - if (gui::InputHexByte("Area Graphics", - overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(), - kInputFieldSize)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - if (gui::InputHexByte("Sprite GFX 1", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(1), - kInputFieldSize)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - if (gui::InputHexByte("Sprite GFX 2", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(2), - kInputFieldSize)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - if (asm_version >= 3) { - if (gui::InputHexByte("Animated GFX", - overworld_.mutable_overworld_map(current_map_)->mutable_animated_gfx(), - kInputFieldSize)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - } - - ImGui::Separator(); - if (ImGui::Button("Tile Graphics (8 sheets)")) { - show_map_properties_panel_ = true; - } - - ImGui::EndPopup(); - } - - TableNextColumn(); - if (ImGui::Button("Palettes", ImVec2(60, 0))) { - ImGui::OpenPopup("PalettesPopup"); - } - HOVER_HINT("Palette Settings"); - if (ImGui::BeginPopup("PalettesPopup")) { - ImGui::Text("Palette Settings"); - ImGui::Separator(); - - if (gui::InputHexByte("Area Palette", - overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(), - kInputFieldSize)) { - RefreshMapProperties(); - status_ = RefreshMapPalette(); - RefreshOverworldMap(); - } - - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - if (asm_version >= 2) { - if (gui::InputHexByte("Main Palette", - overworld_.mutable_overworld_map(current_map_)->mutable_main_palette(), - kInputFieldSize)) { - RefreshMapProperties(); - status_ = RefreshMapPalette(); - RefreshOverworldMap(); - } - } - - if (gui::InputHexByte("Sprite Palette 1", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(1), - kInputFieldSize)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - if (gui::InputHexByte("Sprite Palette 2", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(2), - kInputFieldSize)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - ImGui::Separator(); - if (ImGui::Button("Background Color")) { - show_custom_bg_color_editor_ = !show_custom_bg_color_editor_; - } - - ImGui::EndPopup(); - } - - TableNextColumn(); - if (ImGui::Button("Overlays", ImVec2(60, 0))) { - ImGui::OpenPopup("OverlaysPopup"); - } - HOVER_HINT("Overlay Settings"); - if (ImGui::BeginPopup("OverlaysPopup")) { - ImGui::Text("Overlay Settings"); - ImGui::Separator(); - - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - if (asm_version >= 3) { - if (gui::InputHexWord("Subscreen Overlay", - overworld_.mutable_overworld_map(current_map_)->mutable_subscreen_overlay(), - kInputFieldSize + 20)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - ImGui::Separator(); - if (ImGui::Button("Overlay Settings")) { - show_overlay_editor_ = !show_overlay_editor_; - } - } else { - ImGui::Text("Overlays require ZSCustomOverworld v3+"); - } - - ImGui::EndPopup(); - } - - TableNextColumn(); - if (ImGui::Button("Properties", ImVec2(70, 0))) { - ImGui::OpenPopup("PropertiesPopup"); - } - HOVER_HINT("Map Properties"); - if (ImGui::BeginPopup("PropertiesPopup")) { - ImGui::Text("Map Properties"); - ImGui::Separator(); - - if (gui::InputHexWord("Message ID", - overworld_.mutable_overworld_map(current_map_)->mutable_message_id(), - kInputFieldSize + 20)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - if (ImGui::Checkbox("Mosaic Effect", - overworld_.mutable_overworld_map(current_map_)->mutable_mosaic())) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - ImGui::SetNextItemWidth(100.f); - if (ImGui::Combo("Game State", &game_state_, kGamePartComboString.data(), 3)) { - RefreshMapProperties(); - RefreshOverworldMap(); - } - - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - if (asm_version != 0xFF) { - static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; - int current_area_size = static_cast(overworld_.overworld_map(current_map_)->area_size()); - ImGui::SetNextItemWidth(120.f); - if (ImGui::Combo("Area Size", ¤t_area_size, area_size_names, 4)) { - overworld_.mutable_overworld_map(current_map_)->SetAreaSize(static_cast(current_area_size)); - RefreshOverworldMap(); - } - } - - ImGui::Separator(); - if (ImGui::Button("Full Properties Panel")) { - show_map_properties_panel_ = true; - } - - ImGui::EndPopup(); - } - - ImGui::EndTable(); - } -} - void OverworldEditor::DrawOverworldMaps() { int xx = 0; int yy = 0; @@ -1012,13 +786,13 @@ absl::Status OverworldEditor::CheckForCurrentMap() { } else if (current_world_ == 2) { hovered_map += 0x80; } - + // Only update current_map_ if not locked if (!current_map_lock_) { current_map_ = hovered_map; current_parent_ = overworld_.overworld_map(current_map_)->parent(); } - + const int current_highlighted_map = current_map_; if (overworld_.overworld_map(current_map_)->is_large_map() || @@ -1072,7 +846,9 @@ void OverworldEditor::CheckForMousePan() { void OverworldEditor::DrawOverworldCanvas() { if (all_gfx_loaded_) { if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) { - DrawSimplifiedMapSettings(); + map_properties_system_->DrawSimplifiedMapSettings( + current_world_, current_map_, current_map_lock_, show_map_properties_panel_, + show_custom_bg_color_editor_, show_overlay_editor_, show_overlay_preview_, game_state_); } else { DrawOverworldMapSettings(); } @@ -1100,6 +876,12 @@ void OverworldEditor::DrawOverworldCanvas() { ow_map_canvas_.scrolling()); DrawOverworldItems(); DrawOverworldSprites(); + + // Draw overlay preview if enabled + if (show_overlay_preview_) { + map_properties_system_->DrawOverlayPreviewOnMap(current_map_, current_world_, show_overlay_preview_); + } + if (current_mode == EditingMode::DRAW_TILE) { CheckForOverworldEdits(); } @@ -1401,19 +1183,17 @@ void OverworldEditor::DrawOverworldSprites() { int i = 0; for (auto &sprite : *overworld_.mutable_sprites(game_state_)) { // Filter sprites by current world - only show sprites for the current world - if (!sprite.deleted() && - sprite.map_id() < 0x40 + (current_world_ * 0x40) && + if (!sprite.deleted() && sprite.map_id() < 0x40 + (current_world_ * 0x40) && sprite.map_id() >= (current_world_ * 0x40)) { - - // Sprites are already stored with global coordinates (realX, realY from ROM loading) - // So we can use sprite.x_ and sprite.y_ directly + // Sprites are already stored with global coordinates (realX, realY from + // ROM loading) So we can use sprite.x_ and sprite.y_ directly int sprite_x = sprite.x_; int sprite_y = sprite.y_; - + // Temporarily update sprite coordinates for entity interaction int original_x = sprite.x_; int original_y = sprite.y_; - + ow_map_canvas_.DrawRect(sprite_x, sprite_y, kTile16Size, kTile16Size, /*magenta=*/ImVec4(255, 0, 255, 150)); if (current_mode == EditingMode::SPRITES) { @@ -1429,14 +1209,14 @@ void OverworldEditor::DrawOverworldSprites() { } if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) { if (sprite_previews_[sprite.id()].is_active()) { - ow_map_canvas_.DrawBitmap(sprite_previews_[sprite.id()], sprite_x, sprite_y, - 2.0f); + ow_map_canvas_.DrawBitmap(sprite_previews_[sprite.id()], sprite_x, + sprite_y, 2.0f); } } ow_map_canvas_.DrawText(absl::StrFormat("%s", sprite.name()), sprite_x, sprite_y); - + // Restore original coordinates sprite.x_ = original_x; sprite.y_ = original_y; @@ -1664,143 +1444,286 @@ absl::Status OverworldEditor::RefreshTile16Blockset() { } void OverworldEditor::DrawCustomBackgroundColorEditor() { - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - + static uint8_t asm_version = + (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + if (asm_version < 2 || asm_version == 0xFF) { - Text("Custom background colors are only available in ZSCustomOverworld v2+"); + Text( + "Custom background colors are only available in ZSCustomOverworld v2+"); return; } - + // Check if area-specific background colors are enabled - bool bg_enabled = (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] != 0x00; + bool bg_enabled = + (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] != 0x00; if (Checkbox("Enable Area-Specific Background Colors", &bg_enabled)) { - (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] = bg_enabled ? 0x01 : 0x00; + (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] = + bg_enabled ? 0x01 : 0x00; } - + if (!bg_enabled) { Text("Area-specific background colors are disabled."); return; } - + Separator(); - + // Display current map's background color Text("Current Map: %d (0x%02X)", current_map_, current_map_); - + // Get current background color - uint16_t current_bg_color = (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2)] | - ((*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2) + 1] << 8); - + uint16_t current_bg_color = + (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + + (current_map_ * 2)] | + ((*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + + (current_map_ * 2) + 1] + << 8); + // Convert SNES color to ImVec4 - ImVec4 current_color = ImVec4( - ((current_bg_color & 0x1F) * 8) / 255.0f, - (((current_bg_color >> 5) & 0x1F) * 8) / 255.0f, - (((current_bg_color >> 10) & 0x1F) * 8) / 255.0f, - 1.0f - ); - + ImVec4 current_color = + ImVec4(((current_bg_color & 0x1F) * 8) / 255.0f, + (((current_bg_color >> 5) & 0x1F) * 8) / 255.0f, + (((current_bg_color >> 10) & 0x1F) * 8) / 255.0f, 1.0f); + // Color picker - if (ColorPicker4("Background Color", (float*)¤t_color, - ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_InputRGB)) { + if (ColorPicker4( + "Background Color", (float *)¤t_color, + ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_InputRGB)) { // Convert ImVec4 back to SNES color - uint16_t new_color = - (static_cast(current_color.x * 31) & 0x1F) | - ((static_cast(current_color.y * 31) & 0x1F) << 5) | - ((static_cast(current_color.z * 31) & 0x1F) << 10); - + uint16_t new_color = + (static_cast(current_color.x * 31) & 0x1F) | + ((static_cast(current_color.y * 31) & 0x1F) << 5) | + ((static_cast(current_color.z * 31) & 0x1F) << 10); + // Write to ROM - (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2)] = new_color & 0xFF; - (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2) + 1] = (new_color >> 8) & 0xFF; - + (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2)] = + new_color & 0xFF; + (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2) + + 1] = (new_color >> 8) & 0xFF; + // Update the overworld map - overworld_.mutable_overworld_map(current_map_)->set_area_specific_bg_color(new_color); - + overworld_.mutable_overworld_map(current_map_) + ->set_area_specific_bg_color(new_color); + // Refresh the map RefreshOverworldMap(); } - + Separator(); - + // Show color preview Text("Color Preview:"); - ImGui::ColorButton("##bg_preview", current_color, ImGuiColorEditFlags_NoTooltip, ImVec2(100, 50)); - + ImGui::ColorButton("##bg_preview", current_color, + ImGuiColorEditFlags_NoTooltip, ImVec2(100, 50)); + SameLine(); Text("SNES Color: 0x%04X", current_bg_color); } void OverworldEditor::DrawOverlayEditor() { - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; - - if (asm_version < 1 || asm_version == 0xFF) { + static uint8_t asm_version = + (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + + // Handle vanilla ROMs + if (asm_version == 0xFF) { + Text("Vanilla ROM - Overlay Information:"); + Separator(); + + Text("Current Map: %d (0x%02X)", current_map_, current_map_); + + // Show vanilla overlay information + auto *current_map = overworld_.overworld_map(current_map_); + if (current_map->has_vanilla_overlay()) { + Text("Vanilla Overlay ID: 0x%04X", current_map->vanilla_overlay_id()); + Text("Overlay Data Size: %d bytes", + static_cast(current_map->vanilla_overlay_data().size())); + + Separator(); + if (Checkbox("Show Overlay Preview", &show_overlay_preview_)) { + // Toggle overlay preview + } + + if (show_overlay_preview_) { + DrawOverlayPreview(); + } + } else { + Text("No vanilla overlay data for this map"); + } + + Separator(); + Text( + "Note: Vanilla overlays are read-only. Use ZSCustomOverworld v1+ for " + "editable overlays."); + return; + } + + if (asm_version < 1) { Text("Overlay editor is only available in ZSCustomOverworld v1+"); return; } - + // Check if subscreen overlays are enabled - bool overlay_enabled = (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] != 0x00; + bool overlay_enabled = + (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] != 0x00; if (Checkbox("Enable Subscreen Overlays", &overlay_enabled)) { - (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] = overlay_enabled ? 0x01 : 0x00; + (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] = + overlay_enabled ? 0x01 : 0x00; } - + if (!overlay_enabled) { Text("Subscreen overlays are disabled."); return; } - + Separator(); - + // Display current map's overlay Text("Current Map: %d (0x%02X)", current_map_, current_map_); - + // Get current overlay ID - uint16_t current_overlay = (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2)] | - ((*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2) + 1] << 8); - + uint16_t current_overlay = + (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + + (current_map_ * 2)] | + ((*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + + (current_map_ * 2) + 1] + << 8); + // Overlay ID input if (gui::InputHexWord("Overlay ID", ¤t_overlay, 100)) { // Write to ROM - (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2)] = current_overlay & 0xFF; - (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2) + 1] = (current_overlay >> 8) & 0xFF; - + (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2)] = + current_overlay & 0xFF; + (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2) + + 1] = (current_overlay >> 8) & 0xFF; + // Update the overworld map - overworld_.mutable_overworld_map(current_map_)->set_subscreen_overlay(current_overlay); - + overworld_.mutable_overworld_map(current_map_) + ->set_subscreen_overlay(current_overlay); + // Refresh the map RefreshOverworldMap(); } - + Separator(); - + // Show overlay information Text("Overlay Information:"); Text("ID: 0x%04X", current_overlay); - + if (current_overlay == 0x00FF) { Text("No overlay"); - } else if (current_overlay == 0x009D) { - Text("Fog 2 (Lost Woods/Skull Woods)"); + } else if (current_overlay == 0x0093) { + Text("Triforce Room Curtain"); + } else if (current_overlay == 0x0094) { + Text("Under the Bridge"); } else if (current_overlay == 0x0095) { - Text("Sky Background (Death Mountain)"); - } else if (current_overlay == 0x009C) { - Text("Lava (Dark World Death Mountain)"); + Text("Sky Background (LW Death Mountain)"); } else if (current_overlay == 0x0096) { Text("Pyramid Background"); } else if (current_overlay == 0x0097) { - Text("Fog 1 (Master Sword Area)"); - } else if (current_overlay == 0x0093) { - Text("Triforce Room Curtains"); + Text("First Fog Overlay (Master Sword Area)"); + } else if (current_overlay == 0x009C) { + Text("Lava Background (DW Death Mountain)"); + } else if (current_overlay == 0x009D) { + Text("Second Fog Overlay (Lost Woods/Skull Woods)"); + } else if (current_overlay == 0x009E) { + Text("Tree Canopy (Forest)"); + } else if (current_overlay == 0x009F) { + Text("Rain Effect (Misery Mire)"); } else { Text("Custom overlay"); } } +void OverworldEditor::DrawOverlayPreview() { + if (!show_overlay_preview_) return; + + auto *current_map = overworld_.overworld_map(current_map_); + if (!current_map->has_vanilla_overlay()) return; + + Text("Overlay Preview:"); + Separator(); + + // Get the overlay ID to determine what visual effect this is + uint16_t overlay_id = current_map->vanilla_overlay_id(); + + // Show overlay information + Text("Overlay ID: 0x%04X", overlay_id); + + // Show overlay description based on common overlay IDs + std::string overlay_desc = ""; + if (overlay_id == 0x0093) { + overlay_desc = "Triforce Room Curtain"; + } else if (overlay_id == 0x0094) { + overlay_desc = "Under the Bridge"; + } else if (overlay_id == 0x0095) { + overlay_desc = "Sky Background (LW Death Mountain)"; + } else if (overlay_id == 0x0096) { + overlay_desc = "Pyramid Background"; + } else if (overlay_id == 0x0097) { + overlay_desc = "First Fog Overlay (Master Sword Area)"; + } else if (overlay_id == 0x009C) { + overlay_desc = "Lava Background (DW Death Mountain)"; + } else if (overlay_id == 0x009D) { + overlay_desc = "Second Fog Overlay (Lost Woods/Skull Woods)"; + } else if (overlay_id == 0x009E) { + overlay_desc = "Tree Canopy (Forest)"; + } else if (overlay_id == 0x009F) { + overlay_desc = "Rain Effect (Misery Mire)"; + } else if (overlay_id == 0x00FF) { + overlay_desc = "No Overlay"; + } else { + overlay_desc = "Custom overlay effect"; + } + Text("Description: %s", overlay_desc.c_str()); + + Separator(); + + // Map overlay ID to special area map for preview + int overlay_map_index = -1; + if (overlay_id >= 0x80 && overlay_id < 0xA0) { + overlay_map_index = overlay_id; + } + + if (overlay_map_index >= 0 && overlay_map_index < zelda3::kNumOverworldMaps) { + Text("Overlay Source Map: %d (0x%02X)", overlay_map_index, overlay_map_index); + + // Get the overlay map's bitmap + const auto &overlay_bitmap = maps_bmp_[overlay_map_index]; + + if (overlay_bitmap.is_active()) { + // Display the overlay map bitmap + ImVec2 image_size(256, 256); // Scale down for preview + ImGui::Image((ImTextureID)(intptr_t)overlay_bitmap.texture(), image_size); + + Separator(); + Text("This overlay would be displayed semi-transparently"); + Text("on top of the current map when active."); + + // Show drawing order info + if (overlay_id == 0x0095 || overlay_id == 0x0096) { + Text("Note: This overlay is drawn as a background"); + Text("(behind the main map tiles)."); + } else { + Text("Note: This overlay is drawn on top of"); + Text("the main map tiles."); + } + } else { + Text("Overlay map bitmap not available"); + } + } else { + Text("Unknown overlay ID: 0x%04X", overlay_id); + Text("Could not determine overlay source map"); + } +} + + void OverworldEditor::DrawMapLockControls() { if (current_map_lock_) { PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); Text("Map Locked: %d (0x%02X)", current_map_, current_map_); PopStyleColor(); - + if (Button("Unlock Map")) { current_map_lock_ = false; } @@ -1823,13 +1746,13 @@ void OverworldEditor::DrawOverworldContextMenu() { } else if (current_world_ == 2) { hovered_map += 0x80; } - + // Only show context menu if we're hovering over a valid map if (hovered_map >= 0 && hovered_map < 0xA0) { if (ImGui::BeginPopupContextWindow("OverworldMapContext")) { Text("Map %d (0x%02X)", hovered_map, hovered_map); Separator(); - + // Map lock controls if (current_map_lock_ && current_map_ == hovered_map) { PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); @@ -1844,41 +1767,51 @@ void OverworldEditor::DrawOverworldContextMenu() { current_map_ = hovered_map; } } - + Separator(); - + // Quick access to map settings if (MenuItem("Map Properties")) { show_properties_editor_ = true; current_map_ = hovered_map; } - - // Custom overworld features (only show if v3+) - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + + // Custom overworld features + static uint8_t asm_version = + (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; if (asm_version >= 3 && asm_version != 0xFF) { if (MenuItem("Custom Background Color")) { show_custom_bg_color_editor_ = true; current_map_ = hovered_map; } - + if (MenuItem("Overlay Settings")) { show_overlay_editor_ = true; current_map_ = hovered_map; } + } else if (asm_version == 0xFF) { + // Show vanilla overlay information + auto *hovered_map_obj = overworld_.overworld_map(hovered_map); + if (hovered_map_obj->has_vanilla_overlay()) { + if (MenuItem("View Vanilla Overlay")) { + show_overlay_editor_ = true; + current_map_ = hovered_map; + } + } } - + Separator(); - + // Canvas controls if (MenuItem("Reset Canvas Position")) { ow_map_canvas_.set_scrolling(ImVec2(0, 0)); } - + if (MenuItem("Zoom to Fit")) { ow_map_canvas_.set_global_scale(1.0f); ow_map_canvas_.set_scrolling(ImVec2(0, 0)); } - + ImGui::EndPopup(); } } @@ -1886,7 +1819,8 @@ void OverworldEditor::DrawOverworldContextMenu() { void OverworldEditor::HandleMapInteraction() { // Handle middle-click for map interaction instead of right-click - if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle) && ImGui::IsItemHovered()) { + if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle) && + ImGui::IsItemHovered()) { // Get the current map from mouse position auto mouse_position = ow_map_canvas_.drawn_tile_position(); int map_x = mouse_position.x / kOverworldMapSize; @@ -1897,7 +1831,7 @@ void OverworldEditor::HandleMapInteraction() { } else if (current_world_ == 2) { hovered_map += 0x80; } - + // Only interact if we're hovering over a valid map if (hovered_map >= 0 && hovered_map < 0xA0) { // Toggle map lock or open properties panel @@ -1910,9 +1844,10 @@ void OverworldEditor::HandleMapInteraction() { } } } - + // Handle double-click to open properties panel - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && ImGui::IsItemHovered()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && + ImGui::IsItemHovered()) { show_map_properties_panel_ = true; } } @@ -1932,25 +1867,28 @@ void OverworldEditor::DrawMapPropertiesPanel() { } else { Text("Current Map: %d (0x%02X)", current_map_, current_map_); } - + SameLine(); if (Button(current_map_lock_ ? "Unlock" : "Lock")) { current_map_lock_ = !current_map_lock_; } ImGui::EndGroup(); - + Separator(); - + // Create tabs for different property categories if (BeginTabBar("MapPropertiesTabs", ImGuiTabBarFlags_FittingPolicyScroll)) { - // Basic Properties Tab if (BeginTabItem("Basic Properties")) { - if (BeginTable("BasicProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + if (BeginTable( + "BasicProperties", 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, + 150); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - - TableNextColumn(); Text("World"); + + TableNextColumn(); + Text("World"); TableNextColumn(); ImGui::SetNextItemWidth(100.f); if (ImGui::Combo("##world", ¤t_world_, kWorldList.data(), 3)) { @@ -1965,158 +1903,195 @@ void OverworldEditor::DrawMapPropertiesPanel() { current_map_ -= 0x80; } } - - TableNextColumn(); Text("Area Graphics"); + TableNextColumn(); - if (gui::InputHexByte("##AreaGfx", - overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(), + Text("Area Graphics"); + TableNextColumn(); + if (gui::InputHexByte("##AreaGfx", + overworld_.mutable_overworld_map(current_map_) + ->mutable_area_graphics(), kInputFieldSize)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Area Palette"); + TableNextColumn(); - if (gui::InputHexByte("##AreaPal", - overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(), + Text("Area Palette"); + TableNextColumn(); + if (gui::InputHexByte("##AreaPal", + overworld_.mutable_overworld_map(current_map_) + ->mutable_area_palette(), kInputFieldSize)) { RefreshMapProperties(); status_ = RefreshMapPalette(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Message ID"); + TableNextColumn(); - if (gui::InputHexWord("##MsgId", - overworld_.mutable_overworld_map(current_map_)->mutable_message_id(), + Text("Message ID"); + TableNextColumn(); + if (gui::InputHexWord("##MsgId", + overworld_.mutable_overworld_map(current_map_) + ->mutable_message_id(), kInputFieldSize + 20)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Mosaic Effect"); + TableNextColumn(); - if (ImGui::Checkbox("##mosaic", - overworld_.mutable_overworld_map(current_map_)->mutable_mosaic())) { + Text("Mosaic Effect"); + TableNextColumn(); + if (ImGui::Checkbox("##mosaic", + overworld_.mutable_overworld_map(current_map_) + ->mutable_mosaic())) { RefreshMapProperties(); RefreshOverworldMap(); } HOVER_HINT("Enable Mosaic effect for the current map"); - + ImGui::EndTable(); } EndTabItem(); } - + // Sprite Properties Tab if (BeginTabItem("Sprite Properties")) { - if (BeginTable("SpriteProperties", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + if (BeginTable( + "SpriteProperties", 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, + 150); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - - TableNextColumn(); Text("Game State"); + + TableNextColumn(); + Text("Game State"); TableNextColumn(); ImGui::SetNextItemWidth(100.f); - if (ImGui::Combo("##GameState", &game_state_, kGamePartComboString.data(), 3)) { + if (ImGui::Combo("##GameState", &game_state_, + kGamePartComboString.data(), 3)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Sprite Graphics 1"); + TableNextColumn(); - if (gui::InputHexByte("##SprGfx1", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(1), + Text("Sprite Graphics 1"); + TableNextColumn(); + if (gui::InputHexByte("##SprGfx1", + overworld_.mutable_overworld_map(current_map_) + ->mutable_sprite_graphics(1), kInputFieldSize)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Sprite Graphics 2"); + TableNextColumn(); - if (gui::InputHexByte("##SprGfx2", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_graphics(2), + Text("Sprite Graphics 2"); + TableNextColumn(); + if (gui::InputHexByte("##SprGfx2", + overworld_.mutable_overworld_map(current_map_) + ->mutable_sprite_graphics(2), kInputFieldSize)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Sprite Palette 1"); + TableNextColumn(); - if (gui::InputHexByte("##SprPal1", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(1), + Text("Sprite Palette 1"); + TableNextColumn(); + if (gui::InputHexByte("##SprPal1", + overworld_.mutable_overworld_map(current_map_) + ->mutable_sprite_palette(1), kInputFieldSize)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Sprite Palette 2"); + TableNextColumn(); - if (gui::InputHexByte("##SprPal2", - overworld_.mutable_overworld_map(current_map_)->mutable_sprite_palette(2), + Text("Sprite Palette 2"); + TableNextColumn(); + if (gui::InputHexByte("##SprPal2", + overworld_.mutable_overworld_map(current_map_) + ->mutable_sprite_palette(2), kInputFieldSize)) { RefreshMapProperties(); RefreshOverworldMap(); } - + ImGui::EndTable(); } EndTabItem(); } - + // Custom Overworld Features Tab - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + static uint8_t asm_version = + (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; if (asm_version != 0xFF && BeginTabItem("Custom Features")) { - if (BeginTable("CustomFeatures", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 150); + if (BeginTable( + "CustomFeatures", 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, + 150); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); - - TableNextColumn(); Text("Area Size"); + TableNextColumn(); - static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", "Wide (2x1)", "Tall (1x2)"}; - int current_area_size = static_cast(overworld_.overworld_map(current_map_)->area_size()); + Text("Area Size"); + TableNextColumn(); + static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)", + "Wide (2x1)", "Tall (1x2)"}; + int current_area_size = static_cast( + overworld_.overworld_map(current_map_)->area_size()); ImGui::SetNextItemWidth(120.f); - if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, 4)) { - overworld_.mutable_overworld_map(current_map_)->SetAreaSize(static_cast(current_area_size)); + if (ImGui::Combo("##AreaSize", ¤t_area_size, area_size_names, + 4)) { + overworld_.mutable_overworld_map(current_map_) + ->SetAreaSize( + static_cast(current_area_size)); RefreshOverworldMap(); } - + if (asm_version >= 2) { - TableNextColumn(); Text("Main Palette"); TableNextColumn(); - if (gui::InputHexByte("##MainPal", - overworld_.mutable_overworld_map(current_map_)->mutable_main_palette(), + Text("Main Palette"); + TableNextColumn(); + if (gui::InputHexByte("##MainPal", + overworld_.mutable_overworld_map(current_map_) + ->mutable_main_palette(), kInputFieldSize)) { RefreshMapProperties(); status_ = RefreshMapPalette(); RefreshOverworldMap(); } } - + if (asm_version >= 3) { - TableNextColumn(); Text("Animated GFX"); TableNextColumn(); - if (gui::InputHexByte("##AnimGfx", - overworld_.mutable_overworld_map(current_map_)->mutable_animated_gfx(), + Text("Animated GFX"); + TableNextColumn(); + if (gui::InputHexByte("##AnimGfx", + overworld_.mutable_overworld_map(current_map_) + ->mutable_animated_gfx(), kInputFieldSize)) { RefreshMapProperties(); RefreshOverworldMap(); } - - TableNextColumn(); Text("Subscreen Overlay"); + TableNextColumn(); - if (gui::InputHexWord("##SubOverlay", - overworld_.mutable_overworld_map(current_map_)->mutable_subscreen_overlay(), + Text("Subscreen Overlay"); + TableNextColumn(); + if (gui::InputHexWord("##SubOverlay", + overworld_.mutable_overworld_map(current_map_) + ->mutable_subscreen_overlay(), kInputFieldSize + 20)) { RefreshMapProperties(); RefreshOverworldMap(); } } - + ImGui::EndTable(); } - + Separator(); - + // Quick action buttons ImGui::BeginGroup(); if (Button("Custom Background Color")) { @@ -2127,48 +2102,52 @@ void OverworldEditor::DrawMapPropertiesPanel() { show_overlay_editor_ = !show_overlay_editor_; } ImGui::EndGroup(); - + EndTabItem(); } - + // Tile Graphics Tab if (BeginTabItem("Tile Graphics")) { Text("Custom Tile Graphics (8 sheets per map):"); Separator(); - - if (BeginTable("TileGraphics", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60); - ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80); - ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 60); - ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 80); - + + if (BeginTable( + "TileGraphics", 4, + ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 120); + ImGui::TableSetupColumn("Sheet", ImGuiTableColumnFlags_WidthFixed, 80); + ImGui::TableSetupColumn("GFX ID", ImGuiTableColumnFlags_WidthFixed, 120); + for (int i = 0; i < 4; i++) { TableNextColumn(); Text("Sheet %d", i); TableNextColumn(); - if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(), - overworld_.mutable_overworld_map(current_map_)->mutable_custom_tileset(i), - kInputFieldSize)) { + if (gui::InputHexByte(absl::StrFormat("Sheet %d GFX", i).c_str(), + overworld_.mutable_overworld_map(current_map_) + ->mutable_custom_tileset(i), + 100.f)) { RefreshMapProperties(); RefreshOverworldMap(); } - + TableNextColumn(); Text("Sheet %d", i + 4); TableNextColumn(); - if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i + 4).c_str(), - overworld_.mutable_overworld_map(current_map_)->mutable_custom_tileset(i + 4), - kInputFieldSize)) { + if (gui::InputHexByte(absl::StrFormat("Sheet %d GFX", i + 4).c_str(), + overworld_.mutable_overworld_map(current_map_) + ->mutable_custom_tileset(i + 4), + 100.f)) { RefreshMapProperties(); RefreshOverworldMap(); } } - + ImGui::EndTable(); } EndTabItem(); } - + EndTabBar(); } } @@ -2176,7 +2155,7 @@ void OverworldEditor::DrawMapPropertiesPanel() { void OverworldEditor::SetupOverworldCanvasContextMenu() { // Clear any existing context menu items ow_map_canvas_.ClearContextMenuItems(); - + // Add overworld-specific context menu items gui::Canvas::ContextMenuItem lock_item; lock_item.label = current_map_lock_ ? "Unlock Map" : "Lock to This Map"; @@ -2199,35 +2178,30 @@ void OverworldEditor::SetupOverworldCanvasContextMenu() { } }; ow_map_canvas_.AddContextMenuItem(lock_item); - + // Map Properties gui::Canvas::ContextMenuItem properties_item; properties_item.label = "Map Properties"; - properties_item.callback = [this]() { - show_map_properties_panel_ = true; - }; + properties_item.callback = [this]() { show_map_properties_panel_ = true; }; ow_map_canvas_.AddContextMenuItem(properties_item); - + // Custom overworld features (only show if v3+) - static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + static uint8_t asm_version = + (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; if (asm_version >= 3 && asm_version != 0xFF) { // Custom Background Color gui::Canvas::ContextMenuItem bg_color_item; bg_color_item.label = "Custom Background Color"; - bg_color_item.callback = [this]() { - show_custom_bg_color_editor_ = true; - }; + bg_color_item.callback = [this]() { show_custom_bg_color_editor_ = true; }; ow_map_canvas_.AddContextMenuItem(bg_color_item); - + // Overlay Settings gui::Canvas::ContextMenuItem overlay_item; overlay_item.label = "Overlay Settings"; - overlay_item.callback = [this]() { - show_overlay_editor_ = true; - }; + overlay_item.callback = [this]() { show_overlay_editor_ = true; }; ow_map_canvas_.AddContextMenuItem(overlay_item); } - + // Canvas controls gui::Canvas::ContextMenuItem reset_pos_item; reset_pos_item.label = "Reset Canvas Position"; @@ -2235,7 +2209,7 @@ void OverworldEditor::SetupOverworldCanvasContextMenu() { ow_map_canvas_.set_scrolling(ImVec2(0, 0)); }; ow_map_canvas_.AddContextMenuItem(reset_pos_item); - + gui::Canvas::ContextMenuItem zoom_fit_item; zoom_fit_item.label = "Zoom to Fit"; zoom_fit_item.callback = [this]() { diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 1fdb705d..f48cb881 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -6,6 +6,7 @@ #include "app/editor/graphics/gfx_group_editor.h" #include "app/editor/graphics/palette_editor.h" #include "app/editor/overworld/tile16_editor.h" +#include "app/editor/overworld/map_properties.h" #include "app/gfx/bitmap.h" #include "app/gfx/snes_palette.h" #include "app/gfx/tilemap.h" @@ -77,6 +78,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { explicit OverworldEditor(Rom* rom) : rom_(rom) { type_ = EditorType::kOverworld; gfx_group_editor_.set_rom(rom); + // MapPropertiesSystem will be initialized after maps_bmp_ and canvas are ready } void Initialize() override; @@ -161,6 +163,8 @@ class OverworldEditor : public Editor, public gfx::GfxContext { void DrawCustomBackgroundColorEditor(); void DrawOverlayEditor(); void DrawMapLockControls(); + void DrawOverlayPreview(); + void DrawOverlayPreviewOnMap(); void DrawOverworldContextMenu(); void DrawSimplifiedMapSettings(); void DrawMapPropertiesPanel(); @@ -227,6 +231,10 @@ class OverworldEditor : public Editor, public gfx::GfxContext { bool show_overlay_editor_ = false; bool use_area_specific_bg_color_ = false; bool show_map_properties_panel_ = false; + bool show_overlay_preview_ = false; + + // Map properties system for UI organization + std::unique_ptr map_properties_system_; gfx::Tilemap tile16_blockset_; diff --git a/src/app/zelda3/overworld/overworld_map.cc b/src/app/zelda3/overworld/overworld_map.cc index 34b47472..01681534 100644 --- a/src/app/zelda3/overworld/overworld_map.cc +++ b/src/app/zelda3/overworld/overworld_map.cc @@ -58,6 +58,7 @@ absl::Status OverworldMap::BuildMap(int count, int game_state, int world, RETURN_IF_ERROR(BuildTileset()) RETURN_IF_ERROR(BuildTiles16Gfx(tiles16, count)) RETURN_IF_ERROR(LoadPalette()); + RETURN_IF_ERROR(LoadVanillaOverlay()); RETURN_IF_ERROR(BuildBitmap(world_blockset)) built_ = true; return absl::OkStatus(); @@ -824,6 +825,138 @@ absl::Status OverworldMap::LoadPalette() { return absl::OkStatus(); } +absl::Status OverworldMap::LoadVanillaOverlay() { + uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; + + // Only load vanilla overlays if this is a vanilla ROM (asm_version == 0xFF) + if (asm_version != 0xFF) { + has_vanilla_overlay_ = false; + vanilla_overlay_id_ = 0; + vanilla_overlay_data_.clear(); + return absl::OkStatus(); + } + + // Load vanilla overlay for this map + int address = (kOverlayPointersBank << 16) + + ((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) + + (*rom_)[kOverlayPointers + (index_ * 2)]; + + // Convert SNES address to PC address + address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF); + + // Check if custom overlay code is present + if ((*rom_)[kOverlayData1] == 0x6B) { + // Use custom overlay data pointer + address = ((*rom_)[kOverlayData2 + 2 + (index_ * 3)] << 16) + + ((*rom_)[kOverlayData2 + 1 + (index_ * 3)] << 8) + + (*rom_)[kOverlayData2 + (index_ * 3)]; + address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF); + } + + // Validate address + if (address >= rom_->size()) { + has_vanilla_overlay_ = false; + vanilla_overlay_id_ = 0; + vanilla_overlay_data_.clear(); + return absl::OkStatus(); + } + + // Parse overlay data + vanilla_overlay_data_.clear(); + uint8_t b = (*rom_)[address]; + + // Parse overlay commands until we hit END (0x60) + while (b != 0x60 && address < rom_->size()) { + vanilla_overlay_data_.push_back(b); + + // Handle different overlay commands + switch (b) { + case 0xA9: // LDA #$ + if (address + 2 < rom_->size()) { + vanilla_overlay_data_.push_back((*rom_)[address + 1]); + vanilla_overlay_data_.push_back((*rom_)[address + 2]); + address += 3; + } else { + address++; + } + break; + case 0xA2: // LDX #$ + if (address + 2 < rom_->size()) { + vanilla_overlay_data_.push_back((*rom_)[address + 1]); + vanilla_overlay_data_.push_back((*rom_)[address + 2]); + address += 3; + } else { + address++; + } + break; + case 0x8D: // STA $xxxx + if (address + 3 < rom_->size()) { + vanilla_overlay_data_.push_back((*rom_)[address + 1]); + vanilla_overlay_data_.push_back((*rom_)[address + 2]); + vanilla_overlay_data_.push_back((*rom_)[address + 3]); + address += 4; + } else { + address++; + } + break; + case 0x9D: // STA $xxxx,x + if (address + 3 < rom_->size()) { + vanilla_overlay_data_.push_back((*rom_)[address + 1]); + vanilla_overlay_data_.push_back((*rom_)[address + 2]); + vanilla_overlay_data_.push_back((*rom_)[address + 3]); + address += 4; + } else { + address++; + } + break; + case 0x8F: // STA $xxxxxx + if (address + 4 < rom_->size()) { + vanilla_overlay_data_.push_back((*rom_)[address + 1]); + vanilla_overlay_data_.push_back((*rom_)[address + 2]); + vanilla_overlay_data_.push_back((*rom_)[address + 3]); + vanilla_overlay_data_.push_back((*rom_)[address + 4]); + address += 5; + } else { + address++; + } + break; + case 0x1A: // INC A + address++; + break; + case 0x4C: // JMP + if (address + 3 < rom_->size()) { + vanilla_overlay_data_.push_back((*rom_)[address + 1]); + vanilla_overlay_data_.push_back((*rom_)[address + 2]); + vanilla_overlay_data_.push_back((*rom_)[address + 3]); + address += 4; + } else { + address++; + } + break; + default: + address++; + break; + } + + if (address < rom_->size()) { + b = (*rom_)[address]; + } else { + break; + } + } + + // Add the END command if we found it + if (b == 0x60) { + vanilla_overlay_data_.push_back(0x60); + } + + // Set overlay ID based on map index (simplified) + vanilla_overlay_id_ = index_; + has_vanilla_overlay_ = !vanilla_overlay_data_.empty(); + + return absl::OkStatus(); +} + void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset, int size, uint8_t *all_gfx) { // Ensure we don't go out of bounds diff --git a/src/app/zelda3/overworld/overworld_map.h b/src/app/zelda3/overworld/overworld_map.h index 2cf7ac95..58ca2054 100644 --- a/src/app/zelda3/overworld/overworld_map.h +++ b/src/app/zelda3/overworld/overworld_map.h @@ -36,6 +36,13 @@ constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148; // 1 byte, not 0 i constexpr int OverworldCustomMosaicArray = 0x140200; // 1 byte for each overworld area (0xA0) constexpr int OverworldCustomMosaicEnabled = 0x140142; // 1 byte, not 0 if enabled +// Vanilla overlay constants +constexpr int kOverlayPointers = 0x77664; // 2 bytes for each overworld area (0x100) +constexpr int kOverlayPointersBank = 0x0E; // Bank for overlay pointers +constexpr int kOverlayData1 = 0x77676; // Check for custom overlay code +constexpr int kOverlayData2 = 0x77677; // Custom overlay data pointer +constexpr int kOverlayCodeStart = 0x77657; // Start of overlay code + // 1 byte for each overworld area (0xA0) constexpr int OverworldCustomMainPaletteArray = 0x140160; // 1 byte, not 0 if enabled @@ -98,6 +105,7 @@ class OverworldMap : public gfx::GfxContext { void LoadAreaGraphics(); absl::Status LoadPalette(); + absl::Status LoadVanillaOverlay(); absl::Status BuildTileset(); absl::Status BuildTiles16Gfx(std::vector& tiles16, int count); absl::Status BuildBitmap(OverworldBlockset& world_blockset); @@ -139,6 +147,15 @@ class OverworldMap : public gfx::GfxContext { void set_animated_gfx(uint8_t gfx) { animated_gfx_ = gfx; } auto custom_tileset(int index) const { return custom_gfx_ids_[index]; } + + // Vanilla overlay accessors + auto vanilla_overlay_id() const { return vanilla_overlay_id_; } + auto has_vanilla_overlay() const { return has_vanilla_overlay_; } + const auto& vanilla_overlay_data() const { return vanilla_overlay_data_; } + + // Mosaic expanded accessors + const std::array& mosaic_expanded() const { return mosaic_expanded_; } + void set_mosaic_expanded(int index, bool value) { mosaic_expanded_[index] = value; } void set_custom_tileset(int index, uint8_t value) { custom_gfx_ids_[index] = value; } auto mutable_current_graphics() { return ¤t_gfx_; } @@ -216,6 +233,9 @@ class OverworldMap : public gfx::GfxContext { static_graphics_.fill(0); mosaic_expanded_.fill(false); area_size_ = AreaSizeEnum::SmallArea; + vanilla_overlay_id_ = 0; + has_vanilla_overlay_ = false; + vanilla_overlay_data_.clear(); } private: @@ -267,6 +287,11 @@ class OverworldMap : public gfx::GfxContext { std::array mosaic_expanded_; + // Vanilla overlay support + uint16_t vanilla_overlay_id_ = 0; + bool has_vanilla_overlay_ = false; + std::vector vanilla_overlay_data_; + std::vector current_blockset_; std::vector current_gfx_; std::vector bitmap_data_;