diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 1c569ec6..3736cc67 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -1692,10 +1692,6 @@ absl::Status OverworldEditor::DrawAreaGraphics() { return absl::OkStatus(); } -// DrawTileSelector() removed - replaced by individual card system in Update() -// DrawOverworldEntrances(), DrawOverworldExits(), DrawOverworldItems(), DrawOverworldSprites() -// removed - moved to OverworldEntityRenderer - absl::Status OverworldEditor::Save() { if (core::FeatureFlags::get().overworld.kSaveOverworldMaps) { RETURN_IF_ERROR(overworld_.CreateTile32Tilemap()); diff --git a/src/zelda3/overworld/overworld.cc b/src/zelda3/overworld/overworld.cc index d6f4bceb..65cf6675 100644 --- a/src/zelda3/overworld/overworld.cc +++ b/src/zelda3/overworld/overworld.cc @@ -22,6 +22,7 @@ #include "util/log.h" #include "util/macro.h" #include "zelda3/overworld/overworld_item.h" +#include "zelda3/overworld/overworld_map.h" namespace yaze { namespace zelda3 { @@ -78,12 +79,12 @@ absl::Status Overworld::Load(Rom* rom) { { gfx::ScopedTimer entrances_timer("LoadEntrances"); - RETURN_IF_ERROR(LoadEntrances()); + ASSIGN_OR_RETURN(all_entrances_, LoadEntrances(rom_)); } { gfx::ScopedTimer holes_timer("LoadHoles"); - RETURN_IF_ERROR(LoadHoles()); + ASSIGN_OR_RETURN(all_holes_, LoadHoles(rom_)); } { @@ -686,66 +687,6 @@ void Overworld::LoadTileTypes() { } } -absl::Status Overworld::LoadEntrances() { - int ow_entrance_map_ptr = kOverworldEntranceMap; - int ow_entrance_pos_ptr = kOverworldEntrancePos; - int ow_entrance_id_ptr = kOverworldEntranceEntranceId; - int num_entrances = 129; - - // Check if expanded entrance data is actually present in ROM - // The flag position should contain 0xB8 for vanilla, something else for expanded - if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8) { - // ROM has expanded entrance data - use expanded addresses - ow_entrance_map_ptr = kOverworldEntranceMapExpanded; - ow_entrance_pos_ptr = kOverworldEntrancePosExpanded; - ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded; - expanded_entrances_ = true; - num_entrances = 256; // Expanded entrance count - } - // Otherwise use vanilla addresses (already set above) - - for (int i = 0; i < num_entrances; i++) { - ASSIGN_OR_RETURN(auto map_id, - rom()->ReadWord(ow_entrance_map_ptr + (i * 2))); - ASSIGN_OR_RETURN(auto map_pos, - rom()->ReadWord(ow_entrance_pos_ptr + (i * 2))); - ASSIGN_OR_RETURN(auto entrance_id, rom()->ReadByte(ow_entrance_id_ptr + i)); - int p = map_pos >> 1; - int x = (p % 64); - int y = (p >> 6); - bool deleted = false; - if (map_pos == 0xFFFF) { - deleted = true; - } - all_entrances_.emplace_back( - (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512), - (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, map_pos, - deleted); - } - - return absl::OkStatus(); -} - -absl::Status Overworld::LoadHoles() { - constexpr int kNumHoles = 0x13; - for (int i = 0; i < kNumHoles; i++) { - ASSIGN_OR_RETURN(auto map_id, - rom()->ReadWord(kOverworldHoleArea + (i * 2))); - ASSIGN_OR_RETURN(auto map_pos, - rom()->ReadWord(kOverworldHolePos + (i * 2))); - ASSIGN_OR_RETURN(auto entrance_id, - rom()->ReadByte(kOverworldHoleEntrance + i)); - int p = (map_pos + 0x400) >> 1; - int x = (p % 64); - int y = (p >> 6); - all_holes_.emplace_back( - (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512), - (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, - (uint16_t)(map_pos + 0x400), true); - } - return absl::OkStatus(); -} - absl::Status Overworld::LoadExits() { const int NumberOfOverworldExits = 0x4F; std::vector exits; @@ -2572,58 +2513,8 @@ absl::Status Overworld::SaveMap16Tiles() { } absl::Status Overworld::SaveEntrances() { - util::logf("Saving Entrances"); - - auto write_entrance = [&](int index, uint32_t map_addr, uint32_t pos_addr, - uint32_t id_addr) -> absl::Status { - // Mirrors ZeldaFullEditor/Save.cs::SaveOWEntrances (see lines ~1081-1085) - // where MapID and MapPos are written as 16-bit words and EntranceID as a byte. - RETURN_IF_ERROR( - rom()->WriteShort(map_addr, all_entrances_[index].map_id_)); - RETURN_IF_ERROR( - rom()->WriteShort(pos_addr, all_entrances_[index].map_pos_)); - RETURN_IF_ERROR( - rom()->WriteByte(id_addr, all_entrances_[index].entrance_id_)); - return absl::OkStatus(); - }; - - // Always keep the legacy tables in sync for pure vanilla ROMs so e.g. Hyrule - // Magic expects them. ZScream does the same in SaveOWEntrances. - for (int i = 0; i < kNumOverworldEntrances; ++i) { - RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMap + (i * 2), - kOverworldEntrancePos + (i * 2), - kOverworldEntranceEntranceId + i)); - } - - if (expanded_entrances_) { - // For ZS v3+ ROMs, mirror writes into the expanded tables the way - // ZeldaFullEditor does when the ASM patch is active. - for (int i = 0; i < kNumOverworldEntrances; ++i) { - RETURN_IF_ERROR(write_entrance(i, - kOverworldEntranceMapExpanded + (i * 2), - kOverworldEntrancePosExpanded + (i * 2), - kOverworldEntranceEntranceIdExpanded + i)); - } - } - - for (int i = 0; i < kNumOverworldHoles; ++i) { - RETURN_IF_ERROR( - rom()->WriteShort(kOverworldHoleArea + (i * 2), all_holes_[i].map_id_)); - - // ZeldaFullEditor/Data/Overworld.cs::LoadHoles() adds 0x400 when loading - // (see lines ~1006-1014). SaveOWEntrances subtracts it before writing - // (Save.cs lines ~1088-1092). We replicate that here so vanilla ROMs - // receive the expected values. - uint16_t rom_map_pos = - static_cast(all_holes_[i].map_pos_ >= 0x400 - ? all_holes_[i].map_pos_ - 0x400 - : all_holes_[i].map_pos_); - RETURN_IF_ERROR( - rom()->WriteShort(kOverworldHolePos + (i * 2), rom_map_pos)); - RETURN_IF_ERROR(rom()->WriteByte(kOverworldHoleEntrance + i, - all_holes_[i].entrance_id_)); - } - + RETURN_IF_ERROR(::yaze::zelda3::SaveEntrances(rom_, all_entrances_, expanded_entrances_)); + RETURN_IF_ERROR(SaveHoles(rom_, all_holes_)); return absl::OkStatus(); } diff --git a/src/zelda3/overworld/overworld.h b/src/zelda3/overworld/overworld.h index 328daa38..85f154e5 100644 --- a/src/zelda3/overworld/overworld.h +++ b/src/zelda3/overworld/overworld.h @@ -86,7 +86,6 @@ constexpr int kMap32TileBRExpanded = 0x1F8000; constexpr int kMap32TileCountExpanded = 0x0067E0; constexpr int kMap32ExpandedFlagPos = 0x01772E; // 0x04 constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F -constexpr int kOverworldEntranceExpandedFlagPos = 0x0DB895; // 0xB8 constexpr int overworldSpritesBeginingExpanded = 0x141438; constexpr int overworldSpritesZeldaExpanded = 0x141578; @@ -139,8 +138,6 @@ class Overworld { absl::Status Load(Rom *rom); absl::Status LoadOverworldMaps(); void LoadTileTypes(); - absl::Status LoadEntrances(); - absl::Status LoadHoles(); absl::Status LoadExits(); absl::Status LoadItems(); diff --git a/src/zelda3/overworld/overworld_entrance.cc b/src/zelda3/overworld/overworld_entrance.cc new file mode 100644 index 00000000..7e72dcd0 --- /dev/null +++ b/src/zelda3/overworld/overworld_entrance.cc @@ -0,0 +1,122 @@ +#include "zelda3/overworld/overworld_entrance.h" +#include +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "app/rom.h" +#include "util/macro.h" + +namespace yaze::zelda3 { + +absl::StatusOr> LoadEntrances(Rom* rom) { + std::vector entrances; + int ow_entrance_map_ptr = kOverworldEntranceMap; + int ow_entrance_pos_ptr = kOverworldEntrancePos; + int ow_entrance_id_ptr = kOverworldEntranceEntranceId; + int num_entrances = 129; + + // Check if expanded entrance data is actually present in ROM + // The flag position should contain 0xB8 for vanilla, something else for expanded + if (rom->data()[kOverworldEntranceExpandedFlagPos] != 0xB8) { + // ROM has expanded entrance data - use expanded addresses + ow_entrance_map_ptr = kOverworldEntranceMapExpanded; + ow_entrance_pos_ptr = kOverworldEntrancePosExpanded; + ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded; + num_entrances = 256; // Expanded entrance count + } + // Otherwise use vanilla addresses (already set above) + + for (int i = 0; i < num_entrances; i++) { + ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(ow_entrance_map_ptr + (i * 2))); + ASSIGN_OR_RETURN(auto map_pos, + rom->ReadWord(ow_entrance_pos_ptr + (i * 2))); + ASSIGN_OR_RETURN(auto entrance_id, rom->ReadByte(ow_entrance_id_ptr + i)); + int p = map_pos >> 1; + int x = (p % 64); + int y = (p >> 6); + bool deleted = false; + if (map_pos == 0xFFFF) { + deleted = true; + } + entrances.emplace_back( + (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512), + (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, map_pos, + deleted); + } + + return entrances; +} + +absl::StatusOr> LoadHoles(Rom* rom) { + constexpr int kNumHoles = 0x13; + std::vector holes; + for (int i = 0; i < kNumHoles; i++) { + ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(kOverworldHoleArea + (i * 2))); + ASSIGN_OR_RETURN(auto map_pos, rom->ReadWord(kOverworldHolePos + (i * 2))); + ASSIGN_OR_RETURN(auto entrance_id, + rom->ReadByte(kOverworldHoleEntrance + i)); + int p = (map_pos + 0x400) >> 1; + int x = (p % 64); + int y = (p >> 6); + holes.emplace_back( + (x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512), + (y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, + (uint16_t)(map_pos + 0x400), true); + } + return holes; +} + +absl::Status SaveEntrances(Rom* rom, + const std::vector& entrances, bool expanded_entrances) { + + auto write_entrance = [&](int index, uint32_t map_addr, uint32_t pos_addr, + uint32_t id_addr) -> absl::Status { + // Mirrors ZeldaFullEditor/Save.cs::SaveOWEntrances (see lines ~1081-1085) + // where MapID and MapPos are written as 16-bit words and EntranceID as a byte. + RETURN_IF_ERROR(rom->WriteShort(map_addr, entrances[index].map_id_)); + RETURN_IF_ERROR(rom->WriteShort(pos_addr, entrances[index].map_pos_)); + RETURN_IF_ERROR(rom->WriteByte(id_addr, entrances[index].entrance_id_)); + return absl::OkStatus(); + }; + + // Always keep the legacy tables in sync for pure vanilla ROMs so e.g. Hyrule + // Magic expects them. ZScream does the same in SaveOWEntrances. + for (int i = 0; i < kNumOverworldEntrances; ++i) { + RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMap + (i * 2), + kOverworldEntrancePos + (i * 2), + kOverworldEntranceEntranceId + i)); + } + + if (expanded_entrances) { + // For ZS v3+ ROMs, mirror writes into the expanded tables the way + // ZeldaFullEditor does when the ASM patch is active. + for (int i = 0; i < kNumOverworldEntrances; ++i) { + RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMapExpanded + (i * 2), + kOverworldEntrancePosExpanded + (i * 2), + kOverworldEntranceEntranceIdExpanded + i)); + } + } + + return absl::OkStatus(); +} + +absl::Status SaveHoles(Rom* rom, const std::vector& holes) { + for (int i = 0; i < kNumOverworldHoles; ++i) { + RETURN_IF_ERROR( + rom->WriteShort(kOverworldHoleArea + (i * 2), holes[i].map_id_)); + + // ZeldaFullEditor/Data/Overworld.cs::LoadHoles() adds 0x400 when loading + // (see lines ~1006-1014). SaveOWEntrances subtracts it before writing + // (Save.cs lines ~1088-1092). We replicate that here so vanilla ROMs + // receive the expected values. + uint16_t rom_map_pos = static_cast(holes[i].map_pos_ >= 0x400 + ? holes[i].map_pos_ - 0x400 + : holes[i].map_pos_); + RETURN_IF_ERROR(rom->WriteShort(kOverworldHolePos + (i * 2), rom_map_pos)); + RETURN_IF_ERROR( + rom->WriteByte(kOverworldHoleEntrance + i, holes[i].entrance_id_)); + } + + return absl::OkStatus(); +} + +} // namespace yaze::zelda3 diff --git a/src/zelda3/overworld/overworld_entrance.h b/src/zelda3/overworld/overworld_entrance.h index 1a6227a8..ad31476a 100644 --- a/src/zelda3/overworld/overworld_entrance.h +++ b/src/zelda3/overworld/overworld_entrance.h @@ -1,14 +1,18 @@ #ifndef YAZE_APP_ZELDA3_OVERWORLD_ENTRANCE_H #define YAZE_APP_ZELDA3_OVERWORLD_ENTRANCE_H +#include #include +#include +#include +#include "absl/status/status.h" +#include "absl/status/statusor.h" #include "app/rom.h" -#include "zelda3/common.h" #include "util/macro.h" +#include "zelda3/common.h" -namespace yaze { -namespace zelda3 { +namespace yaze::zelda3 { // EXPANDED to 0x78000 to 0x7A000 constexpr int kEntranceRoomEXP = 0x078000; @@ -49,6 +53,8 @@ constexpr int kOverworldEntranceMapExpanded = 0x0DB55F; constexpr int kOverworldEntrancePosExpanded = 0x0DB35F; constexpr int kOverworldEntranceEntranceIdExpanded = 0x0DB75F; +constexpr int kOverworldEntranceExpandedFlagPos = 0x0DB895; // 0xB8 + // (0x13 entries, 2 bytes each) modified(less 0x400) // map16 coordinates for each hole constexpr int kOverworldHolePos = 0xDB800; @@ -124,8 +130,17 @@ struct OverworldEntranceTileTypes { std::array high; }; +absl::StatusOr> LoadEntrances(Rom* rom); +absl::StatusOr> LoadHoles(Rom* rom); + +absl::Status SaveEntrances(Rom* rom, + const std::vector& entrances, + bool expanded_entrances); +absl::Status SaveHoles(Rom* rom, + const std::vector& holes); + inline absl::StatusOr LoadEntranceTileTypes( - Rom *rom) { + Rom* rom) { OverworldEntranceTileTypes tiletypes; for (int i = 0; i < kNumEntranceTileTypes; i++) { ASSIGN_OR_RETURN(auto value_low, @@ -138,7 +153,6 @@ inline absl::StatusOr LoadEntranceTileTypes( return tiletypes; } -} // namespace zelda3 -} // namespace yaze +} // namespace yaze::zelda3 #endif diff --git a/src/zelda3/overworld/overworld_exit.h b/src/zelda3/overworld/overworld_exit.h index 83057bb4..7616f749 100644 --- a/src/zelda3/overworld/overworld_exit.h +++ b/src/zelda3/overworld/overworld_exit.h @@ -124,6 +124,15 @@ class OverworldExit : public GameEntity { // Overworld overworld void UpdateMapProperties(uint16_t map_id) override { + // CRITICAL FIX: Sync player position from base entity coordinates + // The drag system in overworld_editor.cc updates x_/y_ (base GameEntity fields), + // but exit auto-calculation uses x_player_/y_player_ for scroll/camera computation. + // Without this sync, dragged exits retain old scroll values, causing save corruption. + // Matches ZScream ExitMode.cs:229-244 where PlayerX/PlayerY are updated during drag, + // then UpdateMapStuff recalculates scroll/camera from those values. + x_player_ = static_cast(x_); + y_player_ = static_cast(y_); + map_id_ = map_id; int large = 256; diff --git a/src/zelda3/zelda3_library.cmake b/src/zelda3/zelda3_library.cmake index fa533db3..2dc06b2e 100644 --- a/src/zelda3/zelda3_library.cmake +++ b/src/zelda3/zelda3_library.cmake @@ -10,6 +10,7 @@ set( zelda3/music/tracker.cc zelda3/overworld/overworld.cc zelda3/overworld/overworld_map.cc + zelda3/overworld/overworld_entrance.cc zelda3/palette_constants.cc zelda3/screen/dungeon_map.cc zelda3/screen/inventory.cc