refactor: enhance overworld entity properties and version handling

- Updated `UpdateMapProperties` methods across various entities (entrances, exits, items, sprites) to include an optional context parameter for improved area size detection.
- Introduced `OverworldVersionHelper` for centralized ROM version detection and feature gating, replacing scattered inline checks.
- Refactored coordinate calculations to utilize normalized map IDs, ensuring consistency and preventing data corruption.
- Enhanced exit properties to sync player positions and calculate scroll/camera values based on the overworld context.

Benefits:
- Streamlines entity property updates and improves compatibility with different ROM versions.
- Reduces code duplication and enhances maintainability by centralizing version checks.
- Ensures accurate coordinate calculations for overworld entities, improving overall functionality.
This commit is contained in:
scawful
2025-10-18 00:09:09 -04:00
parent 9f56728f80
commit 1e39df88a3
15 changed files with 546 additions and 282 deletions

View File

@@ -13,6 +13,18 @@ namespace yaze::zelda3{
/**
* @class GameEntity
* @brief Base class for all overworld and dungeon entities.
*
* Coordinate System (matches ZScream naming conventions):
* - x_, y_: World coordinates in pixels (0-4095 for overworld)
* ZScream equivalent: PlayerX/PlayerY (ExitOW.cs), GlobalX/GlobalY (EntranceOW.cs)
*
* - game_x_, game_y_: Map-local tile coordinates (0-63 for normal, 0-31 for small areas)
* ZScream equivalent: AreaX/AreaY (ExitOW.cs), GameX/GameY (items/sprites)
*
* - map_id_: Parent map ID accounting for large/wide/tall multi-area maps
* ZScream equivalent: MapID property
*
* - entity_id_: Index in entity array (for display/debugging)
*/
class GameEntity {
public:
@@ -27,11 +39,22 @@ class GameEntity {
kProperties = 7,
kDungeonSprite = 8,
} entity_type_;
// World coordinates (0-4095 for overworld)
// ZScream: PlayerX/PlayerY (exits), GlobalX/GlobalY (entrances)
int x_ = 0;
int y_ = 0;
// Map-local game coordinates (0-63 tiles, or 0-31 for small areas)
// ZScream: AreaX/AreaY (exits), GameX/GameY (items/sprites)
int game_x_ = 0;
int game_y_ = 0;
// Entity index in array (for display/debugging)
int entity_id_ = 0;
// Parent map ID (accounting for large/wide/tall areas)
// ZScream: MapID property
uint16_t map_id_ = 0;
auto set_x(int x) { x_ = x; }
@@ -40,7 +63,19 @@ class GameEntity {
GameEntity() = default;
virtual ~GameEntity() {}
virtual void UpdateMapProperties(uint16_t map_id) = 0;
/**
* @brief Update entity properties based on map position
* @param map_id Parent map ID to update to
* @param context Optional context (typically const Overworld* for coordinate calculations)
*
* ZScream equivalent: UpdateMapStuff() / UpdateMapProperties()
*
* This method recalculates derived properties like:
* - game_x_/game_y_ from world x_/y_ coordinates
* - Scroll/camera values for exits (if is_automatic_ = true)
* - Map position encoding for saving
*/
virtual void UpdateMapProperties(uint16_t map_id, const void* context = nullptr) = 0;
};
constexpr int kNumOverworldMaps = 160;

View File

@@ -84,8 +84,6 @@ class OverworldEntrance : public GameEntity {
public:
uint16_t map_pos_;
uint8_t entrance_id_;
uint8_t area_x_;
uint8_t area_y_;
bool is_hole_ = false;
bool deleted = false;
@@ -95,30 +93,35 @@ class OverworldEntrance : public GameEntity {
: map_pos_(map_pos), entrance_id_(entrance_id), is_hole_(hole) {
x_ = x;
y_ = y;
map_id_ = map_id;
map_id_ = map_id; // Store original map_id (no corruption)
entity_id_ = entrance_id;
entity_type_ = kEntrance;
int mapX = (map_id_ - ((map_id_ / 8) * 8));
int mapY = (map_id_ / 8);
area_x_ = (uint8_t)((std::abs(x - (mapX * 512)) / 16));
area_y_ = (uint8_t)((std::abs(y - (mapY * 512)) / 16));
// Use normalized map_id for coordinate calculations
uint8_t normalized_map_id = map_id % 0x40;
int mapX = normalized_map_id % 8;
int mapY = normalized_map_id / 8;
// Use base game_x_/game_y_ instead of duplicated area_x_/area_y_
game_x_ = static_cast<int>((std::abs(x - (mapX * 512)) / 16));
game_y_ = static_cast<int>((std::abs(y - (mapY * 512)) / 16));
}
void UpdateMapProperties(uint16_t map_id) override {
void UpdateMapProperties(uint16_t map_id, const void* context = nullptr) override {
(void)context; // Not used by entrances currently
map_id_ = map_id;
if (map_id_ >= 64) {
map_id_ -= 64;
}
// Use normalized map_id for calculations (don't corrupt stored value)
uint8_t normalized_map_id = map_id % 0x40;
int mapX = normalized_map_id % 8;
int mapY = normalized_map_id / 8;
int mapX = (map_id_ - ((map_id_ / 8) * 8));
int mapY = (map_id_ / 8);
// Update game coordinates from world coordinates
game_x_ = static_cast<int>((std::abs(x_ - (mapX * 512)) / 16));
game_y_ = static_cast<int>((std::abs(y_ - (mapY * 512)) / 16));
area_x_ = (uint8_t)((std::abs(x_ - (mapX * 512)) / 16));
area_y_ = (uint8_t)((std::abs(y_ - (mapY * 512)) / 16));
map_pos_ = (uint16_t)((((area_y_) << 6) | (area_x_ & 0x3F)) << 1);
// Calculate map position encoding for ROM save
map_pos_ = (uint16_t)((((game_y_) << 6) | (game_x_ & 0x3F)) << 1);
}
};
constexpr int kEntranceTileTypePtrLow = 0xDB8BF;

View File

@@ -1,11 +1,17 @@
#include "zelda3/overworld/overworld_exit.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
#include "util/macro.h"
#include "zelda3/common.h"
#include <cstdint>
#include <vector>
#include "zelda3/overworld/overworld.h"
#include "zelda3/overworld/overworld_version_helper.h"
namespace yaze::zelda3 {
@@ -13,7 +19,7 @@ absl::StatusOr<std::vector<OverworldExit>> LoadExits(Rom* rom) {
const int NumberOfOverworldExits = 0x4F;
std::vector<OverworldExit> exits;
for (int i = 0; i < NumberOfOverworldExits; i++) {
auto rom_data = rom->data();
const auto* rom_data = rom->data();
uint16_t exit_room_id;
uint16_t exit_map_id;
@@ -39,19 +45,128 @@ absl::StatusOr<std::vector<OverworldExit>> LoadExits(Rom* rom) {
OWExitDoorType1 + (i * 2), exit_door_type_2,
OWExitDoorType2 + (i * 2)));
uint16_t py = (uint16_t)((rom_data[OWExitYPlayer + (i * 2) + 1] << 8) +
rom_data[OWExitYPlayer + (i * 2)]);
uint16_t px = (uint16_t)((rom_data[OWExitXPlayer + (i * 2) + 1] << 8) +
rom_data[OWExitXPlayer + (i * 2)]);
uint16_t player_y = static_cast<uint16_t>((rom_data[OWExitYPlayer + (i * 2) + 1] << 8) +
rom_data[OWExitYPlayer + (i * 2)]);
uint16_t player_x = static_cast<uint16_t>((rom_data[OWExitXPlayer + (i * 2) + 1] << 8) +
rom_data[OWExitXPlayer + (i * 2)]);
exits.emplace_back(exit_room_id, exit_map_id, exit_vram, exit_y_scroll,
exit_x_scroll, py, px, exit_y_camera, exit_x_camera,
exit_x_scroll, player_y, player_x, exit_y_camera, exit_x_camera,
exit_scroll_mod_y, exit_scroll_mod_x, exit_door_type_1,
exit_door_type_2, (px & py) == 0xFFFF);
exit_door_type_2, (player_x & player_y) == 0xFFFF);
}
return exits;
}
void OverworldExit::UpdateMapProperties(uint16_t map_id, const void* context) {
// Sync player position from drag system
// ZScream: ExitMode.cs:229-244 updates PlayerX/PlayerY, then calls UpdateMapStuff
x_player_ = static_cast<uint16_t>(x_);
y_player_ = static_cast<uint16_t>(y_);
map_id_ = map_id;
// FIX Bug 3: Query actual area size from overworld
// ZScream: ExitOW.cs:210-212
int area_size_x = 256;
int area_size_y = 256;
if (context != nullptr) {
const auto* overworld = static_cast<const Overworld*>(context);
auto area_size = overworld->overworld_map(map_id)->area_size();
// Calculate area dimensions based on size enum
if (area_size == AreaSizeEnum::LargeArea) {
area_size_x = area_size_y = 768;
} else if (area_size == AreaSizeEnum::WideArea) {
area_size_x = 768;
area_size_y = 256;
} else if (area_size == AreaSizeEnum::TallArea) {
area_size_x = 256;
area_size_y = 768;
}
}
// FIX Bug 5: Normalize map_id FIRST before using for calculations
// ZScream: ExitOW.cs:214
uint8_t normalized_map_id = map_id % 0x40;
// Calculate map grid position
// ZScream: ExitOW.cs:216-217
int mapX = normalized_map_id % 8;
int mapY = normalized_map_id / 8;
// Calculate game coordinates (map-local tile position)
// ZScream: ExitOW.cs:219-220
game_x_ = static_cast<int>((std::abs(x_ - (mapX * 512)) / 16));
game_y_ = static_cast<int>((std::abs(y_ - (mapY * 512)) / 16));
// Clamp to valid range based on area size
// ZScream: ExitOW.cs:222-234
int max_game_x = (area_size_x == 256) ? 31 : 63;
int max_game_y = (area_size_y == 256) ? 31 : 63;
game_x_ = std::clamp(game_x_, 0, max_game_x);
game_y_ = std::clamp(game_y_, 0, max_game_y);
// Map base coordinates in world space
// ZScream: ExitOW.cs:237-238 (mapx, mapy)
int mapx = (normalized_map_id & 7) << 9; // * 512
int mapy = (normalized_map_id & 56) << 6; // (map_id / 8) * 512
if (is_automatic_) {
// Auto-calculate scroll and camera from player position
// ZScream: ExitOW.cs:256-309
// Base scroll calculation (player centered in screen)
x_scroll_ = x_player_ - 120;
y_scroll_ = y_player_ - 80;
// Clamp scroll to map bounds using actual area size
if (x_scroll_ < mapx) {
x_scroll_ = mapx;
}
if (y_scroll_ < mapy) {
y_scroll_ = mapy;
}
if (x_scroll_ > mapx + area_size_x) {
x_scroll_ = mapx + area_size_x;
}
if (y_scroll_ > mapy + area_size_y + 32) {
y_scroll_ = mapy + area_size_y + 32;
}
// Camera position (offset from player)
x_camera_ = x_player_ + 0x07;
y_camera_ = y_player_ + 0x1F;
// Clamp camera to valid range
if (x_camera_ < mapx + 127) {
x_camera_ = mapx + 127;
}
if (y_camera_ < mapy + 111) {
y_camera_ = mapy + 111;
}
if (x_camera_ > mapx + 127 + area_size_x) {
x_camera_ = mapx + 127 + area_size_x;
}
if (y_camera_ > mapy + 143 + area_size_y) {
y_camera_ = mapy + 143 + area_size_y;
}
}
// Calculate VRAM location from scroll values
// ZScream: ExitOW.cs:312-315
int16_t vram_x_scroll = static_cast<int16_t>(x_scroll_ - mapx);
int16_t vram_y_scroll = static_cast<int16_t>(y_scroll_ - mapy);
map_pos_ = static_cast<uint16_t>(((vram_y_scroll & 0xFFF0) << 3) |
((vram_x_scroll & 0xFFF0) >> 3));
}
absl::Status SaveExits(Rom* rom, const std::vector<OverworldExit>& exits) {
@@ -62,7 +177,8 @@ absl::Status SaveExits(Rom* rom, const std::vector<OverworldExit>& exits) {
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
// TODO(scawful): Implement SpecialUpdatePosition for OverworldExit
// Similar to ZScream Save.cs:1034-1039
// if (all_exits_.size() > 0x4D) {
// all_exits_[0x4D].SpecialUpdatePosition();
// }
@@ -110,4 +226,6 @@ absl::Status SaveExits(Rom* rom, const std::vector<OverworldExit>& exits) {
return absl::OkStatus();
}
} // namespace yaze::zelda3

View File

@@ -2,13 +2,20 @@
#define YAZE_APP_ZELDA3_OVERWORLD_EXIT_H
#include <cstdint>
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
#include "zelda3/common.h"
#include "zelda3/overworld/overworld_version_helper.h"
namespace yaze::zelda3 {
// Forward declaration to avoid circular dependency
class Overworld;
constexpr int kNumOverworldExits = 0x4F;
constexpr int OWExitRoomId = 0x15D8A; // 0x15E07 Credits sequences
// 105C2 Ending maps
@@ -39,173 +46,108 @@ constexpr int OWExitUnk1Whirlpool = 0x16BF5; // JP = ;016E91
constexpr int OWExitUnk2Whirlpool = 0x16C17; // JP = ;016EB3
constexpr int OWWhirlpoolPosition = 0x16CF8; // JP = ;016F94
/**
* @class OverworldExit
* @brief Represents an overworld exit that transitions from dungeon to overworld
*
* Coordinate System (inherited from GameEntity):
* - x_, y_: World pixel coordinates (ZScream: PlayerX/PlayerY)
* - game_x_, game_y_: Map-local tile coordinates (ZScream: AreaX/AreaY)
* - map_id_: Parent map ID (ZScream: MapID)
*
* Exit-Specific Properties:
* - x_player_, y_player_: Player spawn position in world (saved to ROM)
* - x_scroll_, y_scroll_: Camera scroll position
* - x_camera_, y_camera_: Camera center position
* - room_id_: Target dungeon room ID (ZScream: RoomID)
* - is_automatic_: If true, scroll/camera auto-calculated from player position (ZScream: IsAutomatic)
*
* ZScream Reference: ExitOW.cs, ExitMode.cs
*/
class OverworldExit : public GameEntity {
public:
uint16_t y_scroll_;
uint16_t x_scroll_;
uint16_t y_player_; // CRITICAL: Changed from uint8_t to uint16_t (0-4088 range)
uint16_t x_player_; // CRITICAL: Changed from uint8_t to uint16_t (0-4088 range)
uint16_t y_camera_; // Changed from uint8_t to uint16_t for consistency
uint16_t x_camera_; // Changed from uint8_t to uint16_t for consistency
uint16_t y_player_; // Player spawn Y (0-4088 range, ZScream: PlayerY)
uint16_t x_player_; // Player spawn X (0-4088 range, ZScream: PlayerX)
uint16_t y_camera_; // Camera Y position
uint16_t x_camera_; // Camera X position
uint8_t scroll_mod_y_;
uint8_t scroll_mod_x_;
uint16_t door_type_1_;
uint16_t door_type_2_;
uint16_t room_id_;
uint16_t map_pos_; // Position in the vram
uint8_t entrance_id_;
uint8_t area_x_;
uint8_t area_y_;
uint16_t room_id_; // ZScream: RoomID
uint16_t map_pos_; // VRAM location (ZScream: VRAMLocation)
bool is_hole_ = false;
bool deleted_ = false;
bool is_automatic_ = false;
bool large_map_ = false;
bool is_automatic_ = true; // FIX: Default to true (matches ZScream ExitOW.cs:101)
OverworldExit() = default;
/**
* @brief Constructor for loading exits from ROM
*
* Matches ZScream ExitOW.cs constructor (lines 129-167)
*
* CRITICAL: Does NOT modify map_id parameter (Bug 1 fix)
* Uses temporary normalized_map_id for calculations only.
*/
OverworldExit(uint16_t room_id, uint8_t map_id, uint16_t vram_location,
uint16_t y_scroll, uint16_t x_scroll, uint16_t player_y,
uint16_t player_x, uint16_t camera_y, uint16_t camera_x,
uint8_t scroll_mod_y, uint8_t scroll_mod_x,
uint16_t door_type_1, uint16_t door_type_2,
bool deleted = false)
: map_pos_(vram_location),
entrance_id_(0),
area_x_(0),
area_y_(0),
room_id_(room_id),
y_scroll_(y_scroll),
: y_scroll_(y_scroll),
x_scroll_(x_scroll),
y_player_(player_y), // No cast - preserve full 16-bit value
x_player_(player_x), // No cast - preserve full 16-bit value
y_camera_(camera_y), // No cast - preserve full 16-bit value
x_camera_(camera_x), // No cast - preserve full 16-bit value
y_player_(player_y),
x_player_(player_x),
y_camera_(camera_y),
x_camera_(camera_x),
scroll_mod_y_(scroll_mod_y),
scroll_mod_x_(scroll_mod_x),
door_type_1_(door_type_1),
door_type_2_(door_type_2),
room_id_(room_id),
map_pos_(vram_location),
is_hole_(false),
deleted_(deleted) {
// Initialize entity variables with full 16-bit coordinates
// Initialize base entity fields (ZScream: PlayerX/PlayerY → x_/y_)
x_ = player_x;
y_ = player_y;
map_id_ = map_id;
map_id_ = map_id; // FIX Bug 1: Store original map_id WITHOUT modification
entity_type_ = kExit;
int mapX = (map_id_ - ((map_id_ / 8) * 8));
int mapY = (map_id_ / 8);
// Calculate game coordinates using normalized map_id (0-63 range)
// ZScream: ExitOW.cs:159-163
uint8_t normalized_map_id = map_id % 0x40;
int mapX = normalized_map_id % 8;
int mapY = normalized_map_id / 8;
area_x_ = (uint8_t)((std::abs(x_ - (mapX * 512)) / 16));
area_y_ = (uint8_t)((std::abs(y_ - (mapY * 512)) / 16));
if (door_type_1 != 0) {
int p = (door_type_1 & 0x7FFF) >> 1;
entrance_id_ = (uint8_t)(p % 64);
area_y_ = (uint8_t)(p >> 6);
}
if (door_type_2 != 0) {
int p = (door_type_2 & 0x7FFF) >> 1;
entrance_id_ = (uint8_t)(p % 64);
area_y_ = (uint8_t)(p >> 6);
}
if (map_id_ >= 64) {
map_id_ -= 64;
}
mapX = (map_id_ - ((map_id_ / 8) * 8));
mapY = (map_id_ / 8);
area_x_ = (uint8_t)((std::abs(x_ - (mapX * 512)) / 16));
area_y_ = (uint8_t)((std::abs(y_ - (mapY * 512)) / 16));
map_pos_ = (uint16_t)((((area_y_) << 6) | (area_x_ & 0x3F)) << 1);
}
// 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<uint16_t>(x_);
y_player_ = static_cast<uint16_t>(y_);
// FIX Bug 4: Calculate game coords ONCE using correct formula
// ZScream: ExitOW.cs:162-163 (AreaX/AreaY)
game_x_ = static_cast<int>((std::abs(x_ - (mapX * 512)) / 16));
game_y_ = static_cast<int>((std::abs(y_ - (mapY * 512)) / 16));
map_id_ = map_id;
int large = 256;
int mapid = map_id;
if (map_id < 128) {
large = large_map_ ? 768 : 256;
// if (overworld.overworld_map(map_id)->Parent() != map_id) {
// mapid = overworld.overworld_map(map_id)->Parent();
// }
}
int mapX = map_id - ((map_id / 8) * 8);
int mapY = map_id / 8;
area_x_ = (uint8_t)((std::abs(x_ - (mapX * 512)) / 16));
area_y_ = (uint8_t)((std::abs(y_ - (mapY * 512)) / 16));
if (map_id >= 64) {
map_id -= 64;
}
int mapx = (map_id & 7) << 9;
int mapy = (map_id & 56) << 6;
if (is_automatic_) {
// Auto-calculate scroll and camera from player position
// Matches ZScream ExitOW.cs:256-309
x_scroll_ = x_player_ - 120;
y_scroll_ = y_player_ - 80;
if (x_scroll_ < mapx) {
x_scroll_ = mapx;
}
if (y_scroll_ < mapy) {
y_scroll_ = mapy;
}
if (x_scroll_ > mapx + large) {
x_scroll_ = mapx + large;
}
if (y_scroll_ > mapy + large + 32) {
y_scroll_ = mapy + large + 32;
}
x_camera_ = x_player_ + 0x07;
y_camera_ = y_player_ + 0x1F;
if (x_camera_ < mapx + 127) {
x_camera_ = mapx + 127;
}
if (y_camera_ < mapy + 111) {
y_camera_ = mapy + 111;
}
if (x_camera_ > mapx + 127 + large) {
x_camera_ = mapx + 127 + large;
}
if (y_camera_ > mapy + 143 + large) {
y_camera_ = mapy + 143 + large;
}
}
short vram_x_scroll = (short)(x_scroll_ - mapx);
short vram_y_scroll = (short)(y_scroll_ - mapy);
map_pos_ = (uint16_t)(((vram_y_scroll & 0xFFF0) << 3) |
((vram_x_scroll & 0xFFF0) >> 3));
// Door position calculations (used by door editor, not for coordinates)
// ZScream: ExitOW.cs:145-157
// These update DoorXEditor/DoorYEditor, NOT AreaX/AreaY
}
/**
* @brief Update exit properties when moved or map changes
* @param map_id Parent map ID to update to
* @param context Pointer to const Overworld for area size queries (can be nullptr for vanilla)
*
* Recalculates:
* - game_x_/game_y_ (map-local tile coords)
* - x_scroll_/y_scroll_ (if is_automatic_ = true)
* - x_camera_/y_camera_ (if is_automatic_ = true)
* - map_pos_ (VRAM location)
*
* ZScream equivalent: ExitOW.cs:206-318 (UpdateMapStuff)
*/
void UpdateMapProperties(uint16_t map_id, const void* context) override;
};
absl::StatusOr<std::vector<OverworldExit>> LoadExits(Rom* rom);

View File

@@ -45,27 +45,29 @@ class OverworldItem : public GameEntity {
: bg2_(bg2), id_(id), room_map_id_(room_map_id) {
x_ = x;
y_ = y;
map_id_ = room_map_id;
map_id_ = room_map_id; // Store original map_id
entity_id_ = id;
entity_type_ = kItem;
int map_x = room_map_id - ((room_map_id / 8) * 8);
int map_y = room_map_id / 8;
// Use normalized map_id for coordinate calculations
uint8_t normalized_map_id = room_map_id % 0x40;
int map_x = normalized_map_id % 8;
int map_y = normalized_map_id / 8;
game_x_ = static_cast<uint8_t>(std::abs(x - (map_x * 512)) / 16);
game_y_ = static_cast<uint8_t>(std::abs(y - (map_y * 512)) / 16);
}
void UpdateMapProperties(uint16_t room_map_id) override {
void UpdateMapProperties(uint16_t room_map_id, const void* context = nullptr) override {
(void)context; // Not used by items currently
room_map_id_ = room_map_id;
if (room_map_id_ >= 64) {
room_map_id_ -= 64;
}
int map_x = room_map_id_ - ((room_map_id_ / 8) * 8);
int map_y = room_map_id_ / 8;
// Use normalized map_id for calculations (don't corrupt stored value)
uint8_t normalized_map_id = room_map_id % 0x40;
int map_x = normalized_map_id % 8;
int map_y = normalized_map_id / 8;
// Update game coordinates from world coordinates
game_x_ = static_cast<uint8_t>(std::abs(x_ - (map_x * 512)) / 16);
game_y_ = static_cast<uint8_t>(std::abs(y_ - (map_y * 512)) / 16);
}

View File

@@ -9,6 +9,7 @@
#include "absl/status/status.h"
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "zelda3/overworld/overworld_version_helper.h"
#include "app/rom.h"
namespace yaze {
@@ -83,12 +84,7 @@ typedef struct OverworldMapTiles {
OverworldBlockset special_world; // 32 maps
} OverworldMapTiles;
enum class AreaSizeEnum {
SmallArea = 0,
LargeArea = 1,
WideArea = 2,
TallArea = 3,
};
/**
* @brief Represents a single Overworld map screen.

View File

@@ -0,0 +1,147 @@
#ifndef YAZE_ZELDA3_OVERWORLD_VERSION_HELPER_H
#define YAZE_ZELDA3_OVERWORLD_VERSION_HELPER_H
#include <cstdint>
#include "app/rom.h"
#include "zelda3/common.h"
namespace yaze::zelda3 {
enum class AreaSizeEnum {
SmallArea = 0,
LargeArea = 1,
WideArea = 2,
TallArea = 3,
};
/**
* @brief ROM version detection for overworld features
*
* Centralizes version checks to distinguish between:
* - Vanilla: No ZScream patches (uses parent system for large maps only)
* - v1: Basic custom overworld features
* - v2: Parent system + BG colors + main palettes
* - v3: Area enum system + wide/tall areas + all features
*/
enum class OverworldVersion {
kVanilla = 0, // 0xFF in ROM, no ZScream ASM applied
kZSCustomV1 = 1, // Basic features, expanded pointers
kZSCustomV2 = 2, // Parent system, BG colors, main palettes
kZSCustomV3 = 3 // Area enum, wide/tall areas, all features
};
/**
* @brief Helper for ROM version detection and feature gating
*
* Provides consistent version checking across the codebase to replace
* scattered inline checks like:
* if (asm_version >= 3 && asm_version != 0xFF) { ... }
*
* With semantic helpers:
* if (OverworldVersionHelper::SupportsAreaEnum(version)) { ... }
*/
class OverworldVersionHelper {
public:
/**
* @brief Detect ROM version from ASM marker
* @param rom ROM to check
* @return Detected overworld version
*/
static OverworldVersion GetVersion(const Rom& rom) {
uint8_t asm_version = rom.data()[OverworldCustomASMHasBeenApplied];
// 0xFF = vanilla ROM (no ZScream ASM applied)
if (asm_version == 0xFF || asm_version == 0x00) {
return OverworldVersion::kVanilla;
}
// Otherwise return version number directly
if (asm_version == 1) {
return OverworldVersion::kZSCustomV1;
} else if (asm_version == 2) {
return OverworldVersion::kZSCustomV2;
} else if (asm_version >= 3) {
return OverworldVersion::kZSCustomV3;
}
// Fallback for unknown values
return OverworldVersion::kVanilla;
}
/**
* @brief Check if ROM supports area enum system (v3+ only)
*
* Area enum system allows:
* - Wide areas (2x1 screens)
* - Tall areas (1x2 screens)
* - Direct area size queries
*
* Vanilla/v1/v2 use parent system with large_map_ flag only.
*/
static bool SupportsAreaEnum(OverworldVersion version) {
return version == OverworldVersion::kZSCustomV3;
}
/**
* @brief Check if ROM uses expanded space for overworld data
*
* v1+ ROMs use expanded pointers for:
* - Map data (0x130000+)
* - Sprite data (0x141438+)
* - Message IDs (0x1417F8+)
*/
static bool SupportsExpandedSpace(OverworldVersion version) {
return version != OverworldVersion::kVanilla;
}
/**
* @brief Check if ROM supports custom background colors (v2+)
*/
static bool SupportsCustomBGColors(OverworldVersion version) {
return version == OverworldVersion::kZSCustomV2 ||
version == OverworldVersion::kZSCustomV3;
}
/**
* @brief Check if ROM supports custom tile GFX groups (v3+)
*/
static bool SupportsCustomTileGFX(OverworldVersion version) {
return version == OverworldVersion::kZSCustomV3;
}
/**
* @brief Check if ROM supports animated GFX (v3+)
*/
static bool SupportsAnimatedGFX(OverworldVersion version) {
return version == OverworldVersion::kZSCustomV3;
}
/**
* @brief Check if ROM supports subscreen overlays (v3+)
*/
static bool SupportsSubscreenOverlay(OverworldVersion version) {
return version == OverworldVersion::kZSCustomV3;
}
/**
* @brief Get human-readable version name
*/
static const char* GetVersionName(OverworldVersion version) {
switch (version) {
case OverworldVersion::kVanilla:
return "Vanilla";
case OverworldVersion::kZSCustomV1:
return "ZSCustomOverworld v1";
case OverworldVersion::kZSCustomV2:
return "ZSCustomOverworld v2";
case OverworldVersion::kZSCustomV3:
return "ZSCustomOverworld v3";
default:
return "Unknown";
}
}
};
} // namespace yaze::zelda3
#endif // YAZE_ZELDA3_OVERWORLD_VERSION_HELPER_H

View File

@@ -6,7 +6,8 @@
namespace yaze {
namespace zelda3 {
void Sprite::UpdateMapProperties(uint16_t map_id) {
void Sprite::UpdateMapProperties(uint16_t map_id, const void* context) {
(void)context; // Not used by sprites currently
map_x_ = x_;
map_y_ = y_;
name_ = kSpriteDefaultNames[id_];

View File

@@ -331,7 +331,7 @@ class Sprite : public GameEntity {
bool mirror_x = false, bool mirror_y = false,
int sizex = 2, int sizey = 2);
void UpdateMapProperties(uint16_t map_id) override;
void UpdateMapProperties(uint16_t map_id, const void* context = nullptr) override;
void UpdateCoordinates(int map_x, int map_y);
auto preview_graphics() const { return &preview_gfx_; }