diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 68c52dd6..273be4d4 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -141,7 +141,7 @@ void OverworldEditor::Initialize() { HOVER_HINT("Gfx Group Editor"); }); gui::AddTableColumn(toolset_table_, "##sep3", ICON_MD_MORE_VERT); - gui::AddTableColumn(toolset_table_, "##Properties", [&]() { + gui::AddTableColumn(toolset_table_, "##CopyMap", [&]() { if (Button(ICON_MD_CONTENT_COPY)) { std::vector png_data; png_data = maps_bmp_[current_map_].GetPngData(); @@ -161,6 +161,24 @@ void OverworldEditor::Initialize() { gui::AddTableColumn(toolset_table_, "##Properties", [&]() { Checkbox("Properties", &show_properties_editor_); }); + gui::AddTableColumn(toolset_table_, "##MapLock", [&]() { + if (Button(current_map_lock_ ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN)) { + current_map_lock_ = !current_map_lock_; + } + HOVER_HINT(current_map_lock_ ? "Unlock Map" : "Lock Map"); + }); + gui::AddTableColumn(toolset_table_, "##CustomBG", [&]() { + if (Button(ICON_MD_PALETTE)) { + show_custom_bg_color_editor_ = !show_custom_bg_color_editor_; + } + HOVER_HINT("Custom Background Colors"); + }); + gui::AddTableColumn(toolset_table_, "##Overlay", [&]() { + if (Button(ICON_MD_LAYERS)) { + show_overlay_editor_ = !show_overlay_editor_; + } + HOVER_HINT("Overlay Editor"); + }); } absl::Status OverworldEditor::Load() { @@ -220,6 +238,18 @@ void OverworldEditor::DrawToolset() { ImGui::End(); } + if (show_custom_bg_color_editor_) { + ImGui::Begin("Custom Background Colors", &show_custom_bg_color_editor_); + DrawCustomBackgroundColorEditor(); + ImGui::End(); + } + + if (show_overlay_editor_) { + ImGui::Begin("Overlay Editor", &show_overlay_editor_); + DrawOverlayEditor(); + ImGui::End(); + } + // TODO: Customizable shortcuts for the Overworld Editor if (!ImGui::IsAnyItemActive()) { using enum EditingMode; @@ -423,6 +453,40 @@ 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_) + ->mutable_main_palette(), + kInputFieldSize)) { + RefreshMapProperties(); + status_ = RefreshMapPalette(); + RefreshOverworldMap(); + } + + // Animated GFX + if (gui::InputHexByte("Animated GFX", + overworld_.mutable_overworld_map(current_map_) + ->mutable_animated_gfx(), + kInputFieldSize)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + + // Subscreen Overlay + if (gui::InputHexWord("Subscreen Overlay", + overworld_.mutable_overworld_map(current_map_) + ->mutable_subscreen_overlay(), + kInputFieldSize + 20)) { + RefreshMapProperties(); + RefreshOverworldMap(); + } + } ImGui::EndTable(); } @@ -783,6 +847,8 @@ void OverworldEditor::DrawOverworldCanvas() { ow_map_canvas_.DrawContextMenu(); } else { ow_map_canvas_.set_draggable(false); + // Always show our custom overworld context menu + DrawOverworldContextMenu(); } if (overworld_.is_loaded()) { @@ -1340,6 +1406,227 @@ absl::Status OverworldEditor::RefreshTile16Blockset() { return absl::OkStatus(); } +void OverworldEditor::DrawCustomBackgroundColorEditor() { + 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+"); + return; + } + + // Check if area-specific background colors are enabled + bool bg_enabled = (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] != 0x00; + if (Checkbox("Enable Area-Specific Background Colors", &bg_enabled)) { + (*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); + + // 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 + ); + + // Color picker + 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); + + // Write to ROM + (*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); + + // Refresh the map + RefreshOverworldMap(); + } + + Separator(); + + // Show color preview + Text("Color Preview:"); + 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) { + Text("Overlay editor is only available in ZSCustomOverworld v1+"); + return; + } + + // Check if subscreen overlays are enabled + bool overlay_enabled = (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] != 0x00; + if (Checkbox("Enable Subscreen Overlays", &overlay_enabled)) { + (*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); + + // 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; + + // Update the overworld map + 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 == 0x0095) { + Text("Sky Background (Death Mountain)"); + } else if (current_overlay == 0x009C) { + Text("Lava (Dark World 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"); + } else { + Text("Custom overlay"); + } +} + +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; + } + } else { + Text("Map: %d (0x%02X) - Click to lock", current_map_, current_map_); + if (Button("Lock Map")) { + current_map_lock_ = true; + } + } +} + +void OverworldEditor::DrawOverworldContextMenu() { + // Get the current map from mouse position + auto mouse_position = ow_map_canvas_.drawn_tile_position(); + int map_x = mouse_position.x / kOverworldMapSize; + int map_y = mouse_position.y / kOverworldMapSize; + int hovered_map = map_x + map_y * 8; + if (current_world_ == 1) { + hovered_map += 0x40; + } 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)); + Text("Currently Locked"); + PopStyleColor(); + if (MenuItem("Unlock Map")) { + current_map_lock_ = false; + } + } else { + if (MenuItem("Lock to This Map")) { + current_map_lock_ = true; + 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]; + 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; + } + } + + 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(); + } + } +} + void OverworldEditor::DrawOverworldProperties() { static bool init_properties = false; diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 12b931f0..e4dc3362 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -158,6 +158,10 @@ class OverworldEditor : public Editor, public gfx::GfxContext { absl::Status LoadSpriteGraphics(); void DrawOverworldProperties(); + void DrawCustomBackgroundColorEditor(); + void DrawOverlayEditor(); + void DrawMapLockControls(); + void DrawOverworldContextMenu(); absl::Status UpdateUsageStats(); void DrawUsageGrid(); @@ -215,6 +219,9 @@ class OverworldEditor : public Editor, public gfx::GfxContext { bool middle_mouse_dragging_ = false; bool is_dragging_entity_ = false; bool current_map_lock_ = false; + bool show_custom_bg_color_editor_ = false; + bool show_overlay_editor_ = false; + bool use_area_specific_bg_color_ = false; gfx::Tilemap tile16_blockset_; @@ -258,7 +265,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { gui::CanvasGridSize::k16x16}; gui::Canvas properties_canvas_; - gui::Table toolset_table_{"##ToolsetTable0", 22, kToolsetTableFlags}; + gui::Table toolset_table_{"##ToolsetTable0", 25, kToolsetTableFlags}; gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags, ImVec2(0, 0)}; diff --git a/src/app/gui/canvas.h b/src/app/gui/canvas.h index 24d3ec70..c2ff0d49 100644 --- a/src/app/gui/canvas.h +++ b/src/app/gui/canvas.h @@ -165,6 +165,7 @@ class Canvas { auto draw_list() const { return draw_list_; } auto zero_point() const { return canvas_p0_; } auto scrolling() const { return scrolling_; } + void set_scrolling(ImVec2 scroll) { scrolling_ = scroll; } auto drawn_tile_position() const { return drawn_tile_pos_; } auto canvas_size() const { return canvas_sz_; } void set_global_scale(float scale) { global_scale_ = scale; } diff --git a/src/app/zelda3/overworld/overworld_map.cc b/src/app/zelda3/overworld/overworld_map.cc index f62833d2..34b47472 100644 --- a/src/app/zelda3/overworld/overworld_map.cc +++ b/src/app/zelda3/overworld/overworld_map.cc @@ -826,6 +826,16 @@ absl::Status OverworldMap::LoadPalette() { void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset, int size, uint8_t *all_gfx) { + // Ensure we don't go out of bounds + int max_offset = static_graphics_offset * size + size; + if (max_offset > rom_->graphics_buffer().size()) { + // Fill with zeros if out of bounds + for (int i = 0; i < size; i++) { + current_gfx_[(index * size) + i] = 0x00; + } + return; + } + for (int i = 0; i < size; i++) { auto byte = all_gfx[i + (static_graphics_offset * size)]; switch (index) { @@ -842,10 +852,29 @@ void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset, absl::Status OverworldMap::BuildTileset() { if (current_gfx_.size() == 0) current_gfx_.resize(0x10000, 0x00); - for (int i = 0; i < 0x10; i++) { - ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000, + + // Process the 8 main graphics sheets (slots 0-7) + for (int i = 0; i < 8; i++) { + if (static_graphics_[i] != 0) { + ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000, + rom_->graphics_buffer().data()); + } + } + + // Process sprite graphics (slots 8-15) + for (int i = 8; i < 16; i++) { + if (static_graphics_[i] != 0) { + ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000, + rom_->graphics_buffer().data()); + } + } + + // Process animated graphics if available (slot 16) + if (static_graphics_[16] != 0) { + ProcessGraphicsBuffer(7, static_graphics_[16], 0x1000, rom_->graphics_buffer().data()); } + return absl::OkStatus(); } diff --git a/src/app/zelda3/overworld/overworld_map.h b/src/app/zelda3/overworld/overworld_map.h index 2b2468aa..dd059773 100644 --- a/src/app/zelda3/overworld/overworld_map.h +++ b/src/app/zelda3/overworld/overworld_map.h @@ -26,34 +26,21 @@ constexpr int OverworldCustomAreaSpecificBGPalette = 0x140000; // 1 byte, not 0 if enabled constexpr int OverworldCustomAreaSpecificBGEnabled = 0x140140; +// Additional v3 constants +constexpr int OverworldCustomSubscreenOverlayArray = 0x140340; // 2 bytes for each overworld area (0x140) +constexpr int OverworldCustomSubscreenOverlayEnabled = 0x140144; // 1 byte, not 0 if enabled +constexpr int OverworldCustomAnimatedGFXArray = 0x1402A0; // 1 byte for each overworld area (0xA0) +constexpr int OverworldCustomAnimatedGFXEnabled = 0x140143; // 1 byte, not 0 if enabled +constexpr int OverworldCustomTileGFXGroupArray = 0x140480; // 8 bytes for each overworld area (0x500) +constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148; // 1 byte, not 0 if enabled +constexpr int OverworldCustomMosaicArray = 0x140200; // 1 byte for each overworld area (0xA0) +constexpr int OverworldCustomMosaicEnabled = 0x140142; // 1 byte, not 0 if enabled + // 1 byte for each overworld area (0xA0) constexpr int OverworldCustomMainPaletteArray = 0x140160; // 1 byte, not 0 if enabled constexpr int OverworldCustomMainPaletteEnabled = 0x140141; -// 1 byte for each overworld area (0xA0) -constexpr int OverworldCustomMosaicArray = 0x140200; - -// 1 byte, not 0 if enabled -constexpr int OverworldCustomMosaicEnabled = 0x140142; - -// 1 byte for each overworld area (0xA0) -constexpr int OverworldCustomAnimatedGFXArray = 0x1402A0; - -// 1 byte, not 0 if enabled -constexpr int OverworldCustomAnimatedGFXEnabled = 0x140143; - -// 2 bytes for each overworld area (0x140) -constexpr int OverworldCustomSubscreenOverlayArray = 0x140340; - -// 1 byte, not 0 if enabled -constexpr int OverworldCustomSubscreenOverlayEnabled = 0x140144; - -// 8 bytes for each overworld area (0x500) -constexpr int OverworldCustomTileGFXGroupArray = 0x140480; - -// 1 byte, not 0 if enabled -constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148; // v3 expanded constants constexpr int kOverworldMessagesExpanded = 0x1417F8; @@ -145,12 +132,24 @@ class OverworldMap : public gfx::GfxContext { area_specific_bg_color_ = color; } + auto subscreen_overlay() const { return subscreen_overlay_; } + void set_subscreen_overlay(uint16_t overlay) { subscreen_overlay_ = overlay; } + + auto animated_gfx() const { return animated_gfx_; } + void set_animated_gfx(uint8_t gfx) { animated_gfx_ = gfx; } + + auto custom_tileset(int index) const { return custom_gfx_ids_[index]; } + void set_custom_tileset(int index, uint8_t value) { custom_gfx_ids_[index] = value; } + auto mutable_current_graphics() { return ¤t_gfx_; } auto mutable_area_graphics() { return &area_graphics_; } auto mutable_area_palette() { return &area_palette_; } auto mutable_sprite_graphics(int i) { return &sprite_graphics_[i]; } auto mutable_sprite_palette(int i) { return &sprite_palette_[i]; } auto mutable_message_id() { return &message_id_; } + auto mutable_main_palette() { return &main_palette_; } + auto mutable_animated_gfx() { return &animated_gfx_; } + auto mutable_subscreen_overlay() { return &subscreen_overlay_; } auto mutable_area_music(int i) { return &area_music_[i]; } auto mutable_static_graphics(int i) { return &static_graphics_[i]; }