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:
@@ -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
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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
|
||||
|
||||
284
src/app/editor/overworld/overworld_editor_manager.cc
Normal file
284
src/app/editor/overworld/overworld_editor_manager.cc
Normal 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", ¤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<int>(current_map->area_size());
|
||||
|
||||
if (Combo("Area Size", ¤t_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
|
||||
90
src/app/editor/overworld/overworld_editor_manager.h
Normal file
90
src/app/editor/overworld/overworld_editor_manager.h
Normal 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
|
||||
@@ -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<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", ¤t_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();
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user