diff --git a/.clangd b/.clangd index 9dc27669..fc6d3291 100644 --- a/.clangd +++ b/.clangd @@ -28,3 +28,8 @@ Diagnostics: Remove: - modernize-use-trailing-return-type - readability-braces-around-statements + - readability-magic-numbers + - readability-implicit-bool-conversion + - readability-identifier-naming + - readability-function-cognitive-complexity + - readability-function-size diff --git a/assets/layouts/overworld.zeml b/assets/layouts/overworld.zeml deleted file mode 100644 index 3e8af78a..00000000 --- a/assets/layouts/overworld.zeml +++ /dev/null @@ -1,25 +0,0 @@ -BeginTabBar title="##OwEditorTabBar" { - BeginTabItem title="Map Editor" { - Function id="owToolset", - - Table id="##owEditTable" count="2" flags="Resizable|Reorderable|Hideable|BordersOuter|BordersV" { - TableSetupColumn title="Canvas" flags="WidthStretch", - TableSetupColumn title="Tile Selector" flags="WidthFixed" width="256", - TableHeadersRow - TableNextRow, - TableNextColumn, - Function id="OverworldCanvas", - TableNextColumn, - Function id="OverworldTileSelector", - } - } - BeginTabItem title="Tile16 Editor" { - Function id="OwTile16Editor" - } - BeginTabItem title "Graphics Group Editor" { - Function id="OwGfxGroupEditor" - } - BeginTabItem title="Usage Statistics" { - Function id="OwUsageStats" - } -} \ No newline at end of file diff --git a/cmake/packaging.cmake b/cmake/packaging.cmake index 89f041f6..fbe62e0c 100644 --- a/cmake/packaging.cmake +++ b/cmake/packaging.cmake @@ -162,7 +162,6 @@ install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets/ PATTERN "*.png" PATTERN "*.ttf" PATTERN "*.asm" - PATTERN "*.zeml" ) # Install documentation diff --git a/docs/C1-changelog.md b/docs/C1-changelog.md index 80d48567..41978b00 100644 --- a/docs/C1-changelog.md +++ b/docs/C1-changelog.md @@ -2,7 +2,40 @@ ## 0.3.1 -- Minor bug fixes for color themes, ZSCustomOverworld v3 item loading, and Tile16 selection. +### Major Features +- **Complete Tile16 Editor Overhaul**: Professional-grade tile editing with modern UI and advanced capabilities +- **Advanced Palette Management**: Full access to all SNES palette groups with configurable normalization +- **Comprehensive Undo/Redo System**: 50-state history with intelligent time-based throttling +- **Scratch Space for Overworld**: Not yet compatible with ZScream + +### Tile16 Editor Enhancements +- **Modern UI Layout**: Fully resizable 3-column interface (Tile8 Source, Editor, Preview & Controls) +- **Multi-Palette Group Support**: Access to Overworld Main/Aux1/Aux2, Dungeon Main, Global Sprites, Armors, and Swords palettes +- **Advanced Transform Operations**: Flip horizontal/vertical, rotate 90°, fill with tile8, clear operations +- **Professional Workflow**: Copy/paste, 4-slot scratch space, live preview with auto-commit +- **Pixel Normalization Settings**: Configurable pixel value masks (0x01-0xFF) for handling corrupted graphics sheets + +### Technical Improvements +- **SNES Data Accuracy**: Proper 4-bit palette index handling with configurable normalization +- **Bitmap Pipeline Fixes**: Corrected tile16 extraction using `GetTilemapData()` with manual fallback +- **Real-time Updates**: Immediate visual feedback for all editing operations +- **Memory Safety**: Enhanced bounds checking and error handling throughout + +### User Interface +- **Keyboard Shortcuts**: Comprehensive shortcuts for all operations (H/V/R for transforms, Q/E for palette cycling, 1-8 for direct palette selection) +- **Visual Feedback**: Hover preview restoration, current palette highlighting, texture status indicators +- **Compact Controls**: Streamlined property panel with essential tools easily accessible +- **Settings Dialog**: Advanced palette normalization controls with real-time application + +### Bug Fixes +- **Tile16 Bitmap Display**: Fixed blank/white tile issue caused by unnormalized pixel values +- **Hover Preview**: Restored tile8 preview when hovering over tile16 canvas +- **Canvas Scaling**: Corrected coordinate scaling for 8x magnification factor +- **Palette Corruption**: Fixed high-bit contamination in graphics sheets +- **UI Layout**: Proper column sizing and resizing behavior +- **Linux CI/CD Build**: Fixed undefined reference errors for `ShowSaveFileDialog` method + +- Minor bug fixes for color themes and ZSCustomOverworld v3 item loading. ## 0.3.0 (September 2025) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 60e852cf..5152e1c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,7 +22,6 @@ set( ) set(YAZE_RESOURCE_FILES - ${CMAKE_SOURCE_DIR}/assets/layouts/overworld.zeml ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf ${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf ${CMAKE_SOURCE_DIR}/assets/font/Cousine-Regular.ttf diff --git a/src/app/editor/editor.cmake b/src/app/editor/editor.cmake index 89435b12..3f960123 100644 --- a/src/app/editor/editor.cmake +++ b/src/app/editor/editor.cmake @@ -11,6 +11,7 @@ set( app/editor/dungeon/dungeon_room_loader.cc app/editor/dungeon/dungeon_usage_tracker.cc app/editor/overworld/overworld_editor.cc + app/editor/overworld/overworld_editor_manager.cc app/editor/sprite/sprite_editor.cc app/editor/music/music_editor.cc app/editor/message/message_editor.cc diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 3f41c262..a5c4fbeb 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -23,7 +23,6 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "app/gui/style.h" -#include "app/gui/zeml.h" #include "app/rom.h" #include "app/zelda3/common.h" #include "app/zelda3/overworld/overworld.h" @@ -42,40 +41,15 @@ using namespace ImGui; 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(); }); + // Initialize OverworldEditorManager for v3 features + overworld_manager_ = std::make_unique(&overworld_, rom_); // Setup overworld canvas context menu SetupOverworldCanvasContextMenu(); - gui::zeml::Bind( - std::to_address(layout_node_.GetNode("OverworldTileSelector")), - [this]() { status_ = DrawTileSelector(); }); - gui::zeml::Bind(std::to_address(layout_node_.GetNode("OwUsageStats")), - [this]() { - if (rom_->is_loaded()) { - status_ = UpdateUsageStats(); - } - }); - gui::zeml::Bind(std::to_address(layout_node_.GetNode("owToolset")), - [this]() { DrawToolset(); }); - gui::zeml::Bind(std::to_address(layout_node_.GetNode("OwTile16Editor")), - [this]() { - if (rom_->is_loaded()) { - status_ = tile16_editor_.Update(); - } - }); - gui::zeml::Bind(std::to_address(layout_node_.GetNode("OwGfxGroupEditor")), - [this]() { - if (rom_->is_loaded()) { - status_ = gfx_group_editor_.Update(); - } - }); // Core editing tools gui::AddTableColumn(toolset_table_, "##Pan", [&]() { @@ -179,8 +153,65 @@ absl::Status OverworldEditor::Load() { absl::Status OverworldEditor::Update() { status_ = absl::OkStatus(); - if (overworld_canvas_fullscreen_) DrawFullscreenCanvas(); - gui::zeml::Render(layout_node_); + if (overworld_canvas_fullscreen_) { + DrawFullscreenCanvas(); + return status_; + } + + // Replace ZEML with pure ImGui layout + if (ImGui::BeginTabBar("##OwEditorTabBar")) { + if (ImGui::BeginTabItem("Map Editor")) { + DrawToolset(); + + if (ImGui::BeginTable("##owEditTable", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | + ImGuiTableFlags_Hideable | ImGuiTableFlags_BordersOuter | + ImGuiTableFlags_BordersV)) { + ImGui::TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Tile Selector", ImGuiTableColumnFlags_WidthFixed, 256.0f); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + DrawOverworldCanvas(); + + ImGui::TableNextColumn(); + status_ = DrawTileSelector(); + + ImGui::EndTable(); + } + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Tile16 Editor")) { + if (rom_->is_loaded()) { + status_ = tile16_editor_.Update(); + } + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Graphics Group Editor")) { + if (rom_->is_loaded()) { + status_ = gfx_group_editor_.Update(); + } + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Usage Statistics")) { + if (rom_->is_loaded()) { + status_ = UpdateUsageStats(); + } + ImGui::EndTabItem(); + } + + // Add v3 settings tab + if (rom_->is_loaded()) { + status_ = overworld_manager_->DrawV3SettingsPanel(); + } + + ImGui::EndTabBar(); + } + return status_; } @@ -1249,9 +1280,9 @@ void OverworldEditor::DrawOverworldEntrances(ImVec2 canvas_p0, ImVec2 scrolling, for (auto &each : overworld_.entrances()) { if (each.map_id_ < 0x40 + (current_world_ * 0x40) && each.map_id_ >= (current_world_ * 0x40) && !each.deleted) { - auto color = ImVec4(255, 255, 0, 100); + auto color = ImVec4(255, 0, 255, 100); if (each.is_hole_) { - color = ImVec4(255, 255, 255, 200); + color = ImVec4(255, 255, 0, 200); } ow_map_canvas_.DrawRect(each.x_, each.y_, 16, 16, color); std::string str = util::HexByte(each.entrance_id_); diff --git a/src/app/editor/overworld/overworld_editor.h b/src/app/editor/overworld/overworld_editor.h index 19649c53..adf55bf6 100644 --- a/src/app/editor/overworld/overworld_editor.h +++ b/src/app/editor/overworld/overworld_editor.h @@ -12,9 +12,9 @@ #include "app/gfx/tilemap.h" #include "app/gui/canvas.h" #include "app/gui/input.h" -#include "app/gui/zeml.h" #include "app/rom.h" #include "app/zelda3/overworld/overworld.h" +#include "app/editor/overworld/overworld_editor_manager.h" #include "imgui/imgui.h" namespace yaze { @@ -263,6 +263,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext { // Map properties system for UI organization std::unique_ptr map_properties_system_; + std::unique_ptr overworld_manager_; // Scratch space for large layouts // Scratch space canvas for tile16 drawing (like a mini overworld) @@ -328,7 +329,6 @@ class OverworldEditor : public Editor, public gfx::GfxContext { gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags, ImVec2(0, 0)}; - gui::zeml::Node layout_node_; absl::Status status_; }; } // namespace editor diff --git a/src/app/editor/overworld/overworld_editor_manager.cc b/src/app/editor/overworld/overworld_editor_manager.cc new file mode 100644 index 00000000..2f0726db --- /dev/null +++ b/src/app/editor/overworld/overworld_editor_manager.cc @@ -0,0 +1,284 @@ +#include "overworld_editor_manager.h" + +#include "app/gfx/snes_color.h" +#include "app/gui/icons.h" +#include "app/gui/input.h" +#include "app/gui/style.h" +#include "app/zelda3/overworld/overworld_map.h" + +namespace yaze { +namespace editor { + +using namespace ImGui; + +absl::Status OverworldEditorManager::DrawV3SettingsPanel() { + if (BeginTabItem("v3 Settings")) { + Text("ZSCustomOverworld v3 Settings"); + Separator(); + + // Check if custom ASM is applied + uint8_t asm_version = GetCustomASMVersion(); + if (asm_version >= 3 && asm_version != 0xFF) { + TextColored(ImVec4(0, 1, 0, 1), "Custom Overworld ASM v%d Applied", asm_version); + } else if (asm_version == 0x00) { + TextColored(ImVec4(1, 1, 0, 1), "Vanilla ROM - Custom features available via flag"); + } else { + TextColored(ImVec4(1, 0, 0, 1), "Custom ASM v%d - Consider upgrading to v3", asm_version); + } + + Separator(); + + RETURN_IF_ERROR(DrawCustomOverworldSettings()); + RETURN_IF_ERROR(DrawAreaSpecificSettings()); + RETURN_IF_ERROR(DrawTransitionSettings()); + RETURN_IF_ERROR(DrawOverlaySettings()); + + EndTabItem(); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawCustomOverworldSettings() { + if (TreeNode("Custom Overworld Features")) { + RETURN_IF_ERROR(DrawBooleanSetting("Enable Area-Specific Background Colors", + &enable_area_specific_bg_, + "Allows each overworld area to have its own background color")); + + RETURN_IF_ERROR(DrawBooleanSetting("Enable Main Palette Override", + &enable_main_palette_, + "Allows each area to override the main palette")); + + RETURN_IF_ERROR(DrawBooleanSetting("Enable Mosaic Transitions", + &enable_mosaic_, + "Enables mosaic screen transitions between areas")); + + RETURN_IF_ERROR(DrawBooleanSetting("Enable Custom GFX Groups", + &enable_gfx_groups_, + "Allows each area to have custom tile GFX groups")); + + RETURN_IF_ERROR(DrawBooleanSetting("Enable Subscreen Overlays", + &enable_subscreen_overlay_, + "Enables custom subscreen overlays (fog, sky, etc.)")); + + RETURN_IF_ERROR(DrawBooleanSetting("Enable Animated GFX Override", + &enable_animated_gfx_, + "Allows each area to have custom animated tiles")); + + Separator(); + + if (Button("Apply Custom Overworld ASM")) { + RETURN_IF_ERROR(ApplyCustomOverworldASM()); + } + SameLine(); + HOVER_HINT("Writes the custom overworld settings to ROM"); + + TreePop(); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawAreaSpecificSettings() { + if (TreeNode("Area-Specific Settings")) { + // Map selection + int map_count = zelda3::kNumOverworldMaps; + SliderInt("Map Index", ¤t_map_index_, 0, map_count - 1); + + auto* current_map = overworld_->mutable_overworld_map(current_map_index_); + + // Area size controls + RETURN_IF_ERROR(DrawAreaSizeControls()); + + // Background color + if (enable_area_specific_bg_) { + uint16_t bg_color = current_map->area_specific_bg_color(); + RETURN_IF_ERROR(DrawColorPicker("Background Color", &bg_color)); + current_map->set_area_specific_bg_color(bg_color); + } + + // Main palette + if (enable_main_palette_) { + uint8_t main_palette = current_map->main_palette(); + SliderInt("Main Palette", (int*)&main_palette, 0, 5); + current_map->set_main_palette(main_palette); + } + + // Mosaic settings + if (enable_mosaic_) { + RETURN_IF_ERROR(DrawMosaicControls()); + } + + // GFX groups + if (enable_gfx_groups_) { + RETURN_IF_ERROR(DrawGfxGroupControls()); + } + + // Subscreen overlay + if (enable_subscreen_overlay_) { + uint16_t overlay = current_map->subscreen_overlay(); + RETURN_IF_ERROR(DrawOverlaySetting("Subscreen Overlay", &overlay)); + current_map->set_subscreen_overlay(overlay); + } + + // Animated GFX + if (enable_animated_gfx_) { + uint8_t animated_gfx = current_map->animated_gfx(); + RETURN_IF_ERROR(DrawGfxGroupSetting("Animated GFX", &animated_gfx)); + current_map->set_animated_gfx(animated_gfx); + } + + TreePop(); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawAreaSizeControls() { + auto* current_map = overworld_->mutable_overworld_map(current_map_index_); + + const char* area_size_names[] = {"Small", "Large", "Wide", "Tall"}; + int current_size = static_cast(current_map->area_size()); + + if (Combo("Area Size", ¤t_size, area_size_names, 4)) { + current_map->SetAreaSize(static_cast(current_size)); + } + + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawMosaicControls() { + auto* current_map = overworld_->mutable_overworld_map(current_map_index_); + const auto& mosaic = current_map->mosaic_expanded(); + + bool mosaic_up = mosaic[0]; + bool mosaic_down = mosaic[1]; + bool mosaic_left = mosaic[2]; + bool mosaic_right = mosaic[3]; + + if (Checkbox("Mosaic Up", &mosaic_up)) { + current_map->set_mosaic_expanded(0, mosaic_up); + } + SameLine(); + if (Checkbox("Mosaic Down", &mosaic_down)) { + current_map->set_mosaic_expanded(1, mosaic_down); + } + if (Checkbox("Mosaic Left", &mosaic_left)) { + current_map->set_mosaic_expanded(2, mosaic_left); + } + SameLine(); + if (Checkbox("Mosaic Right", &mosaic_right)) { + current_map->set_mosaic_expanded(3, mosaic_right); + } + + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawGfxGroupControls() { + auto* current_map = overworld_->mutable_overworld_map(current_map_index_); + + Text("Custom Tile GFX Groups:"); + for (int i = 0; i < 8; i++) { + uint8_t gfx_id = current_map->custom_tileset(i); + std::string label = "GFX " + std::to_string(i); + RETURN_IF_ERROR(DrawGfxGroupSetting(label.c_str(), &gfx_id)); + current_map->set_custom_tileset(i, gfx_id); + if (i < 7) SameLine(); + } + + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawTransitionSettings() { + if (TreeNode("Transition Settings")) { + Text("Complex area transition calculations are automatically handled"); + Text("based on neighboring area sizes (Large, Wide, Tall, Small)."); + + if (GetCustomASMVersion() >= 3) { + TextColored(ImVec4(0, 1, 0, 1), "Using v3+ enhanced transitions"); + } else { + TextColored(ImVec4(1, 1, 0, 1), "Using vanilla/v2 transitions"); + } + + TreePop(); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawOverlaySettings() { + if (TreeNode("Interactive Overlay Settings")) { + Text("Interactive overlays reveal holes and change map elements."); + + auto* current_map = overworld_->mutable_overworld_map(current_map_index_); + + Text("Map %d has %s", current_map_index_, + current_map->has_overlay() ? "interactive overlay" : "no overlay"); + + if (current_map->has_overlay()) { + Text("Overlay ID: 0x%04X", current_map->overlay_id()); + Text("Overlay data size: %zu bytes", current_map->overlay_data().size()); + } + + TreePop(); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::ApplyCustomOverworldASM() { + return overworld_->SaveCustomOverworldASM( + enable_area_specific_bg_, enable_main_palette_, enable_mosaic_, + enable_gfx_groups_, enable_subscreen_overlay_, enable_animated_gfx_); +} + +bool OverworldEditorManager::ValidateV3Compatibility() { + uint8_t asm_version = GetCustomASMVersion(); + return (asm_version >= 3 && asm_version != 0xFF); +} + +bool OverworldEditorManager::CheckCustomASMApplied() { + uint8_t asm_version = GetCustomASMVersion(); + return (asm_version != 0xFF && asm_version != 0x00); +} + +uint8_t OverworldEditorManager::GetCustomASMVersion() { + return (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; +} + +absl::Status OverworldEditorManager::DrawBooleanSetting(const char* label, bool* setting, + const char* help_text) { + Checkbox(label, setting); + if (help_text && IsItemHovered()) { + SetTooltip("%s", help_text); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawColorPicker(const char* label, uint16_t* color) { + gfx::SnesColor snes_color(*color); + ImVec4 imgui_color = snes_color.rgb(); + + if (ColorEdit3(label, &imgui_color.x)) { + gfx::SnesColor new_color; + new_color.set_rgb(imgui_color); + *color = new_color.snes(); + } + + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawOverlaySetting(const char* label, uint16_t* overlay) { + int overlay_int = *overlay; + if (InputInt(label, &overlay_int, 1, 16, ImGuiInputTextFlags_CharsHexadecimal)) { + *overlay = static_cast(overlay_int & 0xFFFF); + } + return absl::OkStatus(); +} + +absl::Status OverworldEditorManager::DrawGfxGroupSetting(const char* label, uint8_t* gfx_id, + int max_value) { + int gfx_int = *gfx_id; + if (SliderInt(label, &gfx_int, 0, max_value)) { + *gfx_id = static_cast(gfx_int); + } + return absl::OkStatus(); +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/overworld/overworld_editor_manager.h b/src/app/editor/overworld/overworld_editor_manager.h new file mode 100644 index 00000000..31f5dd1f --- /dev/null +++ b/src/app/editor/overworld/overworld_editor_manager.h @@ -0,0 +1,90 @@ +#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H +#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H + +#include + +#include "absl/status/status.h" +#include "app/rom.h" +#include "app/zelda3/overworld/overworld.h" + +namespace yaze { +namespace editor { + +/** + * @class OverworldEditorManager + * @brief Manages the complex overworld editor functionality and v3 features + * + * This class separates the complex overworld editing functionality from the main + * OverworldEditor class to improve maintainability and organization, especially + * for ZSCustomOverworld v3 features. + */ +class OverworldEditorManager { + public: + OverworldEditorManager(zelda3::Overworld* overworld, Rom* rom) + : overworld_(overworld), rom_(rom) {} + + // v3 Feature Management + absl::Status DrawV3SettingsPanel(); + absl::Status DrawCustomOverworldSettings(); + absl::Status DrawAreaSpecificSettings(); + absl::Status DrawTransitionSettings(); + absl::Status DrawOverlaySettings(); + + // Map Properties Management + absl::Status DrawMapPropertiesTable(); + absl::Status DrawAreaSizeControls(); + absl::Status DrawMosaicControls(); + absl::Status DrawPaletteControls(); + absl::Status DrawGfxGroupControls(); + + // Save/Load Operations for v3 + absl::Status SaveCustomOverworldData(); + absl::Status LoadCustomOverworldData(); + absl::Status ApplyCustomOverworldASM(); + + // Validation and Checks + bool ValidateV3Compatibility(); + bool CheckCustomASMApplied(); + uint8_t GetCustomASMVersion(); + + // Getters/Setters for v3 settings + auto enable_area_specific_bg() const { return enable_area_specific_bg_; } + auto enable_main_palette() const { return enable_main_palette_; } + auto enable_mosaic() const { return enable_mosaic_; } + auto enable_gfx_groups() const { return enable_gfx_groups_; } + auto enable_subscreen_overlay() const { return enable_subscreen_overlay_; } + auto enable_animated_gfx() const { return enable_animated_gfx_; } + + void set_enable_area_specific_bg(bool value) { enable_area_specific_bg_ = value; } + void set_enable_main_palette(bool value) { enable_main_palette_ = value; } + void set_enable_mosaic(bool value) { enable_mosaic_ = value; } + void set_enable_gfx_groups(bool value) { enable_gfx_groups_ = value; } + void set_enable_subscreen_overlay(bool value) { enable_subscreen_overlay_ = value; } + void set_enable_animated_gfx(bool value) { enable_animated_gfx_ = value; } + + private: + zelda3::Overworld* overworld_; + Rom* rom_; + + // v3 Feature flags + bool enable_area_specific_bg_ = false; + bool enable_main_palette_ = false; + bool enable_mosaic_ = false; + bool enable_gfx_groups_ = false; + bool enable_subscreen_overlay_ = false; + bool enable_animated_gfx_ = false; + + // Current editing state + int current_map_index_ = 0; + + // Helper methods + absl::Status DrawBooleanSetting(const char* label, bool* setting, const char* help_text = nullptr); + absl::Status DrawColorPicker(const char* label, uint16_t* color); + absl::Status DrawOverlaySetting(const char* label, uint16_t* overlay); + absl::Status DrawGfxGroupSetting(const char* label, uint8_t* gfx_id, int max_value = 255); +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H diff --git a/src/app/editor/overworld/tile16_editor.cc b/src/app/editor/overworld/tile16_editor.cc index 2e6f65e3..cc11dd3c 100644 --- a/src/app/editor/overworld/tile16_editor.cc +++ b/src/app/editor/overworld/tile16_editor.cc @@ -471,14 +471,12 @@ absl::Status Tile16Editor::DrawToCurrentTile16(ImVec2 click_position) { absl::Status Tile16Editor::UpdateTile16Edit() { static bool show_tile8_selector = true; static bool show_tile16_editor = true; - static bool show_controls = true; // View controls if (BeginMenuBar()) { if (BeginMenu("View")) { Checkbox("Tile8 Selector", &show_tile8_selector); Checkbox("Tile16 Editor", &show_tile16_editor); - Checkbox("Controls", &show_controls); EndMenu(); } EndMenuBar(); @@ -552,6 +550,32 @@ absl::Status Tile16Editor::UpdateTile16Edit() { tile8_source_canvas_.DrawOverlay(); EndChild(); + + Separator(); + Text("Palette: %d (Group: %d)", current_palette_, current_palette_group_); + if (Button("Pal -", ImVec2(40, 0))) + RETURN_IF_ERROR(CyclePalette(false)); + SameLine(); + if (Button("Pal +", ImVec2(40, 0))) + RETURN_IF_ERROR(CyclePalette(true)); + + // Quick palette grid + for (int i = 0; i < 8; ++i) { + if (i > 0 && i % 4 != 0) + SameLine(); + bool is_current = (current_palette_ == i); + if (is_current) + PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f)); + if (Button(std::to_string(i).c_str(), ImVec2(20, 20))) { + current_palette_ = i; + RETURN_IF_ERROR(RefreshAllPalettes()); + } + if (is_current) + PopStyleColor(); + if (i == 3) { /* New line */ + } + } + ImGui::EndGroup(); } @@ -674,132 +698,98 @@ absl::Status Tile16Editor::UpdateTile16Edit() { } } } - - // Compact controls section directly below - if (show_controls) { - if (BeginTable("##Tile16CompactControls", 3, - ImGuiTableFlags_Resizable | - ImGuiTableFlags_BordersInnerV)) { - TableSetupColumn("Info & Palette", ImGuiTableColumnFlags_WidthFixed, - 280); - TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 200); - TableSetupColumn("Advanced", ImGuiTableColumnFlags_WidthStretch); - - TableNextRow(); - - // Info and palette column - TableNextColumn(); - if (BeginChild("InfoPaletteChild", ImVec2(270, 120), true)) { - gui::DrawTable(tile_edit_table_); - - Separator(); - Text("Palette: %d (Group: %d)", current_palette_, - current_palette_group_); - if (Button("Pal -", ImVec2(35, 0))) - RETURN_IF_ERROR(CyclePalette(false)); - SameLine(); - if (Button("Pal +", ImVec2(35, 0))) - RETURN_IF_ERROR(CyclePalette(true)); - - // Quick palette grid - for (int i = 0; i < 8; ++i) { - if (i > 0 && i % 4 != 0) - SameLine(); - bool is_current = (current_palette_ == i); - if (is_current) - PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.7f, 0.4f, 1.0f)); - if (Button(std::to_string(i).c_str(), ImVec2(16, 16))) { - current_palette_ = i; - RETURN_IF_ERROR(RefreshAllPalettes()); - } - if (is_current) - PopStyleColor(); - if (i == 3) { /* New line */ - } - } - } - EndChild(); - - // Actions column - TableNextColumn(); - if (BeginChild("ActionsChild", ImVec2(190, 120), true)) { - if (Button("Clear Tile16", ImVec2(80, 0))) - RETURN_IF_ERROR(ClearTile16()); - if (Button("Copy", ImVec2(80, 0))) - RETURN_IF_ERROR(CopyTile16ToClipboard(current_tile16_)); - if (Button("Paste", ImVec2(80, 0))) - RETURN_IF_ERROR(PasteTile16FromClipboard()); - - Separator(); - - bool can_undo = !undo_stack_.empty(); - bool can_redo = !redo_stack_.empty(); - - if (!can_undo) - BeginDisabled(); - if (Button("Undo", ImVec2(80, 0))) - RETURN_IF_ERROR(Undo()); - if (!can_undo) - EndDisabled(); - - if (!can_redo) - BeginDisabled(); - if (Button("Redo", ImVec2(80, 0))) - RETURN_IF_ERROR(Redo()); - if (!can_redo) - EndDisabled(); - - Separator(); - DrawScratchSpace(); - } - EndChild(); - - // Advanced settings column - TableNextColumn(); - if (BeginChild("AdvancedChild", ImVec2(0, 120), true)) { - if (Button("Palette Settings")) { - show_palette_settings_ = !show_palette_settings_; - } - - if (Button("Manual Tile8 Inputs")) { - ImGui::OpenPopup("ManualTile8Editor"); - } - HOVER_HINT("Edit tile8 IDs and properties directly"); - - if (Button("Refresh Blockset")) { - RETURN_IF_ERROR(RefreshTile16Blockset()); - } - HOVER_HINT("Regenerate tile16 blockset from ROM data"); - - Text("Advanced Palette:"); - const char* palette_group_names[] = { - "OW Main", "OW Aux", "OW Anim", "Dungeon", - "Sprites", "Armor", "Sword"}; - if (Combo("##AdvPaletteGroup", ¤t_palette_group_, - palette_group_names, 7)) { - RETURN_IF_ERROR(RefreshAllPalettes()); - } - - Text("Normalization:"); - int mask_value = static_cast(palette_normalization_mask_); - if (SliderInt("##NormMask", &mask_value, 1, 255, "0x%02X")) { - palette_normalization_mask_ = static_cast(mask_value); - RETURN_IF_ERROR(LoadTile8()); // Reload with new mask - } - - Checkbox("Auto Normalize", &auto_normalize_pixels_); - } - EndChild(); - - // Manual tile8 editor popup - DrawManualTile8Inputs(); - - EndTable(); - } - - // Close the tile16 editor scroll region - EndChild(); + if (BeginChild("InfoPaletteChild", ImVec2(270, 120), true)) { + gui::DrawTable(tile_edit_table_); } + EndChild(); + // Compact controls section directly below + if (BeginTable( + "##Tile16CompactControls", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { + TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 100); + TableSetupColumn("Advanced", ImGuiTableColumnFlags_WidthStretch); + + TableNextRow(); + + // Actions column + TableNextColumn(); + if (BeginChild("ActionsChild", ImVec2(190, 120), true)) { + if (Button("Clear Tile16", ImVec2(80, 0))) + RETURN_IF_ERROR(ClearTile16()); + if (Button("Copy", ImVec2(80, 0))) + RETURN_IF_ERROR(CopyTile16ToClipboard(current_tile16_)); + if (Button("Paste", ImVec2(80, 0))) + RETURN_IF_ERROR(PasteTile16FromClipboard()); + + Separator(); + + bool can_undo = !undo_stack_.empty(); + bool can_redo = !redo_stack_.empty(); + + if (!can_undo) + BeginDisabled(); + if (Button("Undo", ImVec2(80, 0))) + RETURN_IF_ERROR(Undo()); + if (!can_undo) + EndDisabled(); + + if (!can_redo) + BeginDisabled(); + if (Button("Redo", ImVec2(80, 0))) + RETURN_IF_ERROR(Redo()); + if (!can_redo) + EndDisabled(); + + Separator(); + DrawScratchSpace(); + } + EndChild(); + + // Advanced settings column + TableNextColumn(); + if (BeginChild("AdvancedChild", ImVec2(0, 120), true)) { + if (Button("Palette Settings")) { + show_palette_settings_ = !show_palette_settings_; + } + + if (Button("Manual Tile8 Inputs")) { + ImGui::OpenPopup("ManualTile8Editor"); + } + HOVER_HINT("Edit tile8 IDs and properties directly"); + + if (Button("Refresh Blockset")) { + RETURN_IF_ERROR(RefreshTile16Blockset()); + } + HOVER_HINT("Regenerate tile16 blockset from ROM data"); + + Text("Advanced Palette:"); + const char* palette_group_names[] = {"OW Main", "OW Aux", "OW Anim", + "Dungeon", "Sprites", "Armor", + "Sword"}; + if (Combo("##AdvPaletteGroup", ¤t_palette_group_, + palette_group_names, 7)) { + RETURN_IF_ERROR(RefreshAllPalettes()); + } + + Text("Normalization:"); + int mask_value = static_cast(palette_normalization_mask_); + if (SliderInt("##NormMask", &mask_value, 1, 255, "0x%02X")) { + palette_normalization_mask_ = static_cast(mask_value); + RETURN_IF_ERROR(LoadTile8()); // Reload with new mask + } + + Checkbox("Auto Normalize", &auto_normalize_pixels_); + } + EndChild(); + + // Manual tile8 editor popup + DrawManualTile8Inputs(); + + EndTable(); + } + + // Close the tile16 editor scroll region + EndChild(); EndTable(); } diff --git a/src/app/zelda3/overworld/overworld.cc b/src/app/zelda3/overworld/overworld.cc index f685766a..8e925705 100644 --- a/src/app/zelda3/overworld/overworld.cc +++ b/src/app/zelda3/overworld/overworld.cc @@ -12,6 +12,8 @@ #include "app/gfx/snes_tile.h" #include "app/rom.h" #include "app/snes.h" +#include "app/zelda3/overworld/overworld_entrance.h" +#include "app/zelda3/overworld/overworld_exit.h" #include "util/hex.h" #include "util/log.h" #include "util/macro.h" @@ -38,6 +40,7 @@ absl::Status Overworld::Load(Rom* rom) { } FetchLargeMaps(); + LoadTileTypes(); RETURN_IF_ERROR(LoadEntrances()); RETURN_IF_ERROR(LoadHoles()); RETURN_IF_ERROR(LoadExits()); @@ -437,14 +440,6 @@ absl::Status Overworld::LoadExits() { } absl::Status Overworld::LoadItems() { - // byte asmVersion = ROM.DATA[Constants.OverworldCustomASMHasBeenApplied]; - - // // Version 0x03 of the OW ASM added item support for the SW. - // int maxOW = asmVersion >= 0x03 && asmVersion != 0xFF ? Constants.NumberOfOWMaps : 0x80; - - // int pointerSNES = ROM.ReadLong(Constants.overworldItemsAddress); - // this.ItemPointerAddress = Utils.SnesToPc(pointerSNES); // 0x1BC2F9 -> 0x0DC2F9 - // Version 0x03 of the OW ASM added item support for the SW. uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; @@ -606,6 +601,10 @@ absl::Status Overworld::Save(Rom* rom) { RETURN_IF_ERROR(SaveOverworldMaps()) RETURN_IF_ERROR(SaveEntrances()) RETURN_IF_ERROR(SaveExits()) + RETURN_IF_ERROR(SaveItems()) + RETURN_IF_ERROR(SaveMapOverlays()) + RETURN_IF_ERROR(SaveOverworldTilesType()) + RETURN_IF_ERROR(SaveAreaSpecificBGColors()) RETURN_IF_ERROR(SaveMusic()) RETURN_IF_ERROR(SaveAreaSizes()) return absl::OkStatus(); @@ -751,6 +750,17 @@ absl::Status Overworld::SaveOverworldMaps() { absl::Status Overworld::SaveLargeMaps() { util::logf("Saving Large Maps"); + + // Check if this is a v3+ ROM to use expanded transition system + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool use_expanded_transitions = (asm_version >= 3 && asm_version != 0xFF); + + if (use_expanded_transitions) { + // Use new v3+ complex transition system with neighbor awareness + return SaveLargeMapsExpanded(); + } + + // Original vanilla/v2 logic preserved std::vector checked_map; for (int i = 0; i < kNumMapsPerWorld; ++i) { @@ -794,7 +804,7 @@ absl::Status Overworld::SaveLargeMaps() { 0x04)); } - // Check 5 and 6 + // Check 5 and 6 - transition targets RETURN_IF_ERROR( rom()->WriteShort(kTransitionTargetNorth + (i * 2), (uint16_t)((parent_y_pos * 0x200) - 0xE0))); @@ -823,7 +833,7 @@ absl::Status Overworld::SaveLargeMaps() { rom()->WriteShort(kTransitionTargetWest + (i * 2) + 18, (uint16_t)((parent_x_pos * 0x200) - 0x100))); - // Check 7 and 8 + // Check 7 and 8 - transition positions RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionX + (i * 2), (parent_x_pos * 0x200))); RETURN_IF_ERROR(rom()->WriteShort(kOverworldTransitionPositionY + (i * 2), @@ -836,7 +846,6 @@ absl::Status Overworld::SaveLargeMaps() { rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 02, (parent_y_pos * 0x200))); - // problematic RETURN_IF_ERROR( rom()->WriteShort(kOverworldTransitionPositionX + (i * 2) + 16, (parent_x_pos * 0x200))); @@ -851,7 +860,7 @@ absl::Status Overworld::SaveLargeMaps() { rom()->WriteShort(kOverworldTransitionPositionY + (i * 2) + 18, (parent_y_pos * 0x200))); - // Check 9 + // Check 9 - simple vanilla large area transitions RETURN_IF_ERROR(rom()->WriteShort( kOverworldScreenTileMapChangeByScreen1 + (i * 2) + 00, 0x0060)); RETURN_IF_ERROR(rom()->WriteShort( @@ -1069,6 +1078,580 @@ absl::Status Overworld::SaveLargeMaps() { return absl::OkStatus(); } +absl::Status Overworld::SaveSmallAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4) { + // Set basic transition targets + RETURN_IF_ERROR(rom()->WriteShort(transition_target_north + (i * 2), + (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); + RETURN_IF_ERROR(rom()->WriteShort(transition_target_west + (i * 2), + (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); + + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2), parent_x_pos * 0x0200)); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2), parent_y_pos * 0x0200)); + + // byScreen1 = Transitioning right + uint16_t by_screen1_small = 0x0060; + + // Check west neighbor for transition adjustments + if ((i % 0x40) - 1 >= 0) { + auto& west_neighbor = overworld_maps_[i - 1]; + + // Transition from bottom right quadrant of large area to small area + if (west_neighbor.area_size() == AreaSizeEnum::LargeArea && west_neighbor.large_index() == 3) { + by_screen1_small = 0xF060; + } + // Transition from bottom quadrant of tall area to small area + else if (west_neighbor.area_size() == AreaSizeEnum::TallArea && west_neighbor.large_index() == 2) { + by_screen1_small = 0xF060; + } + } + + RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2), by_screen1_small)); + + // byScreen2 = Transitioning left + uint16_t by_screen2_small = 0x0040; + + // Check east neighbor for transition adjustments + if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) { + auto& east_neighbor = overworld_maps_[i + 1]; + + // Transition from bottom left quadrant of large area to small area + if (east_neighbor.area_size() == AreaSizeEnum::LargeArea && east_neighbor.large_index() == 2) { + by_screen2_small = 0xF040; + } + // Transition from bottom quadrant of tall area to small area + else if (east_neighbor.area_size() == AreaSizeEnum::TallArea && east_neighbor.large_index() == 2) { + by_screen2_small = 0xF040; + } + } + + RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2), by_screen2_small)); + + // byScreen3 = Transitioning down + uint16_t by_screen3_small = 0x1800; + + // Check north neighbor for transition adjustments + if ((i % 0x40) - 8 >= 0) { + auto& north_neighbor = overworld_maps_[i - 8]; + + // Transition from bottom right quadrant of large area to small area + if (north_neighbor.area_size() == AreaSizeEnum::LargeArea && north_neighbor.large_index() == 3) { + by_screen3_small = 0x17C0; + } + // Transition from right quadrant of wide area to small area + else if (north_neighbor.area_size() == AreaSizeEnum::WideArea && north_neighbor.large_index() == 1) { + by_screen3_small = 0x17C0; + } + } + + RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2), by_screen3_small)); + + // byScreen4 = Transitioning up + uint16_t by_screen4_small = 0x1000; + + // Check south neighbor for transition adjustments + if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) { + auto& south_neighbor = overworld_maps_[i + 8]; + + // Transition from top right quadrant of large area to small area + if (south_neighbor.area_size() == AreaSizeEnum::LargeArea && south_neighbor.large_index() == 1) { + by_screen4_small = 0x0FC0; + } + // Transition from right quadrant of wide area to small area + else if (south_neighbor.area_size() == AreaSizeEnum::WideArea && south_neighbor.large_index() == 1) { + by_screen4_small = 0x0FC0; + } + } + + RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2), by_screen4_small)); + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveLargeAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4) { + // Set transition targets for all 4 quadrants + const uint16_t offsets[] = {0, 2, 16, 18}; + for (auto offset : offsets) { + RETURN_IF_ERROR(rom()->WriteShort(transition_target_north + (i * 2) + offset, + (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); + RETURN_IF_ERROR(rom()->WriteShort(transition_target_west + (i * 2) + offset, + (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset, parent_x_pos * 0x0200)); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset, parent_y_pos * 0x0200)); + } + + // Complex neighbor-aware transition calculations for large areas + // byScreen1 = Transitioning right + std::array by_screen1_large = {0x0060, 0x0060, 0x1060, 0x1060}; + + // Check west neighbor + if ((i % 0x40) - 1 >= 0) { + auto& west_neighbor = overworld_maps_[i - 1]; + + if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (west_neighbor.large_index()) { + case 1: // From bottom right to bottom left of large area + by_screen1_large[2] = 0x0060; + break; + case 3: // From bottom right to top left of large area + by_screen1_large[0] = 0xF060; + break; + } + } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) { + switch (west_neighbor.large_index()) { + case 0: // From bottom of tall to bottom left of large + by_screen1_large[2] = 0x0060; + break; + case 2: // From bottom of tall to top left of large + by_screen1_large[0] = 0xF060; + break; + } + } + } + + for (int j = 0; j < 4; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j], by_screen1_large[j])); + } + + // byScreen2 = Transitioning left + std::array by_screen2_large = {0x0080, 0x0080, 0x1080, 0x1080}; + + // Check east neighbor + if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) { + auto& east_neighbor = overworld_maps_[i + 2]; + + if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (east_neighbor.large_index()) { + case 0: // From bottom left to bottom right of large area + by_screen2_large[3] = 0x0080; + break; + case 2: // From bottom left to top right of large area + by_screen2_large[1] = 0xF080; + break; + } + } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) { + switch (east_neighbor.large_index()) { + case 0: // From bottom of tall to bottom right of large + by_screen2_large[3] = 0x0080; + break; + case 2: // From bottom of tall to top right of large + by_screen2_large[1] = 0xF080; + break; + } + } + } + + for (int j = 0; j < 4; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j], by_screen2_large[j])); + } + + // byScreen3 = Transitioning down + std::array by_screen3_large = {0x1800, 0x1840, 0x1800, 0x1840}; + + // Check north neighbor + if ((i % 0x40) - 8 >= 0) { + auto& north_neighbor = overworld_maps_[i - 8]; + + if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (north_neighbor.large_index()) { + case 2: // From bottom right to top right of large area + by_screen3_large[1] = 0x1800; + break; + case 3: // From bottom right to top left of large area + by_screen3_large[0] = 0x17C0; + break; + } + } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) { + switch (north_neighbor.large_index()) { + case 0: // From right of wide to top right of large + by_screen3_large[1] = 0x1800; + break; + case 1: // From right of wide to top left of large + by_screen3_large[0] = 0x17C0; + break; + } + } + } + + for (int j = 0; j < 4; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j], by_screen3_large[j])); + } + + // byScreen4 = Transitioning up + std::array by_screen4_large = {0x2000, 0x2040, 0x2000, 0x2040}; + + // Check south neighbor + if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) { + auto& south_neighbor = overworld_maps_[i + 16]; + + if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (south_neighbor.large_index()) { + case 0: // From top right to bottom right of large area + by_screen4_large[3] = 0x2000; + break; + case 1: // From top right to bottom left of large area + by_screen4_large[2] = 0x1FC0; + break; + } + } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) { + switch (south_neighbor.large_index()) { + case 0: // From right of wide to bottom right of large + by_screen4_large[3] = 0x2000; + break; + case 1: // From right of wide to bottom left of large + by_screen4_large[2] = 0x1FC0; + break; + } + } + } + + for (int j = 0; j < 4; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j], by_screen4_large[j])); + } + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveWideAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4) { + // Set transition targets for both quadrants + const uint16_t offsets[] = {0, 2}; + for (auto offset : offsets) { + RETURN_IF_ERROR(rom()->WriteShort(transition_target_north + (i * 2) + offset, + (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); + RETURN_IF_ERROR(rom()->WriteShort(transition_target_west + (i * 2) + offset, + (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset, parent_x_pos * 0x0200)); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset, parent_y_pos * 0x0200)); + } + + // byScreen1 = Transitioning right + std::array by_screen1_wide = {0x0060, 0x0060}; + + // Check west neighbor + if ((i % 0x40) - 1 >= 0) { + auto& west_neighbor = overworld_maps_[i - 1]; + + // From bottom right of large to left of wide + if (west_neighbor.area_size() == AreaSizeEnum::LargeArea && west_neighbor.large_index() == 3) { + by_screen1_wide[0] = 0xF060; + } + // From bottom of tall to left of wide + else if (west_neighbor.area_size() == AreaSizeEnum::TallArea && west_neighbor.large_index() == 2) { + by_screen1_wide[0] = 0xF060; + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j], by_screen1_wide[j])); + } + + // byScreen2 = Transitioning left + std::array by_screen2_wide = {0x0080, 0x0080}; + + // Check east neighbor + if ((i % 0x40) + 2 < 0x40 && i + 2 < kNumOverworldMaps) { + auto& east_neighbor = overworld_maps_[i + 2]; + + // From bottom left of large to right of wide + if (east_neighbor.area_size() == AreaSizeEnum::LargeArea && east_neighbor.large_index() == 2) { + by_screen2_wide[1] = 0xF080; + } + // From bottom of tall to right of wide + else if (east_neighbor.area_size() == AreaSizeEnum::TallArea && east_neighbor.large_index() == 2) { + by_screen2_wide[1] = 0xF080; + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j], by_screen2_wide[j])); + } + + // byScreen3 = Transitioning down + std::array by_screen3_wide = {0x1800, 0x1840}; + + // Check north neighbor + if ((i % 0x40) - 8 >= 0) { + auto& north_neighbor = overworld_maps_[i - 8]; + + if (north_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (north_neighbor.large_index()) { + case 2: // From bottom right of large to right of wide + by_screen3_wide[1] = 0x1800; + break; + case 3: // From bottom right of large to left of wide + by_screen3_wide[0] = 0x17C0; + break; + } + } else if (north_neighbor.area_size() == AreaSizeEnum::WideArea) { + switch (north_neighbor.large_index()) { + case 0: // From right of wide to right of wide + by_screen3_wide[1] = 0x1800; + break; + case 1: // From right of wide to left of wide + by_screen3_wide[0] = 0x07C0; + break; + } + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j], by_screen3_wide[j])); + } + + // byScreen4 = Transitioning up + std::array by_screen4_wide = {0x1000, 0x1040}; + + // Check south neighbor + if ((i % 0x40) + 8 < 0x40 && i + 8 < kNumOverworldMaps) { + auto& south_neighbor = overworld_maps_[i + 8]; + + if (south_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (south_neighbor.large_index()) { + case 0: // From top right of large to right of wide + by_screen4_wide[1] = 0x1000; + break; + case 1: // From top right of large to left of wide + by_screen4_wide[0] = 0x0FC0; + break; + } + } else if (south_neighbor.area_size() == AreaSizeEnum::WideArea) { + if (south_neighbor.large_index() == 1) { + by_screen4_wide[0] = 0x0FC0; + } + switch (south_neighbor.large_index()) { + case 0: // From right of wide to right of wide + by_screen4_wide[1] = 0x1000; + break; + case 1: // From right of wide to left of wide + by_screen4_wide[0] = 0x0FC0; + break; + } + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j], by_screen4_wide[j])); + } + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveTallAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4) { + // Set transition targets for both quadrants + const uint16_t offsets[] = {0, 16}; + for (auto offset : offsets) { + RETURN_IF_ERROR(rom()->WriteShort(transition_target_north + (i * 2) + offset, + (uint16_t)((parent_y_pos * 0x0200) - 0x00E0))); + RETURN_IF_ERROR(rom()->WriteShort(transition_target_west + (i * 2) + offset, + (uint16_t)((parent_x_pos * 0x0200) - 0x0100))); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_x + (i * 2) + offset, parent_x_pos * 0x0200)); + RETURN_IF_ERROR(rom()->WriteShort(transition_pos_y + (i * 2) + offset, parent_y_pos * 0x0200)); + } + + // byScreen1 = Transitioning right + std::array by_screen1_tall = {0x0060, 0x1060}; + + // Check west neighbor + if ((i % 0x40) - 1 >= 0) { + auto& west_neighbor = overworld_maps_[i - 1]; + + if (west_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (west_neighbor.large_index()) { + case 1: // From bottom right of large to bottom of tall + by_screen1_tall[1] = 0x0060; + break; + case 3: // From bottom right of large to top of tall + by_screen1_tall[0] = 0xF060; + break; + } + } else if (west_neighbor.area_size() == AreaSizeEnum::TallArea) { + switch (west_neighbor.large_index()) { + case 0: // From bottom of tall to bottom of tall + by_screen1_tall[1] = 0x0060; + break; + case 2: // From bottom of tall to top of tall + by_screen1_tall[0] = 0xF060; + break; + } + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_1 + (i * 2) + offsets[j], by_screen1_tall[j])); + } + + // byScreen2 = Transitioning left + std::array by_screen2_tall = {0x0040, 0x1040}; + + // Check east neighbor + if ((i % 0x40) + 1 < 0x40 && i + 1 < kNumOverworldMaps) { + auto& east_neighbor = overworld_maps_[i + 1]; + + if (east_neighbor.area_size() == AreaSizeEnum::LargeArea) { + switch (east_neighbor.large_index()) { + case 0: // From bottom left of large to bottom of tall + by_screen2_tall[1] = 0x0040; + break; + case 2: // From bottom left of large to top of tall + by_screen2_tall[0] = 0xF040; + break; + } + } else if (east_neighbor.area_size() == AreaSizeEnum::TallArea) { + switch (east_neighbor.large_index()) { + case 0: // From bottom of tall to bottom of tall + by_screen2_tall[1] = 0x0040; + break; + case 2: // From bottom of tall to top of tall + by_screen2_tall[0] = 0xF040; + break; + } + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_2 + (i * 2) + offsets[j], by_screen2_tall[j])); + } + + // byScreen3 = Transitioning down + std::array by_screen3_tall = {0x1800, 0x1800}; + + // Check north neighbor + if ((i % 0x40) - 8 >= 0) { + auto& north_neighbor = overworld_maps_[i - 8]; + + // From bottom right of large to top of tall + if (north_neighbor.area_size() == AreaSizeEnum::LargeArea && north_neighbor.large_index() == 3) { + by_screen3_tall[0] = 0x17C0; + } + // From right of wide to top of tall + else if (north_neighbor.area_size() == AreaSizeEnum::WideArea && north_neighbor.large_index() == 1) { + by_screen3_tall[0] = 0x17C0; + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_3 + (i * 2) + offsets[j], by_screen3_tall[j])); + } + + // byScreen4 = Transitioning up + std::array by_screen4_tall = {0x2000, 0x2000}; + + // Check south neighbor + if ((i % 0x40) + 16 < 0x40 && i + 16 < kNumOverworldMaps) { + auto& south_neighbor = overworld_maps_[i + 16]; + + // From top right of large to bottom of tall + if (south_neighbor.area_size() == AreaSizeEnum::LargeArea && south_neighbor.large_index() == 1) { + by_screen4_tall[1] = 0x1FC0; + } + // From right of wide to bottom of tall + else if (south_neighbor.area_size() == AreaSizeEnum::WideArea && south_neighbor.large_index() == 1) { + by_screen4_tall[1] = 0x1FC0; + } + } + + for (int j = 0; j < 2; j++) { + RETURN_IF_ERROR(rom()->WriteShort(screen_change_4 + (i * 2) + offsets[j], by_screen4_tall[j])); + } + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveLargeMapsExpanded() { + util::logf("Saving Large Maps (v3+ Expanded)"); + + // Use expanded memory locations for v3+ + int transition_target_north = zelda3::transition_target_northExpanded; + int transition_target_west = zelda3::transition_target_westExpanded; + int transition_pos_x = zelda3::kOverworldTransitionPositionXExpanded; + int transition_pos_y = zelda3::kOverworldTransitionPositionYExpanded; + int screen_change_1 = zelda3::kOverworldScreenTileMapChangeByScreen1Expanded; + int screen_change_2 = zelda3::kOverworldScreenTileMapChangeByScreen2Expanded; + int screen_change_3 = zelda3::kOverworldScreenTileMapChangeByScreen3Expanded; + int screen_change_4 = zelda3::kOverworldScreenTileMapChangeByScreen4Expanded; + + std::vector checked_map; + + // Process all overworld maps (0xA0 for v3) + for (int i = 0; i < kNumOverworldMaps; ++i) { + // Skip if this map was already processed as part of a multi-area structure + if (std::find(checked_map.begin(), checked_map.end(), i) != checked_map.end()) { + continue; + } + + int parent_y_pos = (overworld_maps_[i].parent() % 0x40) / 8; + int parent_x_pos = (overworld_maps_[i].parent() % 0x40) % 8; + + // Write the map parent ID to expanded parent table + RETURN_IF_ERROR(rom()->WriteByte(zelda3::kOverworldMapParentIdExpanded + i, + overworld_maps_[i].parent())); + + // Handle transitions based on area size + switch (overworld_maps_[i].area_size()) { + case AreaSizeEnum::SmallArea: + RETURN_IF_ERROR(SaveSmallAreaTransitions(i, parent_x_pos, parent_y_pos, + transition_target_north, transition_target_west, + transition_pos_x, transition_pos_y, + screen_change_1, screen_change_2, + screen_change_3, screen_change_4)); + checked_map.emplace_back(i); + break; + + case AreaSizeEnum::LargeArea: + RETURN_IF_ERROR(SaveLargeAreaTransitions(i, parent_x_pos, parent_y_pos, + transition_target_north, transition_target_west, + transition_pos_x, transition_pos_y, + screen_change_1, screen_change_2, + screen_change_3, screen_change_4)); + // Mark all 4 quadrants as processed + checked_map.emplace_back(i); + checked_map.emplace_back(i + 1); + checked_map.emplace_back(i + 8); + checked_map.emplace_back(i + 9); + break; + + case AreaSizeEnum::WideArea: + RETURN_IF_ERROR(SaveWideAreaTransitions(i, parent_x_pos, parent_y_pos, + transition_target_north, transition_target_west, + transition_pos_x, transition_pos_y, + screen_change_1, screen_change_2, + screen_change_3, screen_change_4)); + // Mark both horizontal quadrants as processed + checked_map.emplace_back(i); + checked_map.emplace_back(i + 1); + break; + + case AreaSizeEnum::TallArea: + RETURN_IF_ERROR(SaveTallAreaTransitions(i, parent_x_pos, parent_y_pos, + transition_target_north, transition_target_west, + transition_pos_x, transition_pos_y, + screen_change_1, screen_change_2, + screen_change_3, screen_change_4)); + // Mark both vertical quadrants as processed + checked_map.emplace_back(i); + checked_map.emplace_back(i + 8); + break; + } + } + + return absl::OkStatus(); +} + namespace { std::vector GetAllTile16(OverworldMapTiles& map_tiles_) { std::vector all_tile_16; // Ensure it's 64 bits @@ -1592,24 +2175,26 @@ absl::Status Overworld::SaveMap16Tiles() { absl::Status Overworld::SaveEntrances() { util::logf("Saving Entrances"); - int ow_entrance_map_ptr = kOverworldEntranceMap; - int ow_entrance_pos_ptr = kOverworldEntrancePos; - int ow_entrance_id_ptr = kOverworldEntranceEntranceId; - int num_entrances = kNumOverworldEntrances; + + // Use expanded entrance tables if available if (expanded_entrances_) { - ow_entrance_map_ptr = kOverworldEntranceMapExpanded; - ow_entrance_pos_ptr = kOverworldEntrancePosExpanded; - ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded; - expanded_entrances_ = true; - } - - for (int i = 0; i < kNumOverworldEntrances; i++) { - RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntranceMap + (i * 2), - all_entrances_[i].map_id_)) - RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntrancePos + (i * 2), - all_entrances_[i].map_pos_)) - RETURN_IF_ERROR(rom()->WriteByte(kOverworldEntranceEntranceId + i, - all_entrances_[i].entrance_id_)) + for (int i = 0; i < kNumOverworldEntrances; i++) { + RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntranceMapExpanded + (i * 2), + all_entrances_[i].map_id_)) + RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntrancePosExpanded + (i * 2), + all_entrances_[i].map_pos_)) + RETURN_IF_ERROR(rom()->WriteByte(kOverworldEntranceEntranceIdExpanded + i, + all_entrances_[i].entrance_id_)) + } + } else { + for (int i = 0; i < kNumOverworldEntrances; i++) { + RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntranceMap + (i * 2), + all_entrances_[i].map_id_)) + RETURN_IF_ERROR(rom()->WriteShort(kOverworldEntrancePos + (i * 2), + all_entrances_[i].map_pos_)) + RETURN_IF_ERROR(rom()->WriteByte(kOverworldEntranceEntranceId + i, + all_entrances_[i].entrance_id_)) + } } for (int i = 0; i < kNumOverworldHoles; i++) { @@ -1626,6 +2211,20 @@ absl::Status Overworld::SaveEntrances() { absl::Status Overworld::SaveExits() { util::logf("Saving Exits"); + + // ASM version 0x03 added SW support and the exit leading to Zora's Domain specifically + // needs to be updated because its camera values are incorrect. + // We only update it if it was a vanilla ROM though because we don't know if the + // user has already adjusted it or not. + uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; + if (asm_version == 0x00) { + // Apply special fix for Zora's Domain exit (index 0x4D) + // TODO: Implement SpecialUpdatePosition for OverworldExit + // if (all_exits_.size() > 0x4D) { + // all_exits_[0x4D].SpecialUpdatePosition(); + // } + } + for (int i = 0; i < kNumOverworldExits; i++) { RETURN_IF_ERROR( rom()->WriteShort(OWExitRoomId + (i * 2), all_exits_[i].room_id_)); @@ -1764,6 +2363,160 @@ absl::Status Overworld::SaveItems() { return absl::OkStatus(); } +absl::Status Overworld::SaveMapOverlays() { + util::logf("Saving Map Overlays"); + + // Generate the new overlay code that handles interactive overlays + std::vector new_overlay_code = { + 0xC2, 0x30, // REP #$30 + 0xA5, 0x8A, // LDA $8A + 0x0A, 0x18, // ASL : CLC + 0x65, 0x8A, // ADC $8A + 0xAA, // TAX + 0xBF, 0x00, 0x00, 0x00, // LDA, X + 0x85, 0x00, // STA $00 + 0xBF, 0x00, 0x00, 0x00, // LDA, X +2 + 0x85, 0x02, // STA $02 + 0x4B, // PHK + 0xF4, 0x00, 0x00, // This position +3 ? + 0xDC, 0x00, 0x00, // JML [$00 00] + 0xE2, 0x30, // SEP #$30 + 0xAB, // PLB + 0x6B, // RTL + }; + + // Write overlay code to ROM + constexpr int kOverlayCodeStart = 0x077657; + RETURN_IF_ERROR(rom()->WriteVector(kOverlayCodeStart, new_overlay_code)); + + // Set up overlay pointers + int ptr_start = kOverlayCodeStart + 0x20; + int snes_ptr_start = PcToSnes(ptr_start); + + // Write overlay pointer addresses in the code + RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 10, snes_ptr_start)); + RETURN_IF_ERROR(rom()->WriteLong(kOverlayCodeStart + 16, snes_ptr_start + 2)); + + int pea_addr = PcToSnes(kOverlayCodeStart + 27); + RETURN_IF_ERROR(rom()->WriteShort(kOverlayCodeStart + 23, pea_addr)); + + // Write overlay data to expanded space + constexpr int kExpandedOverlaySpace = 0x120000; + int pos = kExpandedOverlaySpace; + int ptr_pos = kOverlayCodeStart + 32; + + for (int i = 0; i < kNumOverworldMaps; i++) { + int snes_addr = PcToSnes(pos); + RETURN_IF_ERROR(rom()->WriteLong(ptr_pos, snes_addr & 0xFFFFFF)); + ptr_pos += 3; + + // Write overlay data for each map that has overlays + if (overworld_maps_[i].has_overlay()) { + const auto& overlay_data = overworld_maps_[i].overlay_data(); + for (size_t t = 0; t < overlay_data.size(); t += 3) { + if (t + 2 < overlay_data.size()) { + // Generate LDA/STA sequence for each overlay tile + RETURN_IF_ERROR(rom()->WriteByte(pos, 0xA9)); // LDA #$ + RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t] | (overlay_data[t + 1] << 8))); + pos += 3; + + RETURN_IF_ERROR(rom()->WriteByte(pos, 0x8D)); // STA $xxxx + RETURN_IF_ERROR(rom()->WriteShort(pos + 1, overlay_data[t + 2])); + pos += 3; + } + } + } + + RETURN_IF_ERROR(rom()->WriteByte(pos, 0x6B)); // RTL + pos++; + } + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveOverworldTilesType() { + util::logf("Saving Overworld Tiles Types"); + + for (int i = 0; i < kNumTileTypes; i++) { + RETURN_IF_ERROR(rom()->WriteByte(overworldTilesType + i, all_tiles_types_[i])); + } + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveCustomOverworldASM(bool enable_bg_color, bool enable_main_palette, + bool enable_mosaic, bool enable_gfx_groups, + bool enable_subscreen_overlay, bool enable_animated) { + util::logf("Applying Custom Overworld ASM"); + + // Set the enable/disable settings + uint8_t enable_value = enable_bg_color ? 0xFF : 0x00; + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomAreaSpecificBGEnabled, enable_value)); + + enable_value = enable_main_palette ? 0xFF : 0x00; + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMainPaletteEnabled, enable_value)); + + enable_value = enable_mosaic ? 0xFF : 0x00; + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMosaicEnabled, enable_value)); + + enable_value = enable_gfx_groups ? 0xFF : 0x00; + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomTileGFXGroupEnabled, enable_value)); + + enable_value = enable_animated ? 0xFF : 0x00; + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomAnimatedGFXEnabled, enable_value)); + + enable_value = enable_subscreen_overlay ? 0xFF : 0x00; + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomSubscreenOverlayEnabled, enable_value)); + + // Write the main palette table + for (int i = 0; i < kNumOverworldMaps; i++) { + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMainPaletteArray + i, + overworld_maps_[i].main_palette())); + } + + // Write the mosaic table + for (int i = 0; i < kNumOverworldMaps; i++) { + const auto& mosaic = overworld_maps_[i].mosaic_expanded(); + // .... udlr bit format + uint8_t mosaic_byte = (mosaic[0] ? 0x08 : 0x00) | // up + (mosaic[1] ? 0x04 : 0x00) | // down + (mosaic[2] ? 0x02 : 0x00) | // left + (mosaic[3] ? 0x01 : 0x00); // right + + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomMosaicArray + i, mosaic_byte)); + } + + // Write the main and animated gfx tiles table + for (int i = 0; i < kNumOverworldMaps; i++) { + for (int j = 0; j < 8; j++) { + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomTileGFXGroupArray + (i * 8) + j, + overworld_maps_[i].custom_tileset(j))); + } + RETURN_IF_ERROR(rom()->WriteByte(OverworldCustomAnimatedGFXArray + i, + overworld_maps_[i].animated_gfx())); + } + + // Write the subscreen overlay table + for (int i = 0; i < kNumOverworldMaps; i++) { + RETURN_IF_ERROR(rom()->WriteShort(OverworldCustomSubscreenOverlayArray + (i * 2), + overworld_maps_[i].subscreen_overlay())); + } + + return absl::OkStatus(); +} + +absl::Status Overworld::SaveAreaSpecificBGColors() { + util::logf("Saving Area Specific Background Colors"); + + // Write area-specific background colors if enabled + for (int i = 0; i < kNumOverworldMaps; i++) { + uint16_t bg_color = overworld_maps_[i].area_specific_bg_color(); + RETURN_IF_ERROR(rom()->WriteShort(OverworldCustomAreaSpecificBGPalette + (i * 2), bg_color)); + } + + return absl::OkStatus(); +} + absl::Status Overworld::SaveMapProperties() { util::logf("Saving Map Properties"); for (int i = 0; i < kDarkWorldMapIdStart; i++) { diff --git a/src/app/zelda3/overworld/overworld.h b/src/app/zelda3/overworld/overworld.h index 622c8c69..0aea3fb4 100644 --- a/src/app/zelda3/overworld/overworld.h +++ b/src/app/zelda3/overworld/overworld.h @@ -150,9 +150,36 @@ class Overworld { absl::Status Save(Rom *rom); absl::Status SaveOverworldMaps(); absl::Status SaveLargeMaps(); + absl::Status SaveLargeMapsExpanded(); + absl::Status SaveSmallAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4); + absl::Status SaveLargeAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4); + absl::Status SaveWideAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4); + absl::Status SaveTallAreaTransitions(int i, int parent_x_pos, int parent_y_pos, + int transition_target_north, int transition_target_west, + int transition_pos_x, int transition_pos_y, + int screen_change_1, int screen_change_2, + int screen_change_3, int screen_change_4); absl::Status SaveEntrances(); absl::Status SaveExits(); absl::Status SaveItems(); + absl::Status SaveMapOverlays(); + absl::Status SaveOverworldTilesType(); + absl::Status SaveCustomOverworldASM(bool enable_bg_color, bool enable_main_palette, + bool enable_mosaic, bool enable_gfx_groups, + bool enable_subscreen_overlay, bool enable_animated); + absl::Status SaveAreaSpecificBGColors(); absl::Status CreateTile32Tilemap(); absl::Status SaveMap16Expanded();