Refactor Overworld Editor and enhance settings management for v3 features

- Removed the outdated ZEML layout file and associated references to streamline the OverworldEditor.
- Introduced the OverworldEditorManager class to encapsulate v3 settings management, improving code organization and maintainability.
- Updated the OverworldEditor to utilize ImGui for layout, enhancing user interaction and flexibility.
- Enhanced the changelog with detailed descriptions of new features and improvements, including a comprehensive undo/redo system and advanced palette management.
- Removed deprecated diagnostic patterns from the clangd configuration to improve code quality checks.
This commit is contained in:
scawful
2025-09-27 15:47:22 -04:00
parent a9ead0a45c
commit 7355294f49
13 changed files with 1405 additions and 218 deletions

View File

@@ -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

View File

@@ -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<MapPropertiesSystem>(
&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<OverworldEditorManager>(&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_);

View File

@@ -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<MapPropertiesSystem> map_properties_system_;
std::unique_ptr<OverworldEditorManager> 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

View File

@@ -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", &current_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<int>(current_map->area_size());
if (Combo("Area Size", &current_size, area_size_names, 4)) {
current_map->SetAreaSize(static_cast<zelda3::AreaSizeEnum>(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<uint16_t>(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<uint8_t>(gfx_int);
}
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,90 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H
#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H
#include <memory>
#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

View File

@@ -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", &current_palette_group_,
palette_group_names, 7)) {
RETURN_IF_ERROR(RefreshAllPalettes());
}
Text("Normalization:");
int mask_value = static_cast<int>(palette_normalization_mask_);
if (SliderInt("##NormMask", &mask_value, 1, 255, "0x%02X")) {
palette_normalization_mask_ = static_cast<uint8_t>(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", &current_palette_group_,
palette_group_names, 7)) {
RETURN_IF_ERROR(RefreshAllPalettes());
}
Text("Normalization:");
int mask_value = static_cast<int>(palette_normalization_mask_);
if (SliderInt("##NormMask", &mask_value, 1, 255, "0x%02X")) {
palette_normalization_mask_ = static_cast<uint8_t>(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();
}

View File

@@ -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<uint8_t> 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<uint16_t, 4> 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<uint16_t, 4> 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<uint16_t, 4> 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<uint16_t, 4> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint16_t, 2> 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<uint8_t> 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<uint64_t> GetAllTile16(OverworldMapTiles& map_tiles_) {
std::vector<uint64_t> 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<uint8_t> 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++) {

View File

@@ -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();