backend-infra-engineer: Release v0.3.3 snapshot

This commit is contained in:
scawful
2025-11-21 21:35:50 -05:00
parent 3d71417f62
commit 476dd1cd1c
818 changed files with 65706 additions and 35514 deletions

View File

@@ -8,11 +8,25 @@
* @namespace yaze::zelda3
* @brief Zelda 3 specific classes and functions.
*/
namespace yaze::zelda3{
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 +41,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 +65,21 @@ 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

@@ -14,7 +14,7 @@ absl::Status DungeonEditorSystem::Initialize() {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null");
}
// Initialize default dungeon settings
dungeon_settings_.dungeon_id = 0;
dungeon_settings_.name = "Default Dungeon";
@@ -27,7 +27,7 @@ absl::Status DungeonEditorSystem::Initialize() {
dungeon_settings_.has_map = true;
dungeon_settings_.has_compass = true;
dungeon_settings_.has_big_key = true;
return absl::OkStatus();
}
@@ -37,7 +37,7 @@ absl::Status DungeonEditorSystem::LoadDungeon(int dungeon_id) {
editor_state_.is_dirty = false;
editor_state_.auto_save_enabled = true;
editor_state_.last_save_time = std::chrono::steady_clock::now();
return absl::OkStatus();
}
@@ -45,7 +45,7 @@ absl::Status DungeonEditorSystem::SaveDungeon() {
// TODO: Implement actual dungeon saving to ROM
editor_state_.is_dirty = false;
editor_state_.last_save_time = std::chrono::steady_clock::now();
return absl::OkStatus();
}
@@ -71,7 +71,7 @@ absl::Status DungeonEditorSystem::SetCurrentRoom(int room_id) {
if (room_id < 0 || room_id >= NumberOfRooms) {
return absl::InvalidArgumentError("Invalid room ID");
}
editor_state_.current_room_id = room_id;
return absl::OkStatus();
}
@@ -84,12 +84,13 @@ absl::StatusOr<Room> DungeonEditorSystem::GetRoom(int room_id) {
if (room_id < 0 || room_id >= NumberOfRooms) {
return absl::InvalidArgumentError("Invalid room ID");
}
// TODO: Load room from ROM or return cached room
return Room(room_id, rom_);
}
absl::Status DungeonEditorSystem::CreateRoom(int room_id, const std::string& name) {
absl::Status DungeonEditorSystem::CreateRoom(int room_id,
const std::string& name) {
// TODO: Implement room creation
return absl::OkStatus();
}
@@ -99,7 +100,8 @@ absl::Status DungeonEditorSystem::DeleteRoom(int room_id) {
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::DuplicateRoom(int source_room_id, int target_room_id) {
absl::Status DungeonEditorSystem::DuplicateRoom(int source_room_id,
int target_room_id) {
// TODO: Implement room duplication
return absl::OkStatus();
}
@@ -121,11 +123,11 @@ absl::Status DungeonEditorSystem::AddSprite(const SpriteData& sprite_data) {
int sprite_id = GenerateSpriteId();
sprites_[sprite_id] = sprite_data;
sprites_[sprite_id].sprite_id = sprite_id;
if (sprite_changed_callback_) {
sprite_changed_callback_(sprite_id);
}
return absl::OkStatus();
}
@@ -134,73 +136,78 @@ absl::Status DungeonEditorSystem::RemoveSprite(int sprite_id) {
if (it == sprites_.end()) {
return absl::NotFoundError("Sprite not found");
}
sprites_.erase(it);
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::UpdateSprite(int sprite_id, const SpriteData& sprite_data) {
absl::Status DungeonEditorSystem::UpdateSprite(int sprite_id,
const SpriteData& sprite_data) {
auto it = sprites_.find(sprite_id);
if (it == sprites_.end()) {
return absl::NotFoundError("Sprite not found");
}
it->second = sprite_data;
it->second.sprite_id = sprite_id;
if (sprite_changed_callback_) {
sprite_changed_callback_(sprite_id);
}
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::SpriteData> DungeonEditorSystem::GetSprite(int sprite_id) {
absl::StatusOr<DungeonEditorSystem::SpriteData> DungeonEditorSystem::GetSprite(
int sprite_id) {
auto it = sprites_.find(sprite_id);
if (it == sprites_.end()) {
return absl::NotFoundError("Sprite not found");
}
return it->second;
}
absl::StatusOr<std::vector<DungeonEditorSystem::SpriteData>> DungeonEditorSystem::GetSpritesByRoom(int room_id) {
absl::StatusOr<std::vector<DungeonEditorSystem::SpriteData>>
DungeonEditorSystem::GetSpritesByRoom(int room_id) {
std::vector<SpriteData> room_sprites;
for (const auto& [id, sprite] : sprites_) {
if (sprite.x >= 0 && sprite.y >= 0) { // Simple room assignment logic
room_sprites.push_back(sprite);
}
}
return room_sprites;
}
absl::StatusOr<std::vector<DungeonEditorSystem::SpriteData>> DungeonEditorSystem::GetSpritesByType(SpriteType type) {
absl::StatusOr<std::vector<DungeonEditorSystem::SpriteData>>
DungeonEditorSystem::GetSpritesByType(SpriteType type) {
std::vector<SpriteData> typed_sprites;
for (const auto& [id, sprite] : sprites_) {
if (sprite.type == type) {
typed_sprites.push_back(sprite);
}
}
return typed_sprites;
}
absl::Status DungeonEditorSystem::MoveSprite(int sprite_id, int new_x, int new_y) {
absl::Status DungeonEditorSystem::MoveSprite(int sprite_id, int new_x,
int new_y) {
auto it = sprites_.find(sprite_id);
if (it == sprites_.end()) {
return absl::NotFoundError("Sprite not found");
}
it->second.x = new_x;
it->second.y = new_y;
if (sprite_changed_callback_) {
sprite_changed_callback_(sprite_id);
}
return absl::OkStatus();
}
@@ -209,13 +216,13 @@ absl::Status DungeonEditorSystem::SetSpriteActive(int sprite_id, bool active) {
if (it == sprites_.end()) {
return absl::NotFoundError("Sprite not found");
}
it->second.is_active = active;
if (sprite_changed_callback_) {
sprite_changed_callback_(sprite_id);
}
return absl::OkStatus();
}
@@ -224,11 +231,11 @@ absl::Status DungeonEditorSystem::AddItem(const ItemData& item_data) {
int item_id = GenerateItemId();
items_[item_id] = item_data;
items_[item_id].item_id = item_id;
if (item_changed_callback_) {
item_changed_callback_(item_id);
}
return absl::OkStatus();
}
@@ -237,57 +244,61 @@ absl::Status DungeonEditorSystem::RemoveItem(int item_id) {
if (it == items_.end()) {
return absl::NotFoundError("Item not found");
}
items_.erase(it);
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::UpdateItem(int item_id, const ItemData& item_data) {
absl::Status DungeonEditorSystem::UpdateItem(int item_id,
const ItemData& item_data) {
auto it = items_.find(item_id);
if (it == items_.end()) {
return absl::NotFoundError("Item not found");
}
it->second = item_data;
it->second.item_id = item_id;
if (item_changed_callback_) {
item_changed_callback_(item_id);
}
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::ItemData> DungeonEditorSystem::GetItem(int item_id) {
absl::StatusOr<DungeonEditorSystem::ItemData> DungeonEditorSystem::GetItem(
int item_id) {
auto it = items_.find(item_id);
if (it == items_.end()) {
return absl::NotFoundError("Item not found");
}
return it->second;
}
absl::StatusOr<std::vector<DungeonEditorSystem::ItemData>> DungeonEditorSystem::GetItemsByRoom(int room_id) {
absl::StatusOr<std::vector<DungeonEditorSystem::ItemData>>
DungeonEditorSystem::GetItemsByRoom(int room_id) {
std::vector<ItemData> room_items;
for (const auto& [id, item] : items_) {
if (item.room_id == room_id) {
room_items.push_back(item);
}
}
return room_items;
}
absl::StatusOr<std::vector<DungeonEditorSystem::ItemData>> DungeonEditorSystem::GetItemsByType(ItemType type) {
absl::StatusOr<std::vector<DungeonEditorSystem::ItemData>>
DungeonEditorSystem::GetItemsByType(ItemType type) {
std::vector<ItemData> typed_items;
for (const auto& [id, item] : items_) {
if (item.type == type) {
typed_items.push_back(item);
}
}
return typed_items;
}
@@ -296,14 +307,14 @@ absl::Status DungeonEditorSystem::MoveItem(int item_id, int new_x, int new_y) {
if (it == items_.end()) {
return absl::NotFoundError("Item not found");
}
it->second.x = new_x;
it->second.y = new_y;
if (item_changed_callback_) {
item_changed_callback_(item_id);
}
return absl::OkStatus();
}
@@ -312,26 +323,27 @@ absl::Status DungeonEditorSystem::SetItemHidden(int item_id, bool hidden) {
if (it == items_.end()) {
return absl::NotFoundError("Item not found");
}
it->second.is_hidden = hidden;
if (item_changed_callback_) {
item_changed_callback_(item_id);
}
return absl::OkStatus();
}
// Entrance/exit management
absl::Status DungeonEditorSystem::AddEntrance(const EntranceData& entrance_data) {
absl::Status DungeonEditorSystem::AddEntrance(
const EntranceData& entrance_data) {
int entrance_id = GenerateEntranceId();
entrances_[entrance_id] = entrance_data;
entrances_[entrance_id].entrance_id = entrance_id;
if (entrance_changed_callback_) {
entrance_changed_callback_(entrance_id);
}
return absl::OkStatus();
}
@@ -340,61 +352,67 @@ absl::Status DungeonEditorSystem::RemoveEntrance(int entrance_id) {
if (it == entrances_.end()) {
return absl::NotFoundError("Entrance not found");
}
entrances_.erase(it);
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::UpdateEntrance(int entrance_id, const EntranceData& entrance_data) {
absl::Status DungeonEditorSystem::UpdateEntrance(
int entrance_id, const EntranceData& entrance_data) {
auto it = entrances_.find(entrance_id);
if (it == entrances_.end()) {
return absl::NotFoundError("Entrance not found");
}
it->second = entrance_data;
it->second.entrance_id = entrance_id;
if (entrance_changed_callback_) {
entrance_changed_callback_(entrance_id);
}
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::EntranceData> DungeonEditorSystem::GetEntrance(int entrance_id) {
absl::StatusOr<DungeonEditorSystem::EntranceData>
DungeonEditorSystem::GetEntrance(int entrance_id) {
auto it = entrances_.find(entrance_id);
if (it == entrances_.end()) {
return absl::NotFoundError("Entrance not found");
}
return it->second;
}
absl::StatusOr<std::vector<DungeonEditorSystem::EntranceData>> DungeonEditorSystem::GetEntrancesByRoom(int room_id) {
absl::StatusOr<std::vector<DungeonEditorSystem::EntranceData>>
DungeonEditorSystem::GetEntrancesByRoom(int room_id) {
std::vector<EntranceData> room_entrances;
for (const auto& [id, entrance] : entrances_) {
if (entrance.source_room_id == room_id || entrance.target_room_id == room_id) {
if (entrance.source_room_id == room_id ||
entrance.target_room_id == room_id) {
room_entrances.push_back(entrance);
}
}
return room_entrances;
}
absl::StatusOr<std::vector<DungeonEditorSystem::EntranceData>> DungeonEditorSystem::GetEntrancesByType(EntranceType type) {
absl::StatusOr<std::vector<DungeonEditorSystem::EntranceData>>
DungeonEditorSystem::GetEntrancesByType(EntranceType type) {
std::vector<EntranceData> typed_entrances;
for (const auto& [id, entrance] : entrances_) {
if (entrance.type == type) {
typed_entrances.push_back(entrance);
}
}
return typed_entrances;
}
absl::Status DungeonEditorSystem::ConnectRooms(int room1_id, int room2_id, int x1, int y1, int x2, int y2) {
absl::Status DungeonEditorSystem::ConnectRooms(int room1_id, int room2_id,
int x1, int y1, int x2, int y2) {
EntranceData entrance_data;
entrance_data.source_room_id = room1_id;
entrance_data.target_room_id = room2_id;
@@ -404,7 +422,7 @@ absl::Status DungeonEditorSystem::ConnectRooms(int room1_id, int room2_id, int x
entrance_data.target_y = y2;
entrance_data.type = EntranceType::kNormal;
entrance_data.is_bidirectional = true;
return AddEntrance(entrance_data);
}
@@ -412,14 +430,16 @@ absl::Status DungeonEditorSystem::DisconnectRooms(int room1_id, int room2_id) {
// Find and remove entrance between rooms
for (auto it = entrances_.begin(); it != entrances_.end();) {
const auto& entrance = it->second;
if ((entrance.source_room_id == room1_id && entrance.target_room_id == room2_id) ||
(entrance.source_room_id == room2_id && entrance.target_room_id == room1_id)) {
if ((entrance.source_room_id == room1_id &&
entrance.target_room_id == room2_id) ||
(entrance.source_room_id == room2_id &&
entrance.target_room_id == room1_id)) {
it = entrances_.erase(it);
} else {
++it;
}
}
return absl::OkStatus();
}
@@ -428,11 +448,11 @@ absl::Status DungeonEditorSystem::AddDoor(const DoorData& door_data) {
int door_id = GenerateDoorId();
doors_[door_id] = door_data;
doors_[door_id].door_id = door_id;
if (door_changed_callback_) {
door_changed_callback_(door_id);
}
return absl::OkStatus();
}
@@ -441,45 +461,48 @@ absl::Status DungeonEditorSystem::RemoveDoor(int door_id) {
if (it == doors_.end()) {
return absl::NotFoundError("Door not found");
}
doors_.erase(it);
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::UpdateDoor(int door_id, const DoorData& door_data) {
absl::Status DungeonEditorSystem::UpdateDoor(int door_id,
const DoorData& door_data) {
auto it = doors_.find(door_id);
if (it == doors_.end()) {
return absl::NotFoundError("Door not found");
}
it->second = door_data;
it->second.door_id = door_id;
if (door_changed_callback_) {
door_changed_callback_(door_id);
}
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::DoorData> DungeonEditorSystem::GetDoor(int door_id) {
absl::StatusOr<DungeonEditorSystem::DoorData> DungeonEditorSystem::GetDoor(
int door_id) {
auto it = doors_.find(door_id);
if (it == doors_.end()) {
return absl::NotFoundError("Door not found");
}
return it->second;
}
absl::StatusOr<std::vector<DungeonEditorSystem::DoorData>> DungeonEditorSystem::GetDoorsByRoom(int room_id) {
absl::StatusOr<std::vector<DungeonEditorSystem::DoorData>>
DungeonEditorSystem::GetDoorsByRoom(int room_id) {
std::vector<DoorData> room_doors;
for (const auto& [id, door] : doors_) {
if (door.room_id == room_id) {
room_doors.push_back(door);
}
}
return room_doors;
}
@@ -488,29 +511,31 @@ absl::Status DungeonEditorSystem::SetDoorLocked(int door_id, bool locked) {
if (it == doors_.end()) {
return absl::NotFoundError("Door not found");
}
it->second.is_locked = locked;
if (door_changed_callback_) {
door_changed_callback_(door_id);
}
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::SetDoorKeyRequirement(int door_id, bool requires_key, int key_type) {
absl::Status DungeonEditorSystem::SetDoorKeyRequirement(int door_id,
bool requires_key,
int key_type) {
auto it = doors_.find(door_id);
if (it == doors_.end()) {
return absl::NotFoundError("Door not found");
}
it->second.requires_key = requires_key;
it->second.key_type = key_type;
if (door_changed_callback_) {
door_changed_callback_(door_id);
}
return absl::OkStatus();
}
@@ -519,11 +544,11 @@ absl::Status DungeonEditorSystem::AddChest(const ChestData& chest_data) {
int chest_id = GenerateChestId();
chests_[chest_id] = chest_data;
chests_[chest_id].chest_id = chest_id;
if (chest_changed_callback_) {
chest_changed_callback_(chest_id);
}
return absl::OkStatus();
}
@@ -532,61 +557,65 @@ absl::Status DungeonEditorSystem::RemoveChest(int chest_id) {
if (it == chests_.end()) {
return absl::NotFoundError("Chest not found");
}
chests_.erase(it);
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::UpdateChest(int chest_id, const ChestData& chest_data) {
absl::Status DungeonEditorSystem::UpdateChest(int chest_id,
const ChestData& chest_data) {
auto it = chests_.find(chest_id);
if (it == chests_.end()) {
return absl::NotFoundError("Chest not found");
}
it->second = chest_data;
it->second.chest_id = chest_id;
if (chest_changed_callback_) {
chest_changed_callback_(chest_id);
}
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::ChestData> DungeonEditorSystem::GetChest(int chest_id) {
absl::StatusOr<DungeonEditorSystem::ChestData> DungeonEditorSystem::GetChest(
int chest_id) {
auto it = chests_.find(chest_id);
if (it == chests_.end()) {
return absl::NotFoundError("Chest not found");
}
return it->second;
}
absl::StatusOr<std::vector<DungeonEditorSystem::ChestData>> DungeonEditorSystem::GetChestsByRoom(int room_id) {
absl::StatusOr<std::vector<DungeonEditorSystem::ChestData>>
DungeonEditorSystem::GetChestsByRoom(int room_id) {
std::vector<ChestData> room_chests;
for (const auto& [id, chest] : chests_) {
if (chest.room_id == room_id) {
room_chests.push_back(chest);
}
}
return room_chests;
}
absl::Status DungeonEditorSystem::SetChestItem(int chest_id, int item_id, int quantity) {
absl::Status DungeonEditorSystem::SetChestItem(int chest_id, int item_id,
int quantity) {
auto it = chests_.find(chest_id);
if (it == chests_.end()) {
return absl::NotFoundError("Chest not found");
}
it->second.item_id = item_id;
it->second.item_quantity = quantity;
if (chest_changed_callback_) {
chest_changed_callback_(chest_id);
}
return absl::OkStatus();
}
@@ -595,28 +624,30 @@ absl::Status DungeonEditorSystem::SetChestOpened(int chest_id, bool opened) {
if (it == chests_.end()) {
return absl::NotFoundError("Chest not found");
}
it->second.is_opened = opened;
if (chest_changed_callback_) {
chest_changed_callback_(chest_id);
}
return absl::OkStatus();
}
// Room properties and metadata
absl::Status DungeonEditorSystem::SetRoomProperties(int room_id, const RoomProperties& properties) {
absl::Status DungeonEditorSystem::SetRoomProperties(
int room_id, const RoomProperties& properties) {
room_properties_[room_id] = properties;
if (room_changed_callback_) {
room_changed_callback_(room_id);
}
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::RoomProperties> DungeonEditorSystem::GetRoomProperties(int room_id) {
absl::StatusOr<DungeonEditorSystem::RoomProperties>
DungeonEditorSystem::GetRoomProperties(int room_id) {
auto it = room_properties_.find(room_id);
if (it == room_properties_.end()) {
// Return default properties
@@ -633,17 +664,19 @@ absl::StatusOr<DungeonEditorSystem::RoomProperties> DungeonEditorSystem::GetRoom
default_properties.ambient_sound_id = 0;
return default_properties;
}
return it->second;
}
// Dungeon-wide settings
absl::Status DungeonEditorSystem::SetDungeonSettings(const DungeonSettings& settings) {
absl::Status DungeonEditorSystem::SetDungeonSettings(
const DungeonSettings& settings) {
dungeon_settings_ = settings;
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::DungeonSettings> DungeonEditorSystem::GetDungeonSettings() {
absl::StatusOr<DungeonEditorSystem::DungeonSettings>
DungeonEditorSystem::GetDungeonSettings() {
return dungeon_settings_;
}
@@ -674,7 +707,8 @@ absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderRoom(int room_id) {
return gfx::Bitmap();
}
absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderRoomPreview(int room_id, EditorMode mode) {
absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderRoomPreview(
int room_id, EditorMode mode) {
// TODO: Implement room preview rendering
return gfx::Bitmap();
}
@@ -685,22 +719,26 @@ absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderDungeonMap() {
}
// Import/Export functionality
absl::Status DungeonEditorSystem::ImportRoomFromFile(const std::string& file_path, int room_id) {
absl::Status DungeonEditorSystem::ImportRoomFromFile(
const std::string& file_path, int room_id) {
// TODO: Implement room import
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::ExportRoomToFile(int room_id, const std::string& file_path) {
absl::Status DungeonEditorSystem::ExportRoomToFile(
int room_id, const std::string& file_path) {
// TODO: Implement room export
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::ImportDungeonFromFile(const std::string& file_path) {
absl::Status DungeonEditorSystem::ImportDungeonFromFile(
const std::string& file_path) {
// TODO: Implement dungeon import
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::ExportDungeonToFile(const std::string& file_path) {
absl::Status DungeonEditorSystem::ExportDungeonToFile(
const std::string& file_path) {
// TODO: Implement dungeon export
return absl::OkStatus();
}
@@ -710,7 +748,7 @@ absl::Status DungeonEditorSystem::Undo() {
if (!CanUndo()) {
return absl::FailedPreconditionError("Nothing to undo");
}
// TODO: Implement undo functionality
return absl::OkStatus();
}
@@ -719,7 +757,7 @@ absl::Status DungeonEditorSystem::Redo() {
if (!CanRedo()) {
return absl::FailedPreconditionError("Nothing to redo");
}
// TODO: Implement redo functionality
return absl::OkStatus();
}
@@ -742,7 +780,8 @@ void DungeonEditorSystem::SetRoomChangedCallback(RoomChangedCallback callback) {
room_changed_callback_ = callback;
}
void DungeonEditorSystem::SetSpriteChangedCallback(SpriteChangedCallback callback) {
void DungeonEditorSystem::SetSpriteChangedCallback(
SpriteChangedCallback callback) {
sprite_changed_callback_ = callback;
}
@@ -750,7 +789,8 @@ void DungeonEditorSystem::SetItemChangedCallback(ItemChangedCallback callback) {
item_changed_callback_ = callback;
}
void DungeonEditorSystem::SetEntranceChangedCallback(EntranceChangedCallback callback) {
void DungeonEditorSystem::SetEntranceChangedCallback(
EntranceChangedCallback callback) {
entrance_changed_callback_ = callback;
}
@@ -758,7 +798,8 @@ void DungeonEditorSystem::SetDoorChangedCallback(DoorChangedCallback callback) {
door_changed_callback_ = callback;
}
void DungeonEditorSystem::SetChestChangedCallback(ChestChangedCallback callback) {
void DungeonEditorSystem::SetChestChangedCallback(
ChestChangedCallback callback) {
chest_changed_callback_ = callback;
}

View File

@@ -1,29 +1,29 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_DUNGEON_EDITOR_SYSTEM_H
#define YAZE_APP_ZELDA3_DUNGEON_DUNGEON_EDITOR_SYSTEM_H
#include <memory>
#include <vector>
#include <unordered_map>
#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/platform/window.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/types/snes_palette.h"
#include "app/platform/window.h"
#include "app/rom.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/sprite/sprite.h"
#include "dungeon_object_editor.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/sprite/sprite.h"
namespace yaze {
namespace zelda3 {
/**
* @brief Comprehensive dungeon editing system
*
*
* This class provides a complete dungeon editing solution including:
* - Object editing (walls, floors, decorations)
* - Sprite management (enemies, NPCs, interactive elements)
@@ -47,40 +47,40 @@ class DungeonEditorSystem {
kProperties, // Room properties mode
kGlobal // Dungeon-wide settings mode
};
// Sprite types and categories
enum class SpriteType {
kEnemy, // Hostile entities
kNPC, // Non-player characters
kInteractive, // Interactive objects
kDecoration, // Decorative sprites
kBoss, // Boss entities
kSpecial // Special purpose sprites
kEnemy, // Hostile entities
kNPC, // Non-player characters
kInteractive, // Interactive objects
kDecoration, // Decorative sprites
kBoss, // Boss entities
kSpecial // Special purpose sprites
};
// Item types
enum class ItemType {
kWeapon, // Swords, bows, etc.
kTool, // Hookshot, bombs, etc.
kKey, // Keys and key items
kHeart, // Heart containers and pieces
kRupee, // Currency
kBottle, // Bottles and contents
kUpgrade, // Capacity upgrades
kSpecial // Special items
kWeapon, // Swords, bows, etc.
kTool, // Hookshot, bombs, etc.
kKey, // Keys and key items
kHeart, // Heart containers and pieces
kRupee, // Currency
kBottle, // Bottles and contents
kUpgrade, // Capacity upgrades
kSpecial // Special items
};
// Entrance/exit types
enum class EntranceType {
kNormal, // Standard room entrance
kStairs, // Staircase connection
kDoor, // Door connection
kCave, // Cave entrance
kWarp, // Warp/teleport
kBoss, // Boss room entrance
kSpecial // Special entrance type
kNormal, // Standard room entrance
kStairs, // Staircase connection
kDoor, // Door connection
kCave, // Cave entrance
kWarp, // Warp/teleport
kBoss, // Boss room entrance
kSpecial // Special entrance type
};
// Editor state
struct EditorState {
EditorMode current_mode = EditorMode::kObjects;
@@ -89,7 +89,7 @@ class DungeonEditorSystem {
bool auto_save_enabled = true;
std::chrono::steady_clock::time_point last_save_time;
};
// Sprite editing data
struct SpriteData {
int sprite_id;
@@ -100,7 +100,7 @@ class DungeonEditorSystem {
std::unordered_map<std::string, std::string> properties;
bool is_active = true;
};
// Item placement data
struct ItemData {
int item_id;
@@ -111,7 +111,7 @@ class DungeonEditorSystem {
bool is_hidden = false;
std::unordered_map<std::string, std::string> properties;
};
// Entrance/exit data
struct EntranceData {
int entrance_id;
@@ -124,7 +124,7 @@ class DungeonEditorSystem {
bool is_bidirectional = true;
std::unordered_map<std::string, std::string> properties;
};
// Door configuration data
struct DoorData {
int door_id;
@@ -139,7 +139,7 @@ class DungeonEditorSystem {
bool is_locked = false;
std::unordered_map<std::string, std::string> properties;
};
// Chest data
struct ChestData {
int chest_id;
@@ -151,21 +151,21 @@ class DungeonEditorSystem {
bool is_opened = false;
std::unordered_map<std::string, std::string> properties;
};
explicit DungeonEditorSystem(Rom* rom);
~DungeonEditorSystem() = default;
// System initialization and management
absl::Status Initialize();
absl::Status LoadDungeon(int dungeon_id);
absl::Status SaveDungeon();
absl::Status SaveRoom(int room_id);
absl::Status ReloadRoom(int room_id);
// Mode management
void SetEditorMode(EditorMode mode);
EditorMode GetEditorMode() const;
// Room management
absl::Status SetCurrentRoom(int room_id);
int GetCurrentRoom() const;
@@ -173,41 +173,46 @@ class DungeonEditorSystem {
absl::Status CreateRoom(int room_id, const std::string& name = "");
absl::Status DeleteRoom(int room_id);
absl::Status DuplicateRoom(int source_room_id, int target_room_id);
// Object editing (delegated to DungeonObjectEditor)
std::shared_ptr<DungeonObjectEditor> GetObjectEditor();
absl::Status SetObjectEditorMode();
// Sprite management
absl::Status AddSprite(const SpriteData& sprite_data);
absl::Status RemoveSprite(int sprite_id);
absl::Status UpdateSprite(int sprite_id, const SpriteData& sprite_data);
absl::StatusOr<SpriteData> GetSprite(int sprite_id);
absl::StatusOr<std::vector<SpriteData>> GetSpritesByRoom(int room_id);
absl::StatusOr<std::vector<SpriteData>> GetSpritesByType(DungeonEditorSystem::SpriteType type);
absl::StatusOr<std::vector<SpriteData>> GetSpritesByType(
DungeonEditorSystem::SpriteType type);
absl::Status MoveSprite(int sprite_id, int new_x, int new_y);
absl::Status SetSpriteActive(int sprite_id, bool active);
// Item management
absl::Status AddItem(const ItemData& item_data);
absl::Status RemoveItem(int item_id);
absl::Status UpdateItem(int item_id, const ItemData& item_data);
absl::StatusOr<ItemData> GetItem(int item_id);
absl::StatusOr<std::vector<ItemData>> GetItemsByRoom(int room_id);
absl::StatusOr<std::vector<ItemData>> GetItemsByType(DungeonEditorSystem::ItemType type);
absl::StatusOr<std::vector<ItemData>> GetItemsByType(
DungeonEditorSystem::ItemType type);
absl::Status MoveItem(int item_id, int new_x, int new_y);
absl::Status SetItemHidden(int item_id, bool hidden);
// Entrance/exit management
absl::Status AddEntrance(const EntranceData& entrance_data);
absl::Status RemoveEntrance(int entrance_id);
absl::Status UpdateEntrance(int entrance_id, const EntranceData& entrance_data);
absl::Status UpdateEntrance(int entrance_id,
const EntranceData& entrance_data);
absl::StatusOr<EntranceData> GetEntrance(int entrance_id);
absl::StatusOr<std::vector<EntranceData>> GetEntrancesByRoom(int room_id);
absl::StatusOr<std::vector<EntranceData>> GetEntrancesByType(DungeonEditorSystem::EntranceType type);
absl::Status ConnectRooms(int room1_id, int room2_id, int x1, int y1, int x2, int y2);
absl::StatusOr<std::vector<EntranceData>> GetEntrancesByType(
DungeonEditorSystem::EntranceType type);
absl::Status ConnectRooms(int room1_id, int room2_id, int x1, int y1, int x2,
int y2);
absl::Status DisconnectRooms(int room1_id, int room2_id);
// Door management
absl::Status AddDoor(const DoorData& door_data);
absl::Status RemoveDoor(int door_id);
@@ -215,8 +220,9 @@ class DungeonEditorSystem {
absl::StatusOr<DoorData> GetDoor(int door_id);
absl::StatusOr<std::vector<DoorData>> GetDoorsByRoom(int room_id);
absl::Status SetDoorLocked(int door_id, bool locked);
absl::Status SetDoorKeyRequirement(int door_id, bool requires_key, int key_type);
absl::Status SetDoorKeyRequirement(int door_id, bool requires_key,
int key_type);
// Chest management
absl::Status AddChest(const ChestData& chest_data);
absl::Status RemoveChest(int chest_id);
@@ -225,7 +231,7 @@ class DungeonEditorSystem {
absl::StatusOr<std::vector<ChestData>> GetChestsByRoom(int room_id);
absl::Status SetChestItem(int chest_id, int item_id, int quantity);
absl::Status SetChestOpened(int chest_id, bool opened);
// Room properties and metadata
struct RoomProperties {
int room_id;
@@ -240,10 +246,10 @@ class DungeonEditorSystem {
int ambient_sound_id = 0;
std::unordered_map<std::string, std::string> custom_properties;
};
absl::Status SetRoomProperties(int room_id, const RoomProperties& properties);
absl::StatusOr<RoomProperties> GetRoomProperties(int room_id);
// Dungeon-wide settings
struct DungeonSettings {
int dungeon_id;
@@ -259,34 +265,34 @@ class DungeonEditorSystem {
bool has_big_key = true;
std::unordered_map<std::string, std::string> custom_settings;
};
absl::Status SetDungeonSettings(const DungeonSettings& settings);
absl::StatusOr<DungeonSettings> GetDungeonSettings();
// Validation and error checking
absl::Status ValidateRoom(int room_id);
absl::Status ValidateDungeon();
std::vector<std::string> GetValidationErrors(int room_id);
std::vector<std::string> GetDungeonValidationErrors();
// Rendering and preview
absl::StatusOr<gfx::Bitmap> RenderRoom(int room_id);
absl::StatusOr<gfx::Bitmap> RenderRoomPreview(int room_id, EditorMode mode);
absl::StatusOr<gfx::Bitmap> RenderDungeonMap();
// Import/Export functionality
absl::Status ImportRoomFromFile(const std::string& file_path, int room_id);
absl::Status ExportRoomToFile(int room_id, const std::string& file_path);
absl::Status ImportDungeonFromFile(const std::string& file_path);
absl::Status ExportDungeonToFile(const std::string& file_path);
// Undo/Redo system
absl::Status Undo();
absl::Status Redo();
bool CanUndo() const;
bool CanRedo() const;
void ClearHistory();
// Event callbacks
using RoomChangedCallback = std::function<void(int room_id)>;
using SpriteChangedCallback = std::function<void(int sprite_id)>;
@@ -295,8 +301,9 @@ class DungeonEditorSystem {
using DoorChangedCallback = std::function<void(int door_id)>;
using ChestChangedCallback = std::function<void(int chest_id)>;
using ModeChangedCallback = std::function<void(EditorMode mode)>;
using ValidationCallback = std::function<void(const std::vector<std::string>& errors)>;
using ValidationCallback =
std::function<void(const std::vector<std::string>& errors)>;
void SetRoomChangedCallback(RoomChangedCallback callback);
void SetSpriteChangedCallback(SpriteChangedCallback callback);
void SetItemChangedCallback(ItemChangedCallback callback);
@@ -305,13 +312,13 @@ class DungeonEditorSystem {
void SetChestChangedCallback(ChestChangedCallback callback);
void SetModeChangedCallback(ModeChangedCallback callback);
void SetValidationCallback(ValidationCallback callback);
// Getters
EditorState GetEditorState() const;
Rom* GetROM() const;
bool IsDirty() const;
bool HasUnsavedChanges() const;
// ROM management
void SetROM(Rom* rom);
@@ -323,7 +330,7 @@ class DungeonEditorSystem {
absl::Status InitializeEntranceSystem();
absl::Status InitializeDoorSystem();
absl::Status InitializeChestSystem();
// Data management
absl::Status LoadRoomData(int room_id);
absl::Status SaveRoomData(int room_id);
@@ -337,28 +344,28 @@ class DungeonEditorSystem {
absl::Status SaveDoorData();
absl::Status LoadChestData();
absl::Status SaveChestData();
// Validation helpers
absl::Status ValidateSprite(const SpriteData& sprite);
absl::Status ValidateItem(const ItemData& item);
absl::Status ValidateEntrance(const EntranceData& entrance);
absl::Status ValidateDoor(const DoorData& door);
absl::Status ValidateChest(const ChestData& chest);
// ID generation
int GenerateSpriteId();
int GenerateItemId();
int GenerateEntranceId();
int GenerateDoorId();
int GenerateChestId();
// Member variables
Rom* rom_;
std::shared_ptr<DungeonObjectEditor> object_editor_;
EditorState editor_state_;
DungeonSettings dungeon_settings_;
// Data storage
std::unordered_map<int, Room> rooms_;
std::unordered_map<int, SpriteData> sprites_;
@@ -367,14 +374,14 @@ class DungeonEditorSystem {
std::unordered_map<int, DoorData> doors_;
std::unordered_map<int, ChestData> chests_;
std::unordered_map<int, RoomProperties> room_properties_;
// ID counters
int next_sprite_id_ = 1;
int next_item_id_ = 1;
int next_entrance_id_ = 1;
int next_door_id_ = 1;
int next_chest_id_ = 1;
// Event callbacks
RoomChangedCallback room_changed_callback_;
SpriteChangedCallback sprite_changed_callback_;
@@ -384,7 +391,7 @@ class DungeonEditorSystem {
ChestChangedCallback chest_changed_callback_;
ModeChangedCallback mode_changed_callback_;
ValidationCallback validation_callback_;
// Undo/Redo system
struct UndoPoint {
EditorState state;
@@ -396,7 +403,7 @@ class DungeonEditorSystem {
std::unordered_map<int, ChestData> chests;
std::chrono::steady_clock::time_point timestamp;
};
std::vector<UndoPoint> undo_history_;
std::vector<UndoPoint> redo_history_;
static constexpr size_t kMaxUndoHistory = 100;
@@ -411,7 +418,7 @@ std::unique_ptr<DungeonEditorSystem> CreateDungeonEditorSystem(Rom* rom);
* @brief Sprite type utilities
*/
namespace SpriteTypes {
/**
* @brief Get sprite information by ID
*/
@@ -438,7 +445,7 @@ absl::StatusOr<std::string> GetSpriteCategory(int sprite_id);
* @brief Item type utilities
*/
namespace ItemTypes {
/**
* @brief Get item information by ID
*/
@@ -465,7 +472,7 @@ absl::StatusOr<std::string> GetItemCategory(int item_id);
* @brief Entrance type utilities
*/
namespace EntranceTypes {
/**
* @brief Get entrance information by ID
*/
@@ -482,7 +489,8 @@ struct EntranceInfo {
absl::StatusOr<EntranceInfo> GetEntranceInfo(int entrance_id);
std::vector<EntranceInfo> GetAllEntranceInfos();
std::vector<EntranceInfo> GetEntrancesByType(DungeonEditorSystem::EntranceType type);
std::vector<EntranceInfo> GetEntrancesByType(
DungeonEditorSystem::EntranceType type);
} // namespace EntranceTypes

File diff suppressed because it is too large Load Diff

View File

@@ -9,9 +9,9 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/platform/window.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/types/snes_palette.h"
#include "app/platform/window.h"
#include "app/rom.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_object.h"
@@ -76,7 +76,7 @@ class DungeonObjectEditor {
int auto_save_interval = 300; // 5 minutes
bool validate_objects = true;
bool show_collision_bounds = false;
// Phase 4: Visual feedback settings
bool show_selection_highlight = true;
bool show_layer_colors = true;
@@ -125,7 +125,8 @@ class DungeonObjectEditor {
bool right_button, bool shift_pressed);
absl::Status HandleMouseDrag(int start_x, int start_y, int current_x,
int current_y);
absl::Status HandleMouseRelease(int x, int y); // Phase 4: End drag operations
absl::Status HandleMouseRelease(int x,
int y); // Phase 4: End drag operations
absl::Status HandleScrollWheel(int delta, int x, int y, bool ctrl_pressed);
absl::Status HandleKeyPress(int key_code, bool ctrl_pressed,
bool shift_pressed);
@@ -152,12 +153,12 @@ class DungeonObjectEditor {
absl::StatusOr<gfx::Bitmap> RenderPreview(int x, int y);
void SetPreviewPosition(int x, int y);
void UpdatePreview();
// Phase 4: Visual feedback and GUI
void RenderSelectionHighlight(gfx::Bitmap& canvas);
void RenderLayerVisualization(gfx::Bitmap& canvas);
void RenderObjectPropertyPanel(); // ImGui panel
void RenderLayerControls(); // ImGui controls
void RenderLayerControls(); // ImGui controls
absl::Status HandleDragOperation(int current_x, int current_y);
// Undo/Redo functionality

View File

@@ -0,0 +1,37 @@
#include "zelda3/dungeon/dungeon_object_registry.h"
#include "absl/strings/str_format.h"
namespace yaze {
namespace zelda3 {
void DungeonObjectRegistry::RegisterObject(const DungeonObjectInfo& info) {
registry_[info.id] = info;
}
void DungeonObjectRegistry::RegisterVanillaRange(int16_t start_id,
int16_t end_id) {
for (int16_t id = start_id; id <= end_id; ++id) {
if (registry_.find(id) == registry_.end()) {
registry_[id] = DungeonObjectInfo{.id = id,
.name = absl::StrFormat("Object 0x%03X", id),
.is_custom = false};
}
}
}
void DungeonObjectRegistry::RegisterCustomObject(int16_t id,
const std::string& name) {
registry_[id] = DungeonObjectInfo{.id = id, .name = name, .is_custom = true};
}
const DungeonObjectInfo* DungeonObjectRegistry::Get(int16_t id) const {
auto it = registry_.find(id);
if (it == registry_.end()) {
return nullptr;
}
return &it->second;
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,38 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_OBJECT_REGISTRY_H
#define YAZE_APP_ZELDA3_DUNGEON_OBJECT_REGISTRY_H
#include <cstdint>
#include <string>
#include <unordered_map>
namespace yaze {
namespace zelda3 {
struct DungeonObjectInfo {
int16_t id = 0;
std::string name;
bool is_custom = false;
};
/**
* @brief Minimal registry for dungeon objects (vanilla or custom).
*
* This powers previews and can be extended to load custom object definitions
* from disassembly artifacts (e.g., assets/asm/usdasm outputs).
*/
class DungeonObjectRegistry {
public:
void RegisterObject(const DungeonObjectInfo& info);
void RegisterVanillaRange(int16_t start_id, int16_t end_id);
void RegisterCustomObject(int16_t id, const std::string& name);
const DungeonObjectInfo* Get(int16_t id) const;
private:
std::unordered_map<int16_t, DungeonObjectInfo> registry_;
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_OBJECT_REGISTRY_H

View File

@@ -1,8 +1,8 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#define YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#include <array> // Added for std::array
#include <cstdint>
#include <array> // Added for std::array
namespace yaze {
namespace zelda3 {
@@ -34,15 +34,15 @@ constexpr int kTileAddress = 0x001B52; // Main tile graphics
constexpr int kTileAddressFloor = 0x001B5A; // Floor tile graphics
// === Block Data ===
constexpr int kBlocksLength = 0x8896; // Word value
constexpr int kBlocksPointer1 = 0x15AFA; // Block data pointer 1
constexpr int kBlocksPointer2 = 0x15B01; // Block data pointer 2
constexpr int kBlocksPointer3 = 0x15B08; // Block data pointer 3
constexpr int kBlocksPointer4 = 0x15B0F; // Block data pointer 4
constexpr int kBlocksLength = 0x8896; // Word value
constexpr int kBlocksPointer1 = 0x15AFA; // Block data pointer 1
constexpr int kBlocksPointer2 = 0x15B01; // Block data pointer 2
constexpr int kBlocksPointer3 = 0x15B08; // Block data pointer 3
constexpr int kBlocksPointer4 = 0x15B0F; // Block data pointer 4
// === Chests ===
constexpr int kChestsLengthPointer = 0xEBF6; // Chest count pointer
constexpr int kChestsDataPointer1 = 0xEBFB; // Chest data start
constexpr int kChestsLengthPointer = 0xEBF6; // Chest count pointer
constexpr int kChestsDataPointer1 = 0xEBFB; // Chest data start
// === Torches ===
constexpr int kTorchData = 0x2736A; // JP 0x2704A
@@ -53,22 +53,23 @@ constexpr int kPitPointer = 0x394AB; // Pit/hole data
constexpr int kPitCount = 0x394A6; // Number of pits
// === Doors ===
constexpr int kDoorPointers = 0xF83C0; // Door data table
constexpr int kDoorGfxUp = 0x4D9E; // Door graphics (up)
constexpr int kDoorGfxDown = 0x4E06; // Door graphics (down)
constexpr int kDoorPointers = 0xF83C0; // Door data table
constexpr int kDoorGfxUp = 0x4D9E; // Door graphics (up)
constexpr int kDoorGfxDown = 0x4E06; // Door graphics (down)
constexpr int kDoorGfxCaveExitDown = 0x4E06; // Cave exit door
constexpr int kDoorGfxLeft = 0x4E66; // Door graphics (left)
constexpr int kDoorGfxRight = 0x4EC6; // Door graphics (right)
constexpr int kDoorPosUp = 0x197E; // Door position (up)
constexpr int kDoorPosDown = 0x1996; // Door position (down)
constexpr int kDoorPosLeft = 0x19AE; // Door position (left)
constexpr int kDoorPosRight = 0x19C6; // Door position (right)
constexpr int kDoorGfxLeft = 0x4E66; // Door graphics (left)
constexpr int kDoorGfxRight = 0x4EC6; // Door graphics (right)
constexpr int kDoorPosUp = 0x197E; // Door position (up)
constexpr int kDoorPosDown = 0x1996; // Door position (down)
constexpr int kDoorPosLeft = 0x19AE; // Door position (left)
constexpr int kDoorPosRight = 0x19C6; // Door position (right)
// === Sprites ===
constexpr int kSpritesData = 0x4D8B0; // Sprite data start
constexpr int kSpritesDataEmptyRoom = 0x4D8AE; // Empty room sprite marker
constexpr int kSpritesEndData = 0x4EC9E; // Sprite data end
constexpr int kDungeonSpritePointers = 0x090000; // Dungeon sprite pointer table
constexpr int kDungeonSpritePointers =
0x090000; // Dungeon sprite pointer table
// === Messages ===
constexpr int kMessagesIdDungeon = 0x3F61D; // Dungeon message IDs
@@ -104,4 +105,3 @@ static const std::array<int, 8> kRoomLayoutPointers = {
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,14 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
#define YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
#include <vector>
#include <unordered_map>
#include <functional>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/render/background_buffer.h"
#include "app/gfx/types/snes_tile.h"
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
#include "zelda3/dungeon/room_object.h"
@@ -17,11 +17,11 @@ namespace zelda3 {
/**
* @brief Draws dungeon objects to background buffers using game patterns
*
*
* This class interprets object IDs and draws them to BG1/BG2 buffers
* using the patterns extracted from the game's drawing routines.
* Based on ZScream's DungeonObjectData.cs and Subtype1_Draw.cs patterns.
*
*
* Architecture:
* 1. Load tile data from ROM for the object
* 2. Look up draw routine ID from object ID mapping
@@ -32,7 +32,7 @@ namespace zelda3 {
class ObjectDrawer {
public:
explicit ObjectDrawer(Rom* rom, const uint8_t* room_gfx_buffer = nullptr);
/**
* @brief Draw a room object to background buffers
* @param object The object to draw
@@ -41,11 +41,10 @@ class ObjectDrawer {
* @param palette_group Current palette group for color mapping
* @return Status of the drawing operation
*/
absl::Status DrawObject(const RoomObject& object,
gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2,
const gfx::PaletteGroup& palette_group);
absl::Status DrawObject(const RoomObject& object, gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2,
const gfx::PaletteGroup& palette_group);
/**
* @brief Draw all objects in a room
* @param objects Vector of room objects
@@ -58,14 +57,14 @@ class ObjectDrawer {
gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2,
const gfx::PaletteGroup& palette_group);
/**
* @brief Get draw routine ID for an object
* @param object_id The object ID to look up
* @return Draw routine ID (0-24) based on ZScream mapping
*/
int GetDrawRoutineId(int16_t object_id) const;
/**
* @brief Initialize draw routine registry
* Must be called before drawing objects
@@ -80,97 +79,125 @@ class ObjectDrawer {
* @param pixel_y Y pixel coordinate in bitmap
* @param tiledata Source graphics data from ROM
*/
void DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& tile_info,
int pixel_x, int pixel_y, const uint8_t* tiledata);
void DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& tile_info,
int pixel_x, int pixel_y, const uint8_t* tiledata);
private:
// Draw routine function type
using DrawRoutine = std::function<void(ObjectDrawer*, const RoomObject&, gfx::BackgroundBuffer&,
std::span<const gfx::TileInfo>)>;
using DrawRoutine = std::function<void(ObjectDrawer*, const RoomObject&,
gfx::BackgroundBuffer&,
std::span<const gfx::TileInfo>)>;
// Core draw routines (based on ZScream's subtype1_routines table)
void DrawRightwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawRightwards2x2_1to15or32(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawRightwards2x4_1to15or26(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawRightwards2x4spaced4_1to16(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4spaced4_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4spaced4_1to16_BothBG(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalAcute_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalGrave_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalAcute_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawDiagonalAcute_1to16_BothBG(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDiagonalGrave_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawDiagonalGrave_1to16_BothBG(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards1x2_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawRightwards1x2_1to16_plus2(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsHasEdge1x1_1to16_plus3(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsHasEdge1x1_1to16_plus2(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsTopCorners1x2_1to16_plus13(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsBottomCorners1x2_1to16_plus13(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void CustomDraw(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards4x4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards1x1Solid_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
void DrawRightwards1x1Solid_1to16_plus3(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDoorSwitcherer(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor4x4spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsStatue2x3spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsPillar2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor4x3spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor4x4spaced2_1to16(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsStatue2x3spaced2_1to16(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsPillar2x4spaced4_1to16(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor4x3spaced4_1to16(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDoubled2x2spaced2_1to16(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawRightwardsDecor2x2spaced12_1to16(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
// Downwards draw routines (missing implementation)
void DrawDownwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwards4x2_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsDecor4x2spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwards2x2_1to15or32(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwards4x2_1to15or26(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwards4x2_1to16_BothBG(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsDecor4x2spaced4_1to16(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsEdge1x1_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsHasEdge1x1_1to16_plus3(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsEdge1x1_1to16(const RoomObject& obj,
gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsLeftCorners2x1_1to16_plus12(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
void DrawDownwardsRightCorners2x1_1to16_plus12(
const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
// Utility methods
void WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
const gfx::TileInfo& tile_info);
bool IsValidTilePosition(int tile_x, int tile_y) const;
// Draw routine registry
std::unordered_map<int16_t, int> object_to_routine_map_;
std::vector<DrawRoutine> draw_routines_;
bool routines_initialized_ = false;
Rom* rom_;
const uint8_t* room_gfx_buffer_; // Room-specific graphics buffer (current_gfx16_)
const uint8_t*
room_gfx_buffer_; // Room-specific graphics buffer (current_gfx16_)
// Canvas dimensions in tiles (64x64 = 512x512 pixels)
static constexpr int kMaxTilesX = 64;
static constexpr int kMaxTilesY = 64;
@@ -180,4 +207,3 @@ class ObjectDrawer {
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H

View File

@@ -4,8 +4,8 @@
#include <cstring>
#include "absl/strings/str_format.h"
#include "zelda3/dungeon/room_object.h"
#include "util/log.h"
#include "zelda3/dungeon/room_object.h"
// ROM addresses for object data (PC addresses, not SNES)
static constexpr int kRoomObjectSubtype1 = 0x0A8000;
@@ -16,13 +16,14 @@ static constexpr int kRoomObjectTileAddress = 0x0AB000;
namespace yaze {
namespace zelda3 {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseObject(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseObject(
int16_t object_id) {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null");
}
int subtype = DetermineSubtype(object_id);
switch (subtype) {
case 1:
return ParseSubtype1(object_id);
@@ -36,7 +37,8 @@ absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseObject(int16_t obj
}
}
absl::StatusOr<ObjectRoutineInfo> ObjectParser::ParseObjectRoutine(int16_t object_id) {
absl::StatusOr<ObjectRoutineInfo> ObjectParser::ParseObjectRoutine(
int16_t object_id) {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null");
}
@@ -56,7 +58,8 @@ absl::StatusOr<ObjectRoutineInfo> ObjectParser::ParseObjectRoutine(int16_t objec
return routine_info;
}
absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(int16_t object_id) {
absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(
int16_t object_id) {
ObjectSubtypeInfo info;
info.subtype = DetermineSubtype(object_id);
@@ -65,7 +68,7 @@ absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(int16_t object_
int index = object_id & 0xFF;
info.subtype_ptr = kRoomObjectSubtype1 + (index * 2);
info.routine_ptr = kRoomObjectSubtype1 + 0x200 + (index * 2);
info.max_tile_count = 8; // Most subtype 1 objects use 8 tiles
info.max_tile_count = 8; // Most subtype 1 objects use 8 tiles
break;
}
case 2: {
@@ -90,16 +93,17 @@ absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(int16_t object_
return info;
}
absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(int16_t object_id, uint8_t size_byte) {
absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(
int16_t object_id, uint8_t size_byte) {
ObjectSizeInfo info;
// Extract size bits (0-3 for X, 4-7 for Y)
int size_x = size_byte & 0x03;
int size_y = (size_byte >> 2) & 0x03;
info.width_tiles = (size_x + 1) * 2; // Convert to tile count
info.height_tiles = (size_y + 1) * 2;
// Determine orientation based on object ID and size
// This is a heuristic based on the object naming patterns
if (object_id >= 0x80 && object_id <= 0xFF) {
@@ -109,106 +113,116 @@ absl::StatusOr<ObjectSizeInfo> ObjectParser::ParseObjectSize(int16_t object_id,
// Objects 0x00-0x7F are typically horizontal
info.is_horizontal = true;
}
// Determine if object is repeatable
info.is_repeatable = (size_byte != 0);
info.repeat_count = size_byte == 0 ? 32 : size_byte;
return info;
}
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype1(
int16_t object_id) {
int index = object_id & 0xFF;
int tile_ptr = kRoomObjectSubtype1 + (index * 2);
if (tile_ptr + 1 >= (int)rom_->size()) {
return absl::OutOfRangeError(
absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
}
// Read tile data pointer
uint8_t low = rom_->data()[tile_ptr];
uint8_t high = rom_->data()[tile_ptr + 1];
int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
// Read 8 tiles (most subtype 1 objects use 8 tiles)
return ReadTileData(tile_data_ptr, 8);
}
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype2(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype2(
int16_t object_id) {
int index = object_id & 0x7F;
int tile_ptr = kRoomObjectSubtype2 + (index * 2);
if (tile_ptr + 1 >= (int)rom_->size()) {
return absl::OutOfRangeError(
absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
}
// Read tile data pointer
uint8_t low = rom_->data()[tile_ptr];
uint8_t high = rom_->data()[tile_ptr + 1];
int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
// Read 8 tiles
return ReadTileData(tile_data_ptr, 8);
}
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype3(int16_t object_id) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ParseSubtype3(
int16_t object_id) {
int index = object_id & 0xFF;
int tile_ptr = kRoomObjectSubtype3 + (index * 2);
if (tile_ptr + 1 >= (int)rom_->size()) {
return absl::OutOfRangeError(
absl::StrFormat("Tile pointer out of range: %#06x", tile_ptr));
}
// Read tile data pointer
uint8_t low = rom_->data()[tile_ptr];
uint8_t high = rom_->data()[tile_ptr + 1];
int tile_data_ptr = kRoomObjectTileAddress + ((high << 8) | low);
// Read 8 tiles
return ReadTileData(tile_data_ptr, 8);
}
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ReadTileData(int address, int tile_count) {
absl::StatusOr<std::vector<gfx::TileInfo>> ObjectParser::ReadTileData(
int address, int tile_count) {
// Each tile is stored as a 16-bit word (2 bytes), not 8 bytes!
// ZScream: tiles.Add(new Tile(ROM.DATA[pos + ((i * 2))], ROM.DATA[pos + ((i * 2)) + 1]));
// ZScream: tiles.Add(new Tile(ROM.DATA[pos + ((i * 2))], ROM.DATA[pos + ((i *
// 2)) + 1]));
if (address < 0 || address + (tile_count * 2) >= (int)rom_->size()) {
return absl::OutOfRangeError(
absl::StrFormat("Tile data address out of range: %#06x", address));
}
std::vector<gfx::TileInfo> tiles;
tiles.reserve(tile_count);
// DEBUG: Log first tile read
static int debug_read_count = 0;
bool should_log = (debug_read_count < 3);
for (int i = 0; i < tile_count; i++) {
int tile_offset = address + (i * 2); // 2 bytes per tile word
// Read 1 word (2 bytes) per tile - this is the SNES tile format
uint16_t tile_word = rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8);
uint16_t tile_word =
rom_->data()[tile_offset] | (rom_->data()[tile_offset + 1] << 8);
auto tile_info = gfx::WordToTileInfo(tile_word);
tiles.push_back(tile_info);
// DEBUG: Log first few tiles
if (should_log && i < 4) {
printf("[ObjectParser] ReadTile[%d]: addr=0x%06X word=0x%04X → id=0x%03X pal=%d mirror=(h:%d,v:%d)\n",
i, tile_offset, tile_word, tile_info.id_, tile_info.palette_,
tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
printf(
"[ObjectParser] ReadTile[%d]: addr=0x%06X word=0x%04X → id=0x%03X "
"pal=%d mirror=(h:%d,v:%d)\n",
i, tile_offset, tile_word, tile_info.id_, tile_info.palette_,
tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
}
}
if (should_log) {
printf("[ObjectParser] ReadTileData: addr=0x%06X count=%d → loaded %zu tiles\n",
address, tile_count, tiles.size());
printf(
"[ObjectParser] ReadTileData: addr=0x%06X count=%d → loaded %zu "
"tiles\n",
address, tile_count, tiles.size());
debug_read_count++;
}
return tiles;
}
@@ -224,169 +238,146 @@ int ObjectParser::DetermineSubtype(int16_t object_id) const {
ObjectDrawInfo ObjectParser::GetObjectDrawInfo(int16_t object_id) const {
ObjectDrawInfo info;
// Map object ID to draw routine based on ZScream's subtype1_routines table
// This is based on the DungeonObjectData.cs mapping from ZScream
if (object_id == 0x00) {
info.draw_routine_id = 0; // RoomDraw_Rightwards2x2_1to15or32
info.routine_name = "Rightwards2x2_1to15or32";
info.tile_count = 4;
info.is_horizontal = true;
}
else if (object_id >= 0x01 && object_id <= 0x02) {
} else if (object_id >= 0x01 && object_id <= 0x02) {
info.draw_routine_id = 1; // RoomDraw_Rightwards2x4_1to15or26
info.routine_name = "Rightwards2x4_1to15or26";
info.tile_count = 8;
info.is_horizontal = true;
}
else if (object_id >= 0x03 && object_id <= 0x04) {
} else if (object_id >= 0x03 && object_id <= 0x04) {
info.draw_routine_id = 2; // RoomDraw_Rightwards2x4spaced4_1to16
info.routine_name = "Rightwards2x4spaced4_1to16";
info.tile_count = 8;
info.is_horizontal = true;
}
else if (object_id >= 0x05 && object_id <= 0x06) {
} else if (object_id >= 0x05 && object_id <= 0x06) {
info.draw_routine_id = 3; // RoomDraw_Rightwards2x4spaced4_1to16_BothBG
info.routine_name = "Rightwards2x4spaced4_1to16_BothBG";
info.tile_count = 8;
info.is_horizontal = true;
info.both_layers = true;
}
else if (object_id >= 0x07 && object_id <= 0x08) {
} else if (object_id >= 0x07 && object_id <= 0x08) {
info.draw_routine_id = 4; // RoomDraw_Rightwards2x2_1to16
info.routine_name = "Rightwards2x2_1to16";
info.tile_count = 4;
info.is_horizontal = true;
}
else if (object_id == 0x09) {
} else if (object_id == 0x09) {
info.draw_routine_id = 5; // RoomDraw_DiagonalAcute_1to16
info.routine_name = "DiagonalAcute_1to16";
info.tile_count = 5;
info.is_horizontal = false;
}
else if (object_id >= 0x0A && object_id <= 0x0B) {
} else if (object_id >= 0x0A && object_id <= 0x0B) {
info.draw_routine_id = 6; // RoomDraw_DiagonalGrave_1to16
info.routine_name = "DiagonalGrave_1to16";
info.tile_count = 5;
info.is_horizontal = false;
}
else if (object_id >= 0x15 && object_id <= 0x1F) {
} else if (object_id >= 0x15 && object_id <= 0x1F) {
info.draw_routine_id = 7; // RoomDraw_DiagonalAcute_1to16_BothBG
info.routine_name = "DiagonalAcute_1to16_BothBG";
info.tile_count = 5;
info.is_horizontal = false;
info.both_layers = true;
}
else if (object_id >= 0x16 && object_id <= 0x20) {
} else if (object_id >= 0x16 && object_id <= 0x20) {
info.draw_routine_id = 8; // RoomDraw_DiagonalGrave_1to16_BothBG
info.routine_name = "DiagonalGrave_1to16_BothBG";
info.tile_count = 5;
info.is_horizontal = false;
info.both_layers = true;
}
else if (object_id == 0x21) {
} else if (object_id == 0x21) {
info.draw_routine_id = 9; // RoomDraw_Rightwards1x2_1to16_plus2
info.routine_name = "Rightwards1x2_1to16_plus2";
info.tile_count = 2;
info.is_horizontal = true;
}
else if (object_id == 0x22) {
info.draw_routine_id = 10; // RoomDraw_RightwardsHasEdge1x1_1to16_plus3
} else if (object_id == 0x22) {
info.draw_routine_id = 10; // RoomDraw_RightwardsHasEdge1x1_1to16_plus3
info.routine_name = "RightwardsHasEdge1x1_1to16_plus3";
info.tile_count = 1;
info.is_horizontal = true;
}
else if (object_id >= 0x23 && object_id <= 0x2E) {
info.draw_routine_id = 11; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2
} else if (object_id >= 0x23 && object_id <= 0x2E) {
info.draw_routine_id = 11; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2
info.routine_name = "RightwardsHasEdge1x1_1to16_plus2";
info.tile_count = 1;
info.is_horizontal = true;
}
else if (object_id == 0x2F) {
info.draw_routine_id = 12; // RoomDraw_RightwardsTopCorners1x2_1to16_plus13
} else if (object_id == 0x2F) {
info.draw_routine_id = 12; // RoomDraw_RightwardsTopCorners1x2_1to16_plus13
info.routine_name = "RightwardsTopCorners1x2_1to16_plus13";
info.tile_count = 2;
info.is_horizontal = true;
}
else if (object_id == 0x30) {
info.draw_routine_id = 13; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
} else if (object_id == 0x30) {
info.draw_routine_id =
13; // RoomDraw_RightwardsBottomCorners1x2_1to16_plus13
info.routine_name = "RightwardsBottomCorners1x2_1to16_plus13";
info.tile_count = 2;
info.is_horizontal = true;
}
else if (object_id >= 0x31 && object_id <= 0x32) {
info.draw_routine_id = 14; // CustomDraw
} else if (object_id >= 0x31 && object_id <= 0x32) {
info.draw_routine_id = 14; // CustomDraw
info.routine_name = "CustomDraw";
info.tile_count = 1;
}
else if (object_id == 0x33) {
info.draw_routine_id = 15; // RoomDraw_Rightwards4x4_1to16
} else if (object_id == 0x33) {
info.draw_routine_id = 15; // RoomDraw_Rightwards4x4_1to16
info.routine_name = "Rightwards4x4_1to16";
info.tile_count = 16;
info.is_horizontal = true;
}
else if (object_id == 0x34) {
info.draw_routine_id = 16; // RoomDraw_Rightwards1x1Solid_1to16_plus3
} else if (object_id == 0x34) {
info.draw_routine_id = 16; // RoomDraw_Rightwards1x1Solid_1to16_plus3
info.routine_name = "Rightwards1x1Solid_1to16_plus3";
info.tile_count = 1;
info.is_horizontal = true;
}
else if (object_id == 0x35) {
info.draw_routine_id = 17; // RoomDraw_DoorSwitcherer
} else if (object_id == 0x35) {
info.draw_routine_id = 17; // RoomDraw_DoorSwitcherer
info.routine_name = "DoorSwitcherer";
info.tile_count = 1;
}
else if (object_id >= 0x36 && object_id <= 0x37) {
info.draw_routine_id = 18; // RoomDraw_RightwardsDecor4x4spaced2_1to16
} else if (object_id >= 0x36 && object_id <= 0x37) {
info.draw_routine_id = 18; // RoomDraw_RightwardsDecor4x4spaced2_1to16
info.routine_name = "RightwardsDecor4x4spaced2_1to16";
info.tile_count = 16;
info.is_horizontal = true;
}
else if (object_id == 0x38) {
info.draw_routine_id = 19; // RoomDraw_RightwardsStatue2x3spaced2_1to16
} else if (object_id == 0x38) {
info.draw_routine_id = 19; // RoomDraw_RightwardsStatue2x3spaced2_1to16
info.routine_name = "RightwardsStatue2x3spaced2_1to16";
info.tile_count = 6;
info.is_horizontal = true;
}
else if (object_id == 0x39 || object_id == 0x3D) {
info.draw_routine_id = 20; // RoomDraw_RightwardsPillar2x4spaced4_1to16
} else if (object_id == 0x39 || object_id == 0x3D) {
info.draw_routine_id = 20; // RoomDraw_RightwardsPillar2x4spaced4_1to16
info.routine_name = "RightwardsPillar2x4spaced4_1to16";
info.tile_count = 8;
info.is_horizontal = true;
}
else if (object_id >= 0x3A && object_id <= 0x3B) {
info.draw_routine_id = 21; // RoomDraw_RightwardsDecor4x3spaced4_1to16
} else if (object_id >= 0x3A && object_id <= 0x3B) {
info.draw_routine_id = 21; // RoomDraw_RightwardsDecor4x3spaced4_1to16
info.routine_name = "RightwardsDecor4x3spaced4_1to16";
info.tile_count = 12;
info.is_horizontal = true;
}
else if (object_id == 0x3C) {
info.draw_routine_id = 22; // RoomDraw_RightwardsDoubled2x2spaced2_1to16
} else if (object_id == 0x3C) {
info.draw_routine_id = 22; // RoomDraw_RightwardsDoubled2x2spaced2_1to16
info.routine_name = "RightwardsDoubled2x2spaced2_1to16";
info.tile_count = 8;
info.is_horizontal = true;
}
else if (object_id == 0x3E) {
info.draw_routine_id = 23; // RoomDraw_RightwardsDecor2x2spaced12_1to16
} else if (object_id == 0x3E) {
info.draw_routine_id = 23; // RoomDraw_RightwardsDecor2x2spaced12_1to16
info.routine_name = "RightwardsDecor2x2spaced12_1to16";
info.tile_count = 4;
info.is_horizontal = true;
}
else if (object_id >= 0x3F && object_id <= 0x40) {
info.draw_routine_id = 24; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2 (variant)
} else if (object_id >= 0x3F && object_id <= 0x40) {
info.draw_routine_id =
24; // RoomDraw_RightwardsHasEdge1x1_1to16_plus2 (variant)
info.routine_name = "RightwardsHasEdge1x1_1to16_plus2_variant";
info.tile_count = 1;
info.is_horizontal = true;
}
else {
} else {
// Default to simple 1x1 solid for unmapped objects
info.draw_routine_id = 16; // Use solid block routine
info.draw_routine_id = 16; // Use solid block routine
info.routine_name = "DefaultSolid";
info.tile_count = 1;
info.is_horizontal = true;
}
return info;
}

View File

@@ -69,8 +69,8 @@ struct ObjectDrawInfo {
int tile_count; // How many tiles this object has
bool is_horizontal; // Orientation
bool is_vertical;
bool both_layers; // Draw to both BG1 and BG2
std::string routine_name; // Human-readable routine name
bool both_layers; // Draw to both BG1 and BG2
std::string routine_name; // Human-readable routine name
ObjectDrawInfo()
: draw_routine_id(0),
@@ -129,7 +129,7 @@ class ObjectParser {
* @brief Determine object subtype from ID
*/
int DetermineSubtype(int16_t object_id) const;
/**
* @brief Get draw routine information for an object
* @param object_id The object ID to look up
@@ -161,7 +161,7 @@ class ObjectParser {
* @return StatusOr containing tile data
*/
absl::StatusOr<std::vector<gfx::TileInfo>> ReadTileData(int address,
int tile_count);
int tile_count);
Rom* rom_;
};

View File

@@ -10,15 +10,15 @@
#include "app/gfx/types/snes_palette.h"
#include "app/rom.h"
#include "app/snes.h"
#include "util/log.h"
#include "zelda3/dungeon/object_drawer.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/sprite/sprite.h"
#include "util/log.h"
namespace yaze {
namespace zelda3 {
RoomSize CalculateRoomSize(Rom *rom, int room_id) {
RoomSize CalculateRoomSize(Rom* rom, int room_id) {
// Calculate the size of the room based on how many objects are used per room
// Some notes from hacker Zarby89
// vanilla rooms are using in average ~0x80 bytes
@@ -72,7 +72,7 @@ RoomSize CalculateRoomSize(Rom *rom, int room_id) {
return room_size;
}
Room LoadRoomFromRom(Rom *rom, int room_id) {
Room LoadRoomFromRom(Rom* rom, int room_id) {
Room room(room_id, rom);
int header_pointer = (rom->data()[kRoomHeaderPointer + 2] << 16) +
@@ -193,8 +193,8 @@ Room LoadRoomFromRom(Rom *rom, int room_id) {
}
void Room::LoadRoomGraphics(uint8_t entrance_blockset) {
const auto &room_gfx = rom()->room_blockset_ids;
const auto &sprite_gfx = rom()->spriteset_ids;
const auto& room_gfx = rom()->room_blockset_ids;
const auto& sprite_gfx = rom()->spriteset_ids;
for (int i = 0; i < 8; i++) {
blocks_[i] = rom()->main_blockset_ids[blockset][i];
@@ -230,15 +230,16 @@ void Room::CopyRoomGraphicsToBuffer() {
printf("[CopyRoomGraphicsToBuffer] ROM not loaded\n");
return;
}
auto gfx_buffer_data = rom()->mutable_graphics_buffer();
if (!gfx_buffer_data || gfx_buffer_data->empty()) {
printf("[CopyRoomGraphicsToBuffer] Graphics buffer is null or empty\n");
return;
}
printf("[CopyRoomGraphicsToBuffer] Room %d: Copying graphics from blocks\n", room_id_);
printf("[CopyRoomGraphicsToBuffer] Room %d: Copying graphics from blocks\n",
room_id_);
// Copy room graphics to buffer
int sheet_pos = 0;
int bytes_copied = 0;
@@ -248,21 +249,23 @@ void Room::CopyRoomGraphicsToBuffer() {
sheet_pos += kGfxBufferRoomOffset;
continue;
}
int data = 0;
int block_offset = blocks_[i] * kGfxBufferRoomOffset;
// Validate block_offset bounds
if (block_offset < 0 || block_offset >= static_cast<int>(gfx_buffer_data->size())) {
if (block_offset < 0 ||
block_offset >= static_cast<int>(gfx_buffer_data->size())) {
sheet_pos += kGfxBufferRoomOffset;
continue;
}
while (data < kGfxBufferRoomOffset) {
int buffer_index = data + block_offset;
if (buffer_index >= 0 && buffer_index < static_cast<int>(gfx_buffer_data->size())) {
if (buffer_index >= 0 &&
buffer_index < static_cast<int>(gfx_buffer_data->size())) {
uint8_t map_byte = (*gfx_buffer_data)[buffer_index];
// NOTE: DO NOT apply sprite offset here!
// NOTE: DO NOT apply sprite offset here!
// current_gfx16_ holds pixel data (palette indices 0-7), not tile IDs.
// The 0x88 offset is for tile IDs in tilemaps, not raw pixel data.
// if (i < 4) {
@@ -271,9 +274,11 @@ void Room::CopyRoomGraphicsToBuffer() {
// Validate current_gfx16_ access
int gfx_index = data + sheet_pos;
if (gfx_index >= 0 && gfx_index < static_cast<int>(sizeof(current_gfx16_))) {
if (gfx_index >= 0 &&
gfx_index < static_cast<int>(sizeof(current_gfx16_))) {
current_gfx16_[gfx_index] = map_byte;
if (map_byte != 0) bytes_copied++;
if (map_byte != 0)
bytes_copied++;
}
}
data++;
@@ -282,7 +287,10 @@ void Room::CopyRoomGraphicsToBuffer() {
sheet_pos += kGfxBufferRoomOffset;
}
printf("[CopyRoomGraphicsToBuffer] Room %d: Copied %d non-zero bytes to current_gfx16_\n", room_id_, bytes_copied);
printf(
"[CopyRoomGraphicsToBuffer] Room %d: Copied %d non-zero bytes to "
"current_gfx16_\n",
room_id_, bytes_copied);
LoadAnimatedGraphics();
}
@@ -293,7 +301,8 @@ void Room::RenderRoomGraphics() {
// Check if graphics properties changed
if (cached_blockset_ != blockset || cached_spriteset_ != spriteset ||
cached_palette_ != palette || cached_layout_ != layout ||
cached_floor1_graphics_ != floor1_graphics_ || cached_floor2_graphics_ != floor2_graphics_) {
cached_floor1_graphics_ != floor1_graphics_ ||
cached_floor2_graphics_ != floor2_graphics_) {
cached_blockset_ = blockset;
cached_spriteset_ = spriteset;
cached_palette_ = palette;
@@ -315,17 +324,21 @@ void Room::RenderRoomGraphics() {
}
// If nothing changed and textures exist, skip rendering
if (!properties_changed && !graphics_dirty_ && !objects_dirty_ && !layout_dirty_ && !textures_dirty_) {
if (!properties_changed && !graphics_dirty_ && !objects_dirty_ &&
!layout_dirty_ && !textures_dirty_) {
auto& bg1_bmp = bg1_buffer_.bitmap();
auto& bg2_bmp = bg2_buffer_.bitmap();
if (bg1_bmp.texture() && bg2_bmp.texture()) {
LOG_DEBUG("[RenderRoomGraphics]", "Room %d: No changes detected, skipping render", room_id_);
LOG_DEBUG("[RenderRoomGraphics]",
"Room %d: No changes detected, skipping render", room_id_);
return;
}
}
LOG_DEBUG("[RenderRoomGraphics]", "Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
room_id_, graphics_dirty_, objects_dirty_, layout_dirty_, textures_dirty_);
LOG_DEBUG("[RenderRoomGraphics]",
"Room %d: Rendering graphics (dirty_flags: g=%d o=%d l=%d t=%d)",
room_id_, graphics_dirty_, objects_dirty_, layout_dirty_,
textures_dirty_);
// STEP 0: Load graphics if needed
if (graphics_dirty_) {
@@ -340,57 +353,68 @@ void Room::RenderRoomGraphics() {
}
// Debug: Log floor graphics values
LOG_DEBUG("[RenderRoomGraphics]", "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
room_id_, floor1_graphics_, floor2_graphics_, blocks_.size());
LOG_DEBUG("[RenderRoomGraphics]",
"Room %d: floor1=%d, floor2=%d, blocks_size=%zu", room_id_,
floor1_graphics_, floor2_graphics_, blocks_.size());
// LoadGraphicsSheetsIntoArena() removed - using per-room graphics instead
// Arena sheets are optional and not needed for room rendering
// STEP 2: Draw floor tiles to bitmaps (base layer) - if graphics changed OR bitmaps not created yet
// STEP 2: Draw floor tiles to bitmaps (base layer) - if graphics changed OR
// bitmaps not created yet
bool need_floor_draw = graphics_dirty_;
auto& bg1_bmp = bg1_buffer_.bitmap();
auto& bg2_bmp = bg2_buffer_.bitmap();
// Always draw floor if bitmaps don't exist yet (first time rendering)
if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() || bg2_bmp.width() == 0) {
if (!bg1_bmp.is_active() || bg1_bmp.width() == 0 || !bg2_bmp.is_active() ||
bg2_bmp.width() == 0) {
need_floor_draw = true;
LOG_DEBUG("[RenderRoomGraphics]", "Room %d: Bitmaps not created yet, forcing floor draw", room_id_);
LOG_DEBUG("[RenderRoomGraphics]",
"Room %d: Bitmaps not created yet, forcing floor draw", room_id_);
}
if (need_floor_draw) {
bg1_buffer_.DrawFloor(rom()->vector(), tile_address,
tile_address_floor, floor1_graphics_);
bg2_buffer_.DrawFloor(rom()->vector(), tile_address,
tile_address_floor, floor2_graphics_);
bg1_buffer_.DrawFloor(rom()->vector(), tile_address, tile_address_floor,
floor1_graphics_);
bg2_buffer_.DrawFloor(rom()->vector(), tile_address, tile_address_floor,
floor2_graphics_);
}
// STEP 3: Draw background tiles (walls/structure) to buffers - if graphics changed OR bitmaps just created
// STEP 3: Draw background tiles (walls/structure) to buffers - if graphics
// changed OR bitmaps just created
bool need_bg_draw = graphics_dirty_ || need_floor_draw;
if (need_bg_draw) {
bg1_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
bg2_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
}
// Get and apply palette BEFORE rendering objects (so objects use correct colors)
// Get and apply palette BEFORE rendering objects (so objects use correct
// colors)
auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main;
int num_palettes = dungeon_pal_group.size();
// Use palette indirection table lookup (same as dungeon_canvas_viewer.cc line 854)
// Use palette indirection table lookup (same as dungeon_canvas_viewer.cc line
// 854)
int palette_id = palette; // Default fallback
if (palette < rom()->paletteset_ids.size() && !rom()->paletteset_ids[palette].empty()) {
if (palette < rom()->paletteset_ids.size() &&
!rom()->paletteset_ids[palette].empty()) {
auto dungeon_palette_ptr = rom()->paletteset_ids[palette][0];
auto palette_word = rom()->ReadWord(0xDEC4B + dungeon_palette_ptr);
if (palette_word.ok()) {
palette_id = palette_word.value() / 180; // Divide by 180 to get group index
LOG_DEBUG("[RenderRoomGraphics]", "Palette lookup: byte=0x%02X → group_id=%d", palette, palette_id);
palette_id =
palette_word.value() / 180; // Divide by 180 to get group index
LOG_DEBUG("[RenderRoomGraphics]",
"Palette lookup: byte=0x%02X → group_id=%d", palette,
palette_id);
}
}
// Clamp to valid range
if (palette_id < 0 || palette_id >= num_palettes) {
palette_id = palette_id % num_palettes;
}
auto bg1_palette = dungeon_pal_group[palette_id];
if (bg1_palette.size() > 0) {
@@ -398,24 +422,28 @@ void Room::RenderRoomGraphics() {
bg1_bmp.SetPalette(bg1_palette);
bg2_bmp.SetPalette(bg1_palette);
}
// Render objects ON TOP of background tiles (AFTER palette is set)
// ObjectDrawer will write indexed pixel data that uses the palette we just set
// ObjectDrawer will write indexed pixel data that uses the palette we just
// set
RenderObjectsToBackground();
// PERFORMANCE OPTIMIZATION: Queue texture commands but DON'T process immediately
// This allows multiple rooms to batch their texture updates together
// The dungeon_canvas_viewer.cc:552 will process all queued textures once per frame
// PERFORMANCE OPTIMIZATION: Queue texture commands but DON'T process
// immediately This allows multiple rooms to batch their texture updates
// together The dungeon_canvas_viewer.cc:552 will process all queued textures
// once per frame
if (bg1_bmp.texture()) {
// Texture exists - UPDATE it with new object data
LOG_DEBUG("[RenderRoomGraphics]", "Queueing UPDATE for existing textures (deferred)");
LOG_DEBUG("[RenderRoomGraphics]",
"Queueing UPDATE for existing textures (deferred)");
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp);
} else {
// No texture yet - CREATE it
LOG_DEBUG("[RenderRoomGraphics]", "Queueing CREATE for new textures (deferred)");
LOG_DEBUG("[RenderRoomGraphics]",
"Queueing CREATE for new textures (deferred)");
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand(
@@ -429,11 +457,13 @@ void Room::RenderRoomGraphics() {
// Processing happens once per frame in DrawDungeonCanvas()
// This dramatically improves performance when multiple rooms are open
// gfx::Arena::Get().ProcessTextureQueue(nullptr); // OLD: Caused slowdown!
LOG_DEBUG("[RenderRoomGraphics]", "Texture commands queued for batch processing");
LOG_DEBUG("[RenderRoomGraphics]",
"Texture commands queued for batch processing");
}
void Room::LoadLayoutTilesToBuffer() {
LOG_DEBUG("RenderRoomGraphics", "LoadLayoutTilesToBuffer START for room %d", room_id_);
LOG_DEBUG("RenderRoomGraphics", "LoadLayoutTilesToBuffer START for room %d",
room_id_);
if (!rom_ || !rom_->is_loaded()) {
LOG_DEBUG("RenderRoomGraphics", "ROM not loaded, aborting");
@@ -441,7 +471,8 @@ void Room::LoadLayoutTilesToBuffer() {
}
const auto& layout_objects = layout_.GetObjects();
LOG_DEBUG("RenderRoomGraphics", "Layout has %zu objects", layout_objects.size());
LOG_DEBUG("RenderRoomGraphics", "Layout has %zu objects",
layout_objects.size());
if (layout_objects.empty()) {
return;
}
@@ -471,49 +502,58 @@ void Room::LoadLayoutTilesToBuffer() {
}
}
LOG_DEBUG("RenderRoomGraphics", "Layout tiles: BG1=%d BG2=%d skipped=%d", tiles_written_bg1, tiles_written_bg2, tiles_skipped);
LOG_DEBUG("RenderRoomGraphics", "Layout tiles: BG1=%d BG2=%d skipped=%d",
tiles_written_bg1, tiles_written_bg2, tiles_skipped);
}
void Room::RenderObjectsToBackground() {
LOG_DEBUG("[RenderObjectsToBackground]", "Starting object rendering for room %d", room_id_);
LOG_DEBUG("[RenderObjectsToBackground]",
"Starting object rendering for room %d", room_id_);
if (!rom_ || !rom_->is_loaded()) {
LOG_DEBUG("[RenderObjectsToBackground]", "ROM not loaded, aborting");
return;
}
// PERFORMANCE OPTIMIZATION: Only render objects if they have changed or if graphics changed
// Also render if bitmaps were just created (need_floor_draw was true in RenderRoomGraphics)
// PERFORMANCE OPTIMIZATION: Only render objects if they have changed or if
// graphics changed Also render if bitmaps were just created (need_floor_draw
// was true in RenderRoomGraphics)
auto& bg1_bmp = bg1_buffer_.bitmap();
auto& bg2_bmp = bg2_buffer_.bitmap();
bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 && bg2_bmp.is_active() && bg2_bmp.width() > 0;
bool bitmaps_exist = bg1_bmp.is_active() && bg1_bmp.width() > 0 &&
bg2_bmp.is_active() && bg2_bmp.width() > 0;
if (!objects_dirty_ && !graphics_dirty_ && bitmaps_exist) {
LOG_DEBUG("[RenderObjectsToBackground]", "Room %d: Objects not dirty, skipping render", room_id_);
LOG_DEBUG("[RenderObjectsToBackground]",
"Room %d: Objects not dirty, skipping render", room_id_);
return;
}
// Get palette group for object rendering (use SAME lookup as RenderRoomGraphics)
// Get palette group for object rendering (use SAME lookup as
// RenderRoomGraphics)
auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main;
int num_palettes = dungeon_pal_group.size();
// Use palette indirection table lookup
int palette_id = palette;
if (palette < rom()->paletteset_ids.size() && !rom()->paletteset_ids[palette].empty()) {
if (palette < rom()->paletteset_ids.size() &&
!rom()->paletteset_ids[palette].empty()) {
auto dungeon_palette_ptr = rom()->paletteset_ids[palette][0];
auto palette_word = rom()->ReadWord(0xDEC4B + dungeon_palette_ptr);
if (palette_word.ok()) {
palette_id = palette_word.value() / 180;
}
}
if (palette_id < 0 || palette_id >= num_palettes) {
palette_id = 0;
}
auto room_palette = dungeon_pal_group[palette_id];
// Dungeon palettes are 16-color sub-palettes. Split the 90-color palette into 16-color groups.
auto palette_group_result = gfx::CreatePaletteGroupFromLargePalette(room_palette, 16);
// Dungeon palettes are 16-color sub-palettes. Split the 90-color palette into
// 16-color groups.
auto palette_group_result =
gfx::CreatePaletteGroupFromLargePalette(room_palette, 16);
if (!palette_group_result.ok()) {
// Fallback to empty palette group
gfx::PaletteGroup empty_group;
@@ -522,20 +562,24 @@ void Room::RenderObjectsToBackground() {
return;
}
auto palette_group = palette_group_result.value();
// Use ObjectDrawer for pattern-based object rendering
// This provides proper wall/object drawing patterns
// Pass the room-specific graphics buffer (current_gfx16_) so objects use correct tiles
// Pass the room-specific graphics buffer (current_gfx16_) so objects use
// correct tiles
ObjectDrawer drawer(rom_, current_gfx16_.data());
auto status = drawer.DrawObjectList(tile_objects_, bg1_buffer_, bg2_buffer_, palette_group);
auto status = drawer.DrawObjectList(tile_objects_, bg1_buffer_, bg2_buffer_,
palette_group);
// Log only failures, not successes
if (!status.ok()) {
LOG_DEBUG("[RenderObjectsToBackground]", "ObjectDrawer failed: %s", std::string(status.message()).c_str());
LOG_DEBUG("[RenderObjectsToBackground]", "ObjectDrawer failed: %s",
std::string(status.message()).c_str());
} else {
// Mark objects as clean after successful render
objects_dirty_ = false;
LOG_DEBUG("[RenderObjectsToBackground]", "Room %d: Objects rendered successfully", room_id_);
LOG_DEBUG("[RenderObjectsToBackground]",
"Room %d: Objects rendered successfully", room_id_);
}
}
@@ -546,59 +590,63 @@ void Room::LoadAnimatedGraphics() {
if (!rom_ || !rom_->is_loaded()) {
return;
}
auto gfx_buffer_data = rom()->mutable_graphics_buffer();
if (!gfx_buffer_data || gfx_buffer_data->empty()) {
return;
}
auto rom_data = rom()->vector();
if (rom_data.empty()) {
return;
}
// Validate animated_frame_ bounds
if (animated_frame_ < 0 || animated_frame_ > 10) {
return;
}
// Validate background_tileset_ bounds
if (background_tileset_ < 0 || background_tileset_ > 255) {
return;
}
int gfx_ptr = SnesToPc(rom()->version_constants().kGfxAnimatedPointer);
if (gfx_ptr < 0 || gfx_ptr >= static_cast<int>(rom_data.size())) {
return;
}
int data = 0;
while (data < 512) {
// Validate buffer access for first operation
int first_offset = data + (92 * 2048) + (512 * animated_frame_);
if (first_offset >= 0 && first_offset < static_cast<int>(gfx_buffer_data->size())) {
if (first_offset >= 0 &&
first_offset < static_cast<int>(gfx_buffer_data->size())) {
uint8_t map_byte = (*gfx_buffer_data)[first_offset];
// Validate current_gfx16_ access
int gfx_offset = data + (7 * 2048);
if (gfx_offset >= 0 && gfx_offset < static_cast<int>(sizeof(current_gfx16_))) {
if (gfx_offset >= 0 &&
gfx_offset < static_cast<int>(sizeof(current_gfx16_))) {
current_gfx16_[gfx_offset] = map_byte;
}
}
// Validate buffer access for second operation
int tileset_index = rom_data[gfx_ptr + background_tileset_];
int second_offset = data + (tileset_index * 2048) + (512 * animated_frame_);
if (second_offset >= 0 && second_offset < static_cast<int>(gfx_buffer_data->size())) {
if (second_offset >= 0 &&
second_offset < static_cast<int>(gfx_buffer_data->size())) {
uint8_t map_byte = (*gfx_buffer_data)[second_offset];
// Validate current_gfx16_ access
int gfx_offset = data + (7 * 2048) - 512;
if (gfx_offset >= 0 && gfx_offset < static_cast<int>(sizeof(current_gfx16_))) {
if (gfx_offset >= 0 &&
gfx_offset < static_cast<int>(sizeof(current_gfx16_))) {
current_gfx16_[gfx_offset] = map_byte;
}
}
data++;
}
}
@@ -606,20 +654,20 @@ void Room::LoadAnimatedGraphics() {
void Room::LoadObjects() {
LOG_DEBUG("[LoadObjects]", "Starting LoadObjects for room %d", room_id_);
auto rom_data = rom()->vector();
// Enhanced object loading with comprehensive validation
int object_pointer = (rom_data[room_object_pointer + 2] << 16) +
(rom_data[room_object_pointer + 1] << 8) +
(rom_data[room_object_pointer]);
object_pointer = SnesToPc(object_pointer);
// Enhanced bounds checking for object pointer
if (object_pointer < 0 || object_pointer >= (int)rom_->size()) {
return;
}
int room_address = object_pointer + (room_id_ * 3);
// Enhanced bounds checking for room address
if (room_address < 0 || room_address + 2 >= (int)rom_->size()) {
return;
@@ -629,7 +677,7 @@ void Room::LoadObjects() {
(rom_data[room_address + 1] << 8) + rom_data[room_address];
int objects_location = SnesToPc(tile_address);
// Enhanced bounds checking for objects location
if (objects_location < 0 || objects_location >= (int)rom_->size()) {
return;
@@ -638,9 +686,12 @@ void Room::LoadObjects() {
// Parse floor graphics and layout with validation
if (objects_location + 1 < (int)rom_->size()) {
if (is_floor_) {
floor1_graphics_ = static_cast<uint8_t>(rom_data[objects_location] & 0x0F);
floor2_graphics_ = static_cast<uint8_t>((rom_data[objects_location] >> 4) & 0x0F);
LOG_DEBUG("[LoadObjects]", "Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
floor1_graphics_ =
static_cast<uint8_t>(rom_data[objects_location] & 0x0F);
floor2_graphics_ =
static_cast<uint8_t>((rom_data[objects_location] >> 4) & 0x0F);
LOG_DEBUG("[LoadObjects]",
"Room %d: Set floor1_graphics_=%d, floor2_graphics_=%d",
room_id_, floor1_graphics_, floor2_graphics_);
}
@@ -655,7 +706,7 @@ void Room::LoadObjects() {
void Room::ParseObjectsFromLocation(int objects_location) {
auto rom_data = rom()->vector();
z3_staircases_.clear();
int nbr_of_staircase = 0;
@@ -666,14 +717,14 @@ void Room::ParseObjectsFromLocation(int objects_location) {
int layer = 0;
bool door = false;
bool end_read = false;
// Enhanced parsing loop with bounds checking
while (!end_read && pos < (int)rom_->size()) {
// Check if we have enough bytes to read
if (pos + 1 >= (int)rom_->size()) {
break;
}
b1 = rom_data[pos];
b2 = rom_data[pos + 1];
@@ -697,7 +748,7 @@ void Room::ParseObjectsFromLocation(int objects_location) {
if (pos + 2 >= (int)rom_->size()) {
break;
}
b3 = rom_data[pos + 2];
if (door) {
pos += 2;
@@ -707,8 +758,9 @@ void Room::ParseObjectsFromLocation(int objects_location) {
if (!door) {
// Use the refactored encoding/decoding functions (Phase 1, Task 1.2)
RoomObject r = RoomObject::DecodeObjectFromBytes(b1, b2, b3, static_cast<uint8_t>(layer));
RoomObject r = RoomObject::DecodeObjectFromBytes(
b1, b2, b3, static_cast<uint8_t>(layer));
// Validate object ID before adding to the room
if (r.id_ >= 0 && r.id_ <= 0x3FF) {
r.set_rom(rom_);
@@ -719,8 +771,10 @@ void Room::ParseObjectsFromLocation(int objects_location) {
}
} else {
// Handle door objects (placeholder for future implementation)
// tile_objects_.push_back(z3_object_door(static_cast<short>((b2 << 8) + b1),
// 0, 0, 0, static_cast<uint8_t>(layer)));
// tile_objects_.push_back(z3_object_door(static_cast<short>((b2 << 8) +
// b1),
// 0, 0, 0,
// static_cast<uint8_t>(layer)));
}
}
}
@@ -731,20 +785,26 @@ void Room::ParseObjectsFromLocation(int objects_location) {
std::vector<uint8_t> Room::EncodeObjects() const {
std::vector<uint8_t> bytes;
// Organize objects by layer
std::vector<RoomObject> layer0_objects;
std::vector<RoomObject> layer1_objects;
std::vector<RoomObject> layer2_objects;
for (const auto& obj : tile_objects_) {
switch (obj.GetLayerValue()) {
case 0: layer0_objects.push_back(obj); break;
case 1: layer1_objects.push_back(obj); break;
case 2: layer2_objects.push_back(obj); break;
case 0:
layer0_objects.push_back(obj);
break;
case 1:
layer1_objects.push_back(obj);
break;
case 2:
layer2_objects.push_back(obj);
break;
}
}
// Encode Layer 1 (BG2)
for (const auto& obj : layer0_objects) {
auto encoded = obj.EncodeObjectToBytes();
@@ -754,7 +814,7 @@ std::vector<uint8_t> Room::EncodeObjects() const {
}
bytes.push_back(0xFF);
bytes.push_back(0xFF);
// Encode Layer 2 (BG1)
for (const auto& obj : layer1_objects) {
auto encoded = obj.EncodeObjectToBytes();
@@ -764,7 +824,7 @@ std::vector<uint8_t> Room::EncodeObjects() const {
}
bytes.push_back(0xFF);
bytes.push_back(0xFF);
// Encode Layer 3
for (const auto& obj : layer2_objects) {
auto encoded = obj.EncodeObjectToBytes();
@@ -772,11 +832,11 @@ std::vector<uint8_t> Room::EncodeObjects() const {
bytes.push_back(encoded.b2);
bytes.push_back(encoded.b3);
}
// Final terminator
bytes.push_back(0xFF);
bytes.push_back(0xFF);
return bytes;
}
@@ -784,21 +844,21 @@ absl::Status Room::SaveObjects() {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM pointer is null");
}
auto rom_data = rom()->vector();
// Get object pointer
int object_pointer = (rom_data[room_object_pointer + 2] << 16) +
(rom_data[room_object_pointer + 1] << 8) +
(rom_data[room_object_pointer]);
object_pointer = SnesToPc(object_pointer);
if (object_pointer < 0 || object_pointer >= (int)rom_->size()) {
return absl::OutOfRangeError("Object pointer out of range");
}
int room_address = object_pointer + (room_id_ * 3);
if (room_address < 0 || room_address + 2 >= (int)rom_->size()) {
return absl::OutOfRangeError("Room address out of range");
}
@@ -807,17 +867,17 @@ absl::Status Room::SaveObjects() {
(rom_data[room_address + 1] << 8) + rom_data[room_address];
int objects_location = SnesToPc(tile_address);
if (objects_location < 0 || objects_location >= (int)rom_->size()) {
return absl::OutOfRangeError("Objects location out of range");
}
// Skip graphics/layout header (2 bytes)
int write_pos = objects_location + 2;
// Encode all objects
auto encoded_bytes = EncodeObjects();
// Write encoded bytes to ROM using WriteVector
return rom_->WriteVector(write_pos, encoded_bytes);
}
@@ -831,10 +891,10 @@ absl::Status Room::AddObject(const RoomObject& object) {
if (!ValidateObject(object)) {
return absl::InvalidArgumentError("Invalid object parameters");
}
// Add to internal list
tile_objects_.push_back(object);
return absl::OkStatus();
}
@@ -842,9 +902,9 @@ absl::Status Room::RemoveObject(size_t index) {
if (index >= tile_objects_.size()) {
return absl::OutOfRangeError("Object index out of range");
}
tile_objects_.erase(tile_objects_.begin() + index);
return absl::OkStatus();
}
@@ -852,13 +912,13 @@ absl::Status Room::UpdateObject(size_t index, const RoomObject& object) {
if (index >= tile_objects_.size()) {
return absl::OutOfRangeError("Object index out of range");
}
if (!ValidateObject(object)) {
return absl::InvalidArgumentError("Invalid object parameters");
}
tile_objects_[index] = object;
return absl::OkStatus();
}
@@ -874,32 +934,37 @@ absl::StatusOr<size_t> Room::FindObjectAt(int x, int y, int layer) const {
bool Room::ValidateObject(const RoomObject& object) const {
// Validate position (0-63 for both X and Y)
if (object.x() < 0 || object.x() > 63) return false;
if (object.y() < 0 || object.y() > 63) return false;
if (object.x() < 0 || object.x() > 63)
return false;
if (object.y() < 0 || object.y() > 63)
return false;
// Validate layer (0-2)
if (object.GetLayerValue() < 0 || object.GetLayerValue() > 2) return false;
if (object.GetLayerValue() < 0 || object.GetLayerValue() > 2)
return false;
// Validate object ID range
if (object.id_ < 0 || object.id_ > 0xFFF) return false;
if (object.id_ < 0 || object.id_ > 0xFFF)
return false;
// Validate size for Type 1 objects
if (object.id_ < 0x100 && object.size() > 15) return false;
if (object.id_ < 0x100 && object.size() > 15)
return false;
return true;
}
void Room::HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY, int& nbr_of_staircase) {
void Room::HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY,
int& nbr_of_staircase) {
// Handle staircase objects
for (short stair : stairsObjects) {
if (stair == oid) {
if (nbr_of_staircase < 4) {
tile_objects_.back().set_options(ObjectOption::Stairs |
tile_objects_.back().options());
z3_staircases_.push_back({
posX, posY,
absl::StrCat("To ", staircase_rooms_[nbr_of_staircase])
.data()});
z3_staircases_.push_back(
{posX, posY,
absl::StrCat("To ", staircase_rooms_[nbr_of_staircase]).data()});
nbr_of_staircase++;
} else {
tile_objects_.back().set_options(ObjectOption::Stairs |
@@ -955,8 +1020,8 @@ void Room::LoadSprites() {
(b1 & 0x80) >> 7);
if (sprites_.size() > 1) {
Sprite &spr = sprites_.back();
Sprite &prevSprite = sprites_[sprites_.size() - 2];
Sprite& spr = sprites_.back();
Sprite& prevSprite = sprites_[sprites_.size() - 2];
if (spr.id() == 0xE4 && spr.x() == 0x00 && spr.y() == 0x1E &&
spr.layer() == 1 && spr.subtype() == 0x18) {
@@ -1001,72 +1066,77 @@ void Room::LoadChests() {
void Room::LoadDoors() {
auto rom_data = rom()->vector();
// Doors are loaded as part of the object stream in LoadObjects()
// When the parser encounters 0xF0 0xFF, it enters door mode
// Door objects have format: b1 (position/direction), b2 (type)
// Door encoding: b1 = (door_pos << 3) + door_dir
// b2 = door_type
// This is already handled in ParseObjectsFromLocation()
LOG_DEBUG("Room", "LoadDoors for room %d - doors are loaded via object stream", room_id_);
LOG_DEBUG("Room",
"LoadDoors for room %d - doors are loaded via object stream",
room_id_);
}
void Room::LoadTorches() {
auto rom_data = rom()->vector();
// Read torch data length
int bytes_count = (rom_data[torches_length_pointer + 1] << 8) |
int bytes_count = (rom_data[torches_length_pointer + 1] << 8) |
rom_data[torches_length_pointer];
LOG_DEBUG("Room", "LoadTorches: room_id=%d, bytes_count=%d", room_id_, bytes_count);
LOG_DEBUG("Room", "LoadTorches: room_id=%d, bytes_count=%d", room_id_,
bytes_count);
// Iterate through torch data to find torches for this room
for (int i = 0; i < bytes_count; i += 2) {
if (i + 1 >= bytes_count) break;
if (i + 1 >= bytes_count)
break;
uint8_t b1 = rom_data[torch_data + i];
uint8_t b2 = rom_data[torch_data + i + 1];
// Skip 0xFFFF markers
if (b1 == 0xFF && b2 == 0xFF) {
continue;
}
// Check if this entry is for our room
uint16_t torch_room_id = (b2 << 8) | b1;
if (torch_room_id == room_id_) {
// Found torches for this room, read them
i += 2;
while (i < bytes_count) {
if (i + 1 >= bytes_count) break;
if (i + 1 >= bytes_count)
break;
b1 = rom_data[torch_data + i];
b2 = rom_data[torch_data + i + 1];
// End of torch list for this room
if (b1 == 0xFF && b2 == 0xFF) {
break;
}
// Decode torch position and properties
int address = ((b2 & 0x1F) << 8 | b1) >> 1;
uint8_t px = address % 64;
uint8_t py = address >> 6;
uint8_t layer = (b2 & 0x20) >> 5;
bool lit = (b2 & 0x80) == 0x80;
// Create torch object (ID 0x150)
RoomObject torch_obj(0x150, px, py, 0, layer);
torch_obj.set_rom(rom_);
torch_obj.set_options(ObjectOption::Torch);
// Store lit state if needed (may require adding a field to RoomObject)
tile_objects_.push_back(torch_obj);
LOG_DEBUG("Room", "Loaded torch at (%d,%d) layer=%d lit=%d",
px, py, layer, lit);
LOG_DEBUG("Room", "Loaded torch at (%d,%d) layer=%d lit=%d", px, py,
layer, lit);
i += 2;
}
break; // Found and processed our room's torches
@@ -1074,7 +1144,8 @@ void Room::LoadTorches() {
// Skip to next room's torches
i += 2;
while (i < bytes_count) {
if (i + 1 >= bytes_count) break;
if (i + 1 >= bytes_count)
break;
b1 = rom_data[torch_data + i];
b2 = rom_data[torch_data + i + 1];
if (b1 == 0xFF && b2 == 0xFF) {
@@ -1088,24 +1159,26 @@ void Room::LoadTorches() {
void Room::LoadBlocks() {
auto rom_data = rom()->vector();
// Read blocks length
int blocks_count = (rom_data[blocks_length + 1] << 8) | rom_data[blocks_length];
LOG_DEBUG("Room", "LoadBlocks: room_id=%d, blocks_count=%d", room_id_, blocks_count);
int blocks_count =
(rom_data[blocks_length + 1] << 8) | rom_data[blocks_length];
LOG_DEBUG("Room", "LoadBlocks: room_id=%d, blocks_count=%d", room_id_,
blocks_count);
// Load block data from multiple pointers
std::vector<uint8_t> blocks_data(blocks_count);
int pos1 = blocks_pointer1;
int pos2 = blocks_pointer2;
int pos3 = blocks_pointer3;
int pos4 = blocks_pointer4;
// Read block data from 4 different locations
for (int i = 0; i < 0x80 && i < blocks_count; i++) {
blocks_data[i] = rom_data[pos1 + i];
if (i + 0x80 < blocks_count) {
blocks_data[i + 0x80] = rom_data[pos2 + i];
}
@@ -1116,16 +1189,17 @@ void Room::LoadBlocks() {
blocks_data[i + 0x180] = rom_data[pos4 + i];
}
}
// Parse blocks for this room (4 bytes per block entry)
for (int i = 0; i < blocks_count; i += 4) {
if (i + 3 >= blocks_count) break;
if (i + 3 >= blocks_count)
break;
uint8_t b1 = blocks_data[i];
uint8_t b2 = blocks_data[i + 1];
uint8_t b3 = blocks_data[i + 2];
uint8_t b4 = blocks_data[i + 3];
// Check if this block belongs to our room
uint16_t block_room_id = (b2 << 8) | b1;
if (block_room_id == room_id_) {
@@ -1133,20 +1207,20 @@ void Room::LoadBlocks() {
if (b3 == 0xFF && b4 == 0xFF) {
break;
}
// Decode block position
int address = ((b4 & 0x1F) << 8 | b3) >> 1;
uint8_t px = address % 64;
uint8_t py = address >> 6;
uint8_t layer = (b4 & 0x20) >> 5;
// Create block object (ID 0x0E00)
RoomObject block_obj(0x0E00, px, py, 0, layer);
block_obj.set_rom(rom_);
block_obj.set_options(ObjectOption::Block);
tile_objects_.push_back(block_obj);
LOG_DEBUG("Room", "Loaded block at (%d,%d) layer=%d", px, py, layer);
}
}
@@ -1154,26 +1228,26 @@ void Room::LoadBlocks() {
void Room::LoadPits() {
auto rom_data = rom()->vector();
// Read pit count
int pit_entries = rom_data[pit_count] / 2;
// Read pit pointer (long pointer)
int pit_ptr = (rom_data[pit_pointer + 2] << 16) |
(rom_data[pit_pointer + 1] << 8) |
rom_data[pit_pointer];
(rom_data[pit_pointer + 1] << 8) | rom_data[pit_pointer];
int pit_data_addr = SnesToPc(pit_ptr);
LOG_DEBUG("Room", "LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
LOG_DEBUG("Room", "LoadPits: room_id=%d, pit_entries=%d, pit_ptr=0x%06X",
room_id_, pit_entries, pit_ptr);
// Pit data is stored as: room_id (2 bytes), target info (2 bytes)
// This data is already loaded in LoadRoomFromRom() into pits_ destination struct
// The pit destination (where you go when you fall) is set via SetPitsTarget()
// This data is already loaded in LoadRoomFromRom() into pits_ destination
// struct The pit destination (where you go when you fall) is set via
// SetPitsTarget()
// Pits are typically represented in the layout/collision data, not as objects
// The pits_ member already contains the target room and layer
LOG_DEBUG("Room", "Pit destination - target=%d, target_layer=%d",
LOG_DEBUG("Room", "Pit destination - target=%d, target_layer=%d",
pits_.target, pits_.target_layer);
}

View File

@@ -7,11 +7,11 @@
#include <string_view>
#include <vector>
#include "app/rom.h"
#include "app/gfx/render/background_buffer.h"
#include "app/rom.h"
#include "zelda3/dungeon/dungeon_rom_addresses.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/dungeon/room_layout.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/sprite/sprite.h"
namespace yaze {
@@ -23,7 +23,8 @@ namespace zelda3 {
// Legacy aliases for backward compatibility (gradual migration)
constexpr int room_object_layout_pointer = kRoomObjectLayoutPointer;
constexpr int room_object_pointer = kRoomObjectPointer;
constexpr int dungeons_main_bg_palette_pointers = kDungeonsMainBgPalettePointers;
constexpr int dungeons_main_bg_palette_pointers =
kDungeonsMainBgPalettePointers;
constexpr int dungeons_palettes = kDungeonsPalettes;
constexpr int room_items_pointers = kRoomItemsPointers;
constexpr int rooms_sprite_pointer = kRoomsSpritePointer;
@@ -200,7 +201,6 @@ class Room {
void LoadRoomLayout();
void LoadLayoutTilesToBuffer();
// Public getters and manipulators for sprites
const std::vector<zelda3::Sprite>& GetSprites() const { return sprites_; }
std::vector<zelda3::Sprite>& GetSprites() { return sprites_; }
@@ -215,7 +215,6 @@ class Room {
const RoomLayout& GetLayout() const { return layout_; }
// Public getters and manipulators for tile objects
const std::vector<RoomObject>& GetTileObjects() const {
return tile_objects_;
@@ -228,7 +227,7 @@ class Room {
tile_objects_.push_back(object);
MarkObjectsDirty();
}
// Enhanced object manipulation (Phase 3)
absl::Status AddObject(const RoomObject& object);
absl::Status RemoveObject(size_t index);
@@ -237,9 +236,18 @@ class Room {
bool ValidateObject(const RoomObject& object) const;
// Performance optimization: Mark objects as dirty when modified
void MarkObjectsDirty() { objects_dirty_ = true; textures_dirty_ = true; }
void MarkGraphicsDirty() { graphics_dirty_ = true; textures_dirty_ = true; }
void MarkLayoutDirty() { layout_dirty_ = true; textures_dirty_ = true; }
void MarkObjectsDirty() {
objects_dirty_ = true;
textures_dirty_ = true;
}
void MarkGraphicsDirty() {
graphics_dirty_ = true;
textures_dirty_ = true;
}
void MarkLayoutDirty() {
layout_dirty_ = true;
textures_dirty_ = true;
}
void RemoveTileObject(size_t index) {
if (index < tile_objects_.size()) {
tile_objects_.erase(tile_objects_.begin() + index);
@@ -299,13 +307,16 @@ class Room {
}
}
void SetStaircasePlane(int index, uint8_t plane) {
if (index >= 0 && index < 4) staircase_plane_[index] = plane;
if (index >= 0 && index < 4)
staircase_plane_[index] = plane;
}
void SetHolewarp(uint8_t holewarp) { this->holewarp = holewarp; }
void SetStaircaseRoom(int index, uint8_t room) {
if (index >= 0 && index < 4) staircase_rooms_[index] = room;
if (index >= 0 && index < 4)
staircase_rooms_[index] = room;
}
// SetFloor1/SetFloor2 removed - use set_floor1()/set_floor2() instead (defined above)
// SetFloor1/SetFloor2 removed - use set_floor1()/set_floor2() instead
// (defined above)
void SetMessageId(uint16_t message_id) { message_id_ = message_id; }
// Getters for LoadRoomFromRom function
@@ -332,7 +343,7 @@ class Room {
void SetStair2Target(uint8_t target) { stair2_.target = target; }
void SetStair3Target(uint8_t target) { stair3_.target = target; }
void SetStair4Target(uint8_t target) { stair4_.target = target; }
// Loaded state
bool IsLoaded() const { return is_loaded_; }
void SetLoaded(bool loaded) { is_loaded_ = loaded; }
@@ -354,7 +365,7 @@ class Room {
// NOTE: floor1/floor2 removed - use floor1() and floor2() accessors instead
// Floor graphics are now private (floor1_graphics_, floor2_graphics_)
uint16_t message_id_ = 0;
// Floor graphics accessors (use these instead of direct members!)
uint8_t floor1() const { return floor1_graphics_; }
uint8_t floor2() const { return floor2_graphics_; }
@@ -374,7 +385,7 @@ class Room {
void ParseObjectsFromLocation(int objects_location);
void HandleSpecialObjects(short oid, uint8_t posX, uint8_t posY,
int& nbr_of_staircase);
// Object saving (Phase 1, Task 1.3)
absl::Status SaveObjects();
std::vector<uint8_t> EncodeObjects() const;
@@ -383,8 +394,10 @@ class Room {
auto& mutable_blocks() { return blocks_; }
auto rom() { return rom_; }
auto mutable_rom() { return rom_; }
const std::array<uint8_t, 0x4000>& get_gfx_buffer() const { return current_gfx16_; }
const std::array<uint8_t, 0x4000>& get_gfx_buffer() const {
return current_gfx16_;
}
// Per-room background buffers (not shared via arena!)
auto& bg1_buffer() { return bg1_buffer_; }
auto& bg2_buffer() { return bg2_buffer_; }
@@ -395,7 +408,7 @@ class Room {
Rom* rom_;
std::array<uint8_t, 0x4000> current_gfx16_;
// Each room has its OWN background buffers and bitmaps
gfx::BackgroundBuffer bg1_buffer_{512, 512};
gfx::BackgroundBuffer bg2_buffer_{512, 512};
@@ -405,7 +418,8 @@ class Room {
bool is_dark_;
bool is_floor_ = true;
// Performance optimization: Cache room properties to avoid unnecessary re-renders
// Performance optimization: Cache room properties to avoid unnecessary
// re-renders
uint8_t cached_blockset_ = 0xFF;
uint8_t cached_spriteset_ = 0xFF;
uint8_t cached_palette_ = 0xFF;
@@ -446,7 +460,6 @@ class Room {
std::vector<chest_data> chests_in_room_;
RoomLayout layout_;
LayerMergeType layer_merging_;
CollisionKey collision_;
EffectKey effect_;

View File

@@ -99,7 +99,7 @@ constexpr int bedSheetPositionY = 0x0480B8; // short value
class RoomEntrance {
public:
RoomEntrance() = default;
RoomEntrance(Rom *rom, uint8_t entrance_id, bool is_spawn_point = false)
RoomEntrance(Rom* rom, uint8_t entrance_id, bool is_spawn_point = false)
: entrance_id_(entrance_id) {
room_ = static_cast<short>(
(rom->data()[kEntranceRoom + (entrance_id * 2) + 1] << 8) +
@@ -219,7 +219,7 @@ class RoomEntrance {
}
}
absl::Status Save(Rom *rom, int entrance_id, bool is_spawn_point = false) {
absl::Status Save(Rom* rom, int entrance_id, bool is_spawn_point = false) {
if (!is_spawn_point) {
RETURN_IF_ERROR(
rom->WriteShort(kEntranceYPosition + (entrance_id * 2), y_position_));

View File

@@ -13,21 +13,24 @@ uint16_t ReadWord(const Rom* rom, int pc_addr) {
const auto& data = rom->data();
return static_cast<uint16_t>(data[pc_addr] | (data[pc_addr + 1] << 8));
}
}
} // namespace
absl::StatusOr<int> RoomLayout::GetLayoutAddress(int layout_id) const {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (layout_id < 0 || layout_id >= static_cast<int>(kRoomLayoutPointers.size())) {
return absl::InvalidArgumentError(absl::StrFormat("Invalid layout id %d", layout_id));
if (layout_id < 0 ||
layout_id >= static_cast<int>(kRoomLayoutPointers.size())) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid layout id %d", layout_id));
}
uint32_t snes_addr = kRoomLayoutPointers[layout_id];
int pc_addr = SnesToPc(static_cast<int>(snes_addr));
if (pc_addr < 0 || pc_addr >= static_cast<int>(rom_->size())) {
return absl::OutOfRangeError(absl::StrFormat("Layout pointer %d out of range", layout_id));
return absl::OutOfRangeError(
absl::StrFormat("Layout pointer %d out of range", layout_id));
}
return pc_addr;
}
@@ -64,7 +67,8 @@ absl::Status RoomLayout::LoadLayout(int layout_id) {
uint8_t b3 = rom_data[pos + 2];
pos += 3;
RoomObject obj = RoomObject::DecodeObjectFromBytes(b1, b2, b3, static_cast<uint8_t>(layer));
RoomObject obj = RoomObject::DecodeObjectFromBytes(
b1, b2, b3, static_cast<uint8_t>(layer));
obj.set_rom(rom_);
obj.EnsureTilesLoaded();
objects_.push_back(obj);

View File

@@ -1,22 +1,23 @@
#include "room_object.h"
#include "absl/status/status.h"
#include "zelda3/dungeon/object_parser.h"
#include "util/log.h"
#include "zelda3/dungeon/object_parser.h"
namespace yaze {
namespace zelda3 {
namespace {
struct SubtypeTableInfo {
int base_ptr; // base address of subtype table in ROM (PC)
int index_mask; // mask to apply to object id for index
int base_ptr; // base address of subtype table in ROM (PC)
int index_mask; // mask to apply to object id for index
SubtypeTableInfo(int base, int mask) : base_ptr(base), index_mask(mask) {}
};
SubtypeTableInfo GetSubtypeTable(int object_id) {
// Heuristic: 0x00-0xFF => subtype1, 0x100-0x1FF => subtype2, >=0x200 => subtype3
// Heuristic: 0x00-0xFF => subtype1, 0x100-0x1FF => subtype2, >=0x200 =>
// subtype3
if (object_id >= 0x200) {
return SubtypeTableInfo(kRoomObjectSubtype3, 0xFF);
} else if (object_id >= 0x100) {
@@ -25,7 +26,7 @@ SubtypeTableInfo GetSubtypeTable(int object_id) {
return SubtypeTableInfo(kRoomObjectSubtype1, 0xFF);
}
}
} // namespace
} // namespace
ObjectOption operator|(ObjectOption lhs, ObjectOption rhs) {
return static_cast<ObjectOption>(static_cast<int>(lhs) |
@@ -47,13 +48,14 @@ ObjectOption operator~(ObjectOption option) {
}
// NOTE: DrawTile was legacy ZScream code that is no longer used.
// Modern rendering uses ObjectDrawer which draws directly to BackgroundBuffer bitmaps.
// Modern rendering uses ObjectDrawer which draws directly to BackgroundBuffer
// bitmaps.
void RoomObject::EnsureTilesLoaded() {
if (tiles_loaded_) {
return;
}
if (rom_ == nullptr) {
return;
}
@@ -72,7 +74,7 @@ void RoomObject::EnsureTilesLoaded() {
SubtypeTableInfo sti = GetSubtypeTable(id_);
int index = (id_ & sti.index_mask);
int tile_ptr = sti.base_ptr + (index * 2);
// Enhanced bounds checking
if (tile_ptr < 0 || tile_ptr + 1 >= (int)rom_->size()) {
// Log error but don't crash
@@ -88,7 +90,7 @@ void RoomObject::EnsureTilesLoaded() {
// Log error but don't crash
return;
}
// Read tile data with validation
uint16_t w0 = (uint16_t)(rom_data[pos] | (rom_data[pos + 1] << 8));
uint16_t w1 = (uint16_t)(rom_data[pos + 2] | (rom_data[pos + 3] << 8));
@@ -124,11 +126,11 @@ absl::StatusOr<std::span<const gfx::TileInfo>> RoomObject::GetTiles() const {
if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded();
}
if (tiles_.empty()) {
return absl::FailedPreconditionError("No tiles loaded for object");
}
return std::span<const gfx::TileInfo>(tiles_.data(), tiles_.size());
}
@@ -136,12 +138,12 @@ absl::StatusOr<const gfx::TileInfo*> RoomObject::GetTile(int index) const {
if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded();
}
if (index < 0 || index >= static_cast<int>(tiles_.size())) {
return absl::OutOfRangeError(
absl::StrFormat("Tile index %d out of range (0-%d)", index, tiles_.size() - 1));
return absl::OutOfRangeError(absl::StrFormat(
"Tile index %d out of range (0-%d)", index, tiles_.size() - 1));
}
return &tiles_[index];
}
@@ -149,7 +151,7 @@ int RoomObject::GetTileCount() const {
if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded();
}
return tile_count_;
}
@@ -163,11 +165,11 @@ int RoomObject::DetermineObjectType(uint8_t /* b1 */, uint8_t b3) {
if (b3 >= 0xF8) {
return 3;
}
// Type 1: Standard objects (ID 0x00-0xFF) - check this first
// Type 2: Objects with ID >= 0x100 (these have b1 >= 0xFC)
// We'll handle Type 2 in the decoding logic after Type 1
return 1;
}
@@ -181,31 +183,35 @@ RoomObject RoomObject::DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3,
// Follow ZScream's parsing logic exactly
if (b3 >= 0xF8) {
// Type 3: xxxxxxii yyyyyyii 11111iii
// ZScream: oid = (ushort)((b3 << 4) | 0x80 + (((b2 & 0x03) << 2) + ((b1 & 0x03))));
id = (static_cast<uint16_t>(b3) << 4) | 0x80 |
// ZScream: oid = (ushort)((b3 << 4) | 0x80 + (((b2 & 0x03) << 2) + ((b1 &
// 0x03))));
id = (static_cast<uint16_t>(b3) << 4) | 0x80 |
((static_cast<uint16_t>(b2 & 0x03) << 2) + (b1 & 0x03));
x = (b1 & 0xFC) >> 2;
y = (b2 & 0xFC) >> 2;
size = ((b1 & 0x03) << 2) | (b2 & 0x03);
LOG_DEBUG("ObjectParser", "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
b1, b2, b3, id, x, y, size);
LOG_DEBUG("ObjectParser",
"Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
b2, b3, id, x, y, size);
} else {
// Type 1: xxxxxxss yyyyyyss iiiiiiii
id = b3;
x = (b1 & 0xFC) >> 2;
y = (b2 & 0xFC) >> 2;
size = ((b1 & 0x03) << 2) | (b2 & 0x03);
// Check for Type 2 override: 111111xx xxxxyyyy yyiiiiii
if (b1 >= 0xFC) {
id = (b3 & 0x3F) | 0x100;
x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
size = 0;
LOG_DEBUG("ObjectParser", "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
LOG_DEBUG("ObjectParser",
"Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
b1, b2, b3, id, x, y, size);
} else {
LOG_DEBUG("ObjectParser", "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
LOG_DEBUG("ObjectParser",
"Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
b1, b2, b3, id, x, y, size);
}
}

View File

@@ -67,7 +67,7 @@ class RoomObject {
void set_rom(Rom* rom) { rom_ = rom; }
auto rom() { return rom_; }
auto mutable_rom() { return rom_; }
// Position setters and getters
void set_x(uint8_t x) { x_ = x; }
void set_y(uint8_t y) { y_ = y; }
@@ -79,7 +79,7 @@ class RoomObject {
// Ensures tiles_ is populated with a basic set based on ROM tables so we can
// preview/draw objects without needing full emulator execution.
void EnsureTilesLoaded();
// Load tiles using the new ObjectParser
absl::Status LoadTilesWithParser();
@@ -89,40 +89,40 @@ class RoomObject {
// Get tile data through Arena system - returns references, not copies
absl::StatusOr<std::span<const gfx::TileInfo>> GetTiles() const;
// Get individual tile by index - uses Arena lookup
absl::StatusOr<const gfx::TileInfo*> GetTile(int index) const;
// Get tile count without loading all tiles
int GetTileCount() const;
// ============================================================================
// Object Encoding/Decoding (Phase 1, Task 1.1)
// ============================================================================
// 3-byte object encoding structure
struct ObjectBytes {
uint8_t b1;
uint8_t b2;
uint8_t b3;
};
// Decode object from 3-byte ROM format
// Type1: xxxxxxss yyyyyyss iiiiiiii (ID 0x00-0xFF)
// Type2: 111111xx xxxxyyyy yyiiiiii (ID 0x100-0x1FF)
// Type3: xxxxxxii yyyyyyii 11111iii (ID 0xF00-0xFFF)
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3,
uint8_t layer);
// Encode object to 3-byte ROM format
ObjectBytes EncodeObjectToBytes() const;
// Determine object type from bytes (1, 2, or 3)
static int DetermineObjectType(uint8_t b1, uint8_t b3);
// Get layer from LayerType enum
uint8_t GetLayerValue() const { return static_cast<uint8_t>(layer_); }
// ============================================================================
// NOTE: Legacy ZScream methods removed. Modern rendering uses:
@@ -158,13 +158,14 @@ class RoomObject {
std::string name_;
std::vector<uint8_t> preview_object_data_;
// Tile data storage - using Arena system for efficient memory management
// Instead of copying Tile16 vectors, we store references to Arena-managed data
mutable std::vector<gfx::TileInfo> tiles_; // Individual tiles like ZScream
// Instead of copying Tile16 vectors, we store references to Arena-managed
// data
mutable std::vector<gfx::TileInfo> tiles_; // Individual tiles like ZScream
mutable bool tiles_loaded_ = false;
mutable int tile_count_ = 0;
mutable int tile_data_ptr_ = -1; // Pointer to tile data in ROM
mutable int tile_data_ptr_ = -1; // Pointer to tile data in ROM
LayerType layer_;
ObjectOption options_ = ObjectOption::Nothing;

View File

@@ -21,11 +21,11 @@ namespace yaze {
namespace zelda3 {
namespace {
void AddSpcReloc(music::SongSpcBlock *sbl, short addr) {
void AddSpcReloc(music::SongSpcBlock* sbl, short addr) {
sbl->relocs[sbl->relnum++] = addr;
if (sbl->relnum == sbl->relsz) {
sbl->relsz += 16;
sbl->relocs = (unsigned short *)realloc(sbl->relocs, sbl->relsz << 1);
sbl->relocs = (unsigned short*)realloc(sbl->relocs, sbl->relsz << 1);
}
}
} // namespace
@@ -39,21 +39,21 @@ namespace music {
* @param bank The target sound bank for this block.
* @return A pointer to the newly allocated SongSpcBlock.
*/
SongSpcBlock *Tracker::AllocSpcBlock(int len, int bank) {
SongSpcBlock *sbl;
SongSpcBlock* Tracker::AllocSpcBlock(int len, int bank) {
SongSpcBlock* sbl;
if (!len) {
printf("warning zero length block allocated");
}
if (ss_num == ss_size) {
ss_size += 512;
ssblt = (SongSpcBlock **)realloc(ssblt, ss_size << 2);
ssblt = (SongSpcBlock**)realloc(ssblt, ss_size << 2);
}
ssblt[ss_num] = sbl = (SongSpcBlock *)malloc(sizeof(SongSpcBlock));
ssblt[ss_num] = sbl = (SongSpcBlock*)malloc(sizeof(SongSpcBlock));
ss_num++;
sbl->start = ss_next;
sbl->len = len;
sbl->buf = (uint8_t *)malloc(len);
sbl->relocs = (uint16_t *)malloc(32);
sbl->buf = (uint8_t*)malloc(len);
sbl->relocs = (uint16_t*)malloc(32);
sbl->relsz = 16;
sbl->relnum = 0;
sbl->bank = bank & 7;
@@ -72,8 +72,8 @@ SongSpcBlock *Tracker::AllocSpcBlock(int len, int bank) {
* @param bank The sound bank where the data resides.
* @return A pointer to the data within the ROM buffer, or nullptr if not found.
*/
unsigned char *Tracker::GetSpcAddr(Rom &rom, unsigned short addr, short bank) {
unsigned char *rom_ptr;
unsigned char* Tracker::GetSpcAddr(Rom& rom, unsigned short addr, short bank) {
unsigned char* rom_ptr;
unsigned short a;
unsigned short b;
spcbank = bank + 1;
@@ -82,7 +82,7 @@ again:
rom_ptr = rom.mutable_data() + sbank_ofs[spcbank];
for (;;) {
a = *(unsigned short *)rom_ptr;
a = *(unsigned short*)rom_ptr;
if (!a) {
if (spcbank) {
@@ -93,7 +93,7 @@ again:
return nullptr;
}
b = *(unsigned short *)(rom_ptr + 2);
b = *(unsigned short*)(rom_ptr + 2);
rom_ptr += 4;
if (addr >= b && addr - b < a) {
@@ -116,23 +116,26 @@ short Tracker::AllocSpcCommand() {
int i = m_free;
int j;
int k;
SpcCommand *spc_command;
SpcCommand* spc_command;
if (i == -1) {
j = m_size;
m_size += 1024;
spc_command = current_spc_command_ = (SpcCommand *)realloc(
current_spc_command_, m_size * sizeof(SpcCommand));
spc_command = current_spc_command_ =
(SpcCommand*)realloc(current_spc_command_, m_size * sizeof(SpcCommand));
k = 1023;
while (k--) spc_command[j].next = j + 1, j++;
while (k--)
spc_command[j].next = j + 1, j++;
spc_command[j].next = -1;
k = 1023;
while (k--) spc_command[j].prev = j - 1, j--;
while (k--)
spc_command[j].prev = j - 1, j--;
spc_command[j].prev = -1;
i = j;
} else
spc_command = current_spc_command_;
m_free = spc_command[m_free].next;
if (m_free != -1) spc_command[m_free].prev = -1;
if (m_free != -1)
spc_command[m_free].prev = -1;
return i;
}
@@ -145,9 +148,9 @@ short Tracker::AllocSpcCommand() {
* @param prevtime The duration of the subsequent block.
* @return The total duration in ticks.
*/
short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
SpcCommand *spc_command = current_spc_command_;
SpcCommand *spc_command2;
short Tracker::GetBlockTime(Rom& rom, short num, short prevtime) {
SpcCommand* spc_command = current_spc_command_;
SpcCommand* spc_command2;
int i = -1;
int j = 0;
@@ -157,7 +160,8 @@ short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
int n = prevtime;
l = num;
if (l == -1) return 0;
if (l == -1)
return 0;
for (;;) {
if (spc_command[l].flag & 4) {
@@ -166,9 +170,11 @@ short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
k = 1;
}
if (!k) i = l;
if (!k)
i = l;
if (spc_command[l].flag & 1) n = spc_command[l].b1;
if (spc_command[l].flag & 1)
n = spc_command[l].b1;
l = spc_command[l].next;
@@ -191,7 +197,7 @@ short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
}
spc_command2 = spc_command + i;
if (spc_command2->cmd == 0xef) {
k = *(short *)&(spc_command2->p1);
k = *(short*)&(spc_command2->p1);
if (k >= m_size) {
printf("Invalid music address\n");
m_modf = 1;
@@ -218,7 +224,8 @@ short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
m += spc_command[k].tim2 * spc_command2->p3;
}
} else {
if (spc_command2->cmd < 0xe0) m++;
if (spc_command2->cmd < 0xe0)
m++;
if (spc_command2->flag & 1) {
j += m * spc_command[i].b1;
m = 0;
@@ -228,7 +235,8 @@ short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
spc_command2->tim2 = m;
spc_command2->flag |= 4;
if (i == num) break;
if (i == num)
break;
i = spc_command2->prev;
}
@@ -247,7 +255,7 @@ short Tracker::GetBlockTime(Rom &rom, short num, short prevtime) {
* @param t A time limit for parsing, to handle potentially infinite loops.
* @return The index of the first command in the newly loaded block.
*/
short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
short Tracker::LoadSpcCommand(Rom& rom, unsigned short addr, short bank,
int t) {
int b = 0;
int c = 0;
@@ -265,12 +273,13 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
unsigned char j = 0;
unsigned char k = 0;
unsigned char *a = nullptr;
unsigned char* a = nullptr;
SongRange *sr;
SpcCommand *spc_command = current_spc_command_;
SpcCommand *spc_command2;
if (!addr) return -1;
SongRange* sr;
SpcCommand* spc_command = current_spc_command_;
SpcCommand* spc_command2;
if (!addr)
return -1;
a = GetSpcAddr(rom, addr, bank);
d = spcbank;
@@ -285,7 +294,8 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
for (c = 0; c < e; c++) {
if (sr[c].bank == d) {
if (sr[c].start > addr) {
if (sr[c].start < f) f = sr[c].start;
if (sr[c].start < f)
f = sr[c].start;
n = c;
} else if (sr[c].end > addr) {
for (f = sr[c].first; f != -1; f = spc_command[f].next) {
@@ -297,7 +307,8 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
lastsr = c;
return f;
}
if (spc_command[f].flag & 1) k = spc_command[f].b1;
if (spc_command[f].flag & 1)
k = spc_command[f].b1;
if (spc_command[f].cmd < 0xca) {
if (k) {
m -= k;
@@ -323,28 +334,33 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
spc_command2 = spc_command + i;
if (spc_command2->next == -1) {
l = m_size;
spc_command = current_spc_command_ = (SpcCommand *)realloc(
spc_command = current_spc_command_ = (SpcCommand*)realloc(
spc_command, sizeof(SpcCommand) * (m_size += 1024));
spc_command2 = spc_command + i;
n = l + 1023;
while (l < n) spc_command[l].next = l + 1, l++;
while (l < n)
spc_command[l].next = l + 1, l++;
spc_command[l].next = -1;
n -= 1023;
while (l > n) spc_command[l].prev = l - 1, l--;
while (l > n)
spc_command[l].prev = l - 1, l--;
spc_command[l].prev = i;
spc_command2->next = l;
}
spc_command2->addr = g;
b = a[g];
if (!b) break;
if (m >= t && b != 0xf9) break;
if (!b)
break;
if (m >= t && b != 0xf9)
break;
g++;
j = 0;
if (b < 128) {
j = 1;
k = spc_command2->b1 = b;
b = a[g++];
if (b < 128) j = 3, spc_command2->b2 = b, b = a[g++];
if (b < 128)
j = 3, spc_command2->b2 = b, b = a[g++];
}
if (b < 0xe0) {
if (k) {
@@ -357,16 +373,19 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
spc_command2->flag = j;
if (b >= 0xe0) {
b -= 0xe0;
if (op_len[b]) spc_command2->p1 = a[g++];
if (op_len[b] > 1) spc_command2->p2 = a[g++];
if (op_len[b] > 2) spc_command2->p3 = a[g++];
if (op_len[b])
spc_command2->p1 = a[g++];
if (op_len[b] > 1)
spc_command2->p2 = a[g++];
if (op_len[b] > 2)
spc_command2->p3 = a[g++];
if (b == 15) {
m_free = spc_command2->next;
spc_command[spc_command2->next].prev = -1;
l = LoadSpcCommand(rom, *(short *)(&(spc_command2->p1)), bank, t - m);
l = LoadSpcCommand(rom, *(short*)(&(spc_command2->p1)), bank, t - m);
spc_command = current_spc_command_;
spc_command2 = spc_command + i;
*(short *)(&(spc_command2->p1)) = l;
*(short*)(&(spc_command2->p1)) = l;
spc_command2->next = m_free;
spc_command[spc_command2->next].prev = i;
GetBlockTime(rom, l, 0);
@@ -377,7 +396,8 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
i = spc_command2->next;
break;
}
if (song_range_[lastsr].endtime) k = song_range_[lastsr].endtime;
if (song_range_[lastsr].endtime)
k = song_range_[lastsr].endtime;
}
}
i = spc_command2->next;
@@ -401,7 +421,7 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
} else {
if (srsize == srnum)
song_range_ =
(SongRange *)realloc(song_range_, (srsize += 16) * sizeof(SongRange));
(SongRange*)realloc(song_range_, (srsize += 16) * sizeof(SongRange));
lastsr = srnum;
sr = song_range_ + (srnum++);
@@ -423,12 +443,12 @@ short Tracker::LoadSpcCommand(Rom &rom, unsigned short addr, short bank,
/**
* @brief High-level function to load all song data from the ROM.
* (Currently commented out, but this would be the main entry point for parsing.)
* It would iterate through the main song table in the ROM, and for each song,
* recursively load all its parts and commands using LoadSpcCommand. It would
* also load instrument and sample data.
* (Currently commented out, but this would be the main entry point for
* parsing.) It would iterate through the main song table in the ROM, and for
* each song, recursively load all its parts and commands using LoadSpcCommand.
* It would also load instrument and sample data.
*/
void Tracker::LoadSongs(Rom &rom) {
void Tracker::LoadSongs(Rom& rom) {
// unsigned char *b;
// unsigned char *c;
// unsigned char *d;
@@ -595,8 +615,8 @@ void Tracker::LoadSongs(Rom &rom) {
// numsndinst = spclen / 9;
// b = GetSpcAddr(rom, 0x3c00, 0);
// zelda_wave = waves = (ZeldaWave *)malloc(sizeof(ZeldaWave) * (spclen >> 2));
// p = spclen >> 1;
// zelda_wave = waves = (ZeldaWave *)malloc(sizeof(ZeldaWave) * (spclen >>
// 2)); p = spclen >> 1;
// for (i = 0; i < p; i += 2) {
// j = ((unsigned short *)b)[i];
@@ -708,16 +728,16 @@ void Tracker::LoadSongs(Rom &rom) {
* @param endtr Flag indicating if this is the end of a track.
* @return The new virtual address of the saved block.
*/
short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
short Tracker::SaveSpcCommand(Rom& rom, short num, short songtime,
short endtr) {
SpcCommand *spc_command = current_spc_command_;
SpcCommand *spc_command2;
SongRange *sr = song_range_;
SongSpcBlock *sbl;
SpcCommand* spc_command = current_spc_command_;
SpcCommand* spc_command2;
SongRange* sr = song_range_;
SongSpcBlock* sbl;
text_buf_ty buf;
unsigned char *b;
unsigned char* b;
int i = num;
int j = 0;
int k = 0;
@@ -727,7 +747,8 @@ short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
int o = 0;
int p = 0;
if (i == -1) return 0;
if (i == -1)
return 0;
if (i >= m_size) {
printf("Error.\n");
@@ -735,11 +756,13 @@ short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
return 0;
}
if (spc_command[i].flag & 8) return spc_command[i].addr;
if (spc_command[i].flag & 8)
return spc_command[i].addr;
for (;;) {
j = spc_command[i].prev;
if (j == -1) break;
if (j == -1)
break;
i = j;
}
@@ -748,23 +771,29 @@ short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
l = GetBlockTime(rom, i, 0);
m = i;
for (;;) {
if (m == -1) break;
if (m == -1)
break;
k++;
spc_command2 = spc_command + m;
if (spc_command2->flag & 1) k++, n = spc_command2->b1;
if (spc_command2->flag & 2) k++;
if (spc_command2->cmd >= 0xe0) k += op_len[spc_command2->cmd - 0xe0];
if (spc_command2->flag & 1)
k++, n = spc_command2->b1;
if (spc_command2->flag & 2)
k++;
if (spc_command2->cmd >= 0xe0)
k += op_len[spc_command2->cmd - 0xe0];
m = spc_command2->next;
}
songtime -= l;
if (songtime > 0) {
l = (songtime + 126) / 127;
if (songtime % l) l += 2;
if (songtime % l)
l += 2;
l++;
if (n && !songtime % n) {
p = songtime / n;
if (p < l) l = p;
if (p < l)
l = p;
} else
p = -1;
k += l;
@@ -774,25 +803,32 @@ short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
b = sbl->buf;
for (;;) {
if (i == -1) break;
if (i == -1)
break;
spc_command2 = spc_command + i;
spc_command2->addr = b - sbl->buf + sbl->start;
spc_command2->flag |= 8;
if (spc_command2->flag & 1) *(b++) = spc_command2->b1;
if (spc_command2->flag & 2) *(b++) = spc_command2->b2;
if (spc_command2->flag & 1)
*(b++) = spc_command2->b1;
if (spc_command2->flag & 2)
*(b++) = spc_command2->b2;
*(b++) = spc_command2->cmd;
if (spc_command2->cmd >= 0xe0) {
o = op_len[spc_command2->cmd - 0xe0];
if (spc_command2->cmd == 0xef) {
*(short *)b =
SaveSpcCommand(rom, *(short *)&(spc_command2->p1), 0, 1);
if (b) AddSpcReloc(sbl, b - sbl->buf);
*(short*)b =
SaveSpcCommand(rom, *(short*)&(spc_command2->p1), 0, 1);
if (b)
AddSpcReloc(sbl, b - sbl->buf);
b[2] = spc_command2->p3;
b += 3;
} else {
if (o) *(b++) = spc_command2->p1;
if (o > 1) *(b++) = spc_command2->p2;
if (o > 2) *(b++) = spc_command2->p3;
if (o)
*(b++) = spc_command2->p1;
if (o > 1)
*(b++) = spc_command2->p2;
if (o > 2)
*(b++) = spc_command2->p3;
}
}
i = spc_command2->next;
@@ -808,7 +844,8 @@ short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
*(b++) = n;
}
for (; songtime >= n; songtime -= n) *(b++) = 0xc9;
for (; songtime >= n; songtime -= n)
*(b++) = 0xc9;
if (songtime) {
*(b++) = (uint8_t)songtime;
@@ -841,15 +878,17 @@ short Tracker::SaveSpcCommand(Rom &rom, short num, short songtime,
* @param limit The upper bound for writing in the ROM.
* @return The next available ROM address after writing.
*/
int Tracker::WriteSpcData(Rom &rom, void *buf, int len, int addr, int spc,
int Tracker::WriteSpcData(Rom& rom, void* buf, int len, int addr, int spc,
int limit) {
unsigned char *rom_data = rom.mutable_data();
unsigned char* rom_data = rom.mutable_data();
if (!len) return addr;
if (!len)
return addr;
if (((addr + len + 4) & 0x7fff) > 0x7ffb) {
if (addr + 5 > limit) goto error;
*(int *)(rom_data + addr) = 0x00010140;
if (addr + 5 > limit)
goto error;
*(int*)(rom_data + addr) = 0x00010140;
rom_data[addr + 4] = 0xff;
addr += 5;
}
@@ -861,8 +900,8 @@ int Tracker::WriteSpcData(Rom &rom, void *buf, int len, int addr, int spc,
return 0xc8000;
}
*(short *)(rom_data + addr) = len;
*(short *)(rom_data + addr + 2) = spc;
*(short*)(rom_data + addr) = len;
*(short*)(rom_data + addr + 2) = spc;
memcpy(rom_data + addr + 4, buf, len);
@@ -871,13 +910,13 @@ int Tracker::WriteSpcData(Rom &rom, void *buf, int len, int addr, int spc,
/**
* @brief High-level function to save all modified song data back to the ROM.
* (Currently commented out, but this would orchestrate the entire save process.)
* This function would be responsible for taking all the edited, in-memory
* SongSpcBlocks, arranging them into a final binary layout, patching all the
* relocated pointers, and writing the result back into the ROM file using
* WriteSpcData.
* (Currently commented out, but this would orchestrate the entire save
* process.) This function would be responsible for taking all the edited,
* in-memory SongSpcBlocks, arranging them into a final binary layout, patching
* all the relocated pointers, and writing the result back into the ROM file
* using WriteSpcData.
*/
void Tracker::SaveSongs(Rom &rom) {
void Tracker::SaveSongs(Rom& rom) {
// int i;
// int j;
// int k;
@@ -1076,8 +1115,8 @@ void Tracker::SaveSongs(Rom &rom) {
// (short *)realloc(zelda_wave->buf, (zelda_wave->end + 1) << 1);
// memcpy(zelda_wave->buf, c, zelda_wave->end << 1);
// free(c);
// zelda_wave->buf[zelda_wave->end] = zelda_wave->buf[zelda_wave->lopst];
// zelda_wave2 = waves;
// zelda_wave->buf[zelda_wave->end] =
// zelda_wave->buf[zelda_wave->lopst]; zelda_wave2 = waves;
// for (m = 0; m < numwave; m++, zelda_wave2++)
// if (zelda_wave2->copy == i)
@@ -1192,7 +1231,8 @@ void Tracker::SaveSongs(Rom &rom) {
// }
// // if (sed) {
// // SetDlgItemInt(sed->dlg, ID_Samp_SampleLengthEdit, sed->zelda_wave->end,
// // SetDlgItemInt(sed->dlg, ID_Samp_SampleLengthEdit,
// sed->zelda_wave->end,
// // 0); SetDlgItemInt(sed->dlg, ID_Samp_LoopPointEdit,
// // sed->zelda_wave->lopst, 0);
@@ -1265,7 +1305,8 @@ void Tracker::SaveSongs(Rom &rom) {
// return;
// noerror:
// if (((!sptbl->bank) && stbl->bank < 3) || (sptbl->bank == stbl->bank)) {
// if (((!sptbl->bank) && stbl->bank < 3) || (sptbl->bank == stbl->bank))
// {
// *(unsigned short *)(stbl->buf + stbl->relocs[j]) =
// sptbl->addr + k - sptbl->start;
// } else {
@@ -1331,10 +1372,10 @@ void Tracker::SaveSongs(Rom &rom) {
* @param rom The ROM object.
* @param i The index of the first command of the track to edit.
*/
void Tracker::EditTrack(Rom &rom, short i) {
void Tracker::EditTrack(Rom& rom, short i) {
int j, k, l;
SongRange *sr = song_range_;
SpcCommand *spc_command;
SongRange* sr = song_range_;
SpcCommand* spc_command;
text_buf_ty buf;
@@ -1344,7 +1385,8 @@ void Tracker::EditTrack(Rom &rom, short i) {
spc_command = current_spc_command_;
if (i == -1) return;
if (i == -1)
return;
if (i >= m_size) {
printf("Invalid address: %04X", i);
@@ -1387,13 +1429,13 @@ void Tracker::EditTrack(Rom &rom, short i) {
* @param rom The ROM object.
* @param bank The target bank for the new song.
*/
void Tracker::NewSR(Rom &rom, int bank) {
SpcCommand *spc_command;
SongRange *sr;
void Tracker::NewSR(Rom& rom, int bank) {
SpcCommand* spc_command;
SongRange* sr;
if (srnum == srsize) {
srsize += 16;
song_range_ = (SongRange *)realloc(song_range_, srsize * sizeof(SongRange));
song_range_ = (SongRange*)realloc(song_range_, srsize * sizeof(SongRange));
}
sr = song_range_ + srnum;

View File

@@ -43,34 +43,36 @@ using text_buf_ty = char[512];
* This is the intermediate format used before writing data back to the ROM.
*/
struct SongSpcBlock {
unsigned short start; // The starting address of this block in the virtual SPC memory space.
unsigned short len; // Length of the data buffer.
unsigned short relnum; // Number of relocation entries.
unsigned short relsz; // Allocated size of the relocation table.
unsigned short *relocs; // Table of offsets within 'buf' that are pointers and need to be relocated.
unsigned short bank; // The target sound bank.
unsigned short addr; // The final, relocated address of this block.
unsigned char *buf; // The raw binary data for this block.
int flag; // Flags for managing the block's state.
unsigned short start; // The starting address of this block in the virtual
// SPC memory space.
unsigned short len; // Length of the data buffer.
unsigned short relnum; // Number of relocation entries.
unsigned short relsz; // Allocated size of the relocation table.
unsigned short* relocs; // Table of offsets within 'buf' that are pointers
// and need to be relocated.
unsigned short bank; // The target sound bank.
unsigned short addr; // The final, relocated address of this block.
unsigned char* buf; // The raw binary data for this block.
int flag; // Flags for managing the block's state.
};
/**
* @struct SongRange
* @brief A metadata structure to keep track of parsed sections of the song data.
* Used to avoid re-parsing the same data from the ROM multiple times.
* @brief A metadata structure to keep track of parsed sections of the song
* data. Used to avoid re-parsing the same data from the ROM multiple times.
*/
struct SongRange {
unsigned short start; // Start address of this range in the ROM.
unsigned short end; // End address of this range in the ROM.
unsigned short start; // Start address of this range in the ROM.
unsigned short end; // End address of this range in the ROM.
short first; // Index of the first SpcCommand in this range.
short inst; // Instance count, for tracking usage.
short bank; // The ROM bank this range was loaded from.
short first; // Index of the first SpcCommand in this range.
short inst; // Instance count, for tracking usage.
short bank; // The ROM bank this range was loaded from.
unsigned char endtime; // Default time/duration for this block.
unsigned char endtime; // Default time/duration for this block.
unsigned char filler;
int editor; // Window handle for an associated editor, if any.
int editor; // Window handle for an associated editor, if any.
};
/**
@@ -78,10 +80,10 @@ struct SongRange {
* @brief Represents one of the 8 channels (tracks) in a song.
*/
struct SongPart {
uint8_t flag; // State flags for parsing and saving.
uint8_t inst; // Instance count.
short tbl[8]; // Pointers to the first SpcCommand for each of the 8 channels.
unsigned short addr; // The address of this part's data in the ROM.
uint8_t flag; // State flags for parsing and saving.
uint8_t inst; // Instance count.
short tbl[8]; // Pointers to the first SpcCommand for each of the 8 channels.
unsigned short addr; // The address of this part's data in the ROM.
};
/**
@@ -89,13 +91,13 @@ struct SongPart {
* @brief Represents a complete song, which is a collection of SongParts.
*/
struct Song {
unsigned char flag; // State flags.
unsigned char inst; // Instance count.
SongPart **tbl; // Table of pointers to the song's parts.
short numparts; // Number of parts in the song.
short lopst; // Loop start point.
unsigned short addr; // Address of the song's main data table in the ROM.
bool in_use; // Flag indicating if the song is currently loaded.
unsigned char flag; // State flags.
unsigned char inst; // Instance count.
SongPart** tbl; // Table of pointers to the song's parts.
short numparts; // Number of parts in the song.
short lopst; // Loop start point.
unsigned short addr; // Address of the song's main data table in the ROM.
bool in_use; // Flag indicating if the song is currently loaded.
};
/**
@@ -107,7 +109,7 @@ struct ZeldaWave {
int end; // End of the sample data.
short lflag; // Loop flag.
short copy; // Index of another wave this is a copy of, to save memory.
short *buf; // The buffer containing the decoded PCM sample data.
short* buf; // The buffer containing the decoded PCM sample data.
};
/**
@@ -134,20 +136,21 @@ struct SampleEdit {
int editinst;
ZeldaWave *zw;
ZeldaWave* zw;
};
/**
* @struct ZeldaInstrument
* @brief Defines an instrument for a song, mapping to a sample and ADSR settings.
* @brief Defines an instrument for a song, mapping to a sample and ADSR
* settings.
*/
struct ZeldaInstrument {
unsigned char samp; // Index of the sample (ZeldaWave) to use.
unsigned char ad; // Attack & Decay rates.
unsigned char sr; // Sustain Rate.
unsigned char gain; // Sustain Level & Gain.
unsigned char multhi; // Pitch multiplier (high byte).
unsigned char multlo; // Pitch multiplier (low byte).
unsigned char samp; // Index of the sample (ZeldaWave) to use.
unsigned char ad; // Attack & Decay rates.
unsigned char sr; // Sustain Rate.
unsigned char gain; // Sustain Level & Gain.
unsigned char multhi; // Pitch multiplier (high byte).
unsigned char multlo; // Pitch multiplier (low byte).
};
/**
@@ -167,47 +170,46 @@ struct ZeldaSfxInstrument {
/**
* @struct SpcCommand
* @brief The core data structure representing a single command in a music track.
* A song track is a doubly-linked list of these commands.
* @brief The core data structure representing a single command in a music
* track. A song track is a doubly-linked list of these commands.
*/
struct SpcCommand {
unsigned short addr; // The ROM address this command was loaded from.
short next; // Index of the next command in the list.
short prev; // Index of the previous command in the list.
unsigned char flag; // Bitfield for command properties (e.g., has duration).
unsigned char cmd; // The actual command/opcode.
unsigned char p1; // Parameter 1.
unsigned char p2; // Parameter 2.
unsigned char p3; // Parameter 3.
unsigned char b1; // Note duration or first byte of a multi-byte duration.
unsigned char b2; // Second byte of a multi-byte duration.
unsigned char tim2; // Calculated time/duration component.
unsigned short tim; // Calculated time/duration component.
unsigned short addr; // The ROM address this command was loaded from.
short next; // Index of the next command in the list.
short prev; // Index of the previous command in the list.
unsigned char flag; // Bitfield for command properties (e.g., has duration).
unsigned char cmd; // The actual command/opcode.
unsigned char p1; // Parameter 1.
unsigned char p2; // Parameter 2.
unsigned char p3; // Parameter 3.
unsigned char b1; // Note duration or first byte of a multi-byte duration.
unsigned char b2; // Second byte of a multi-byte duration.
unsigned char tim2; // Calculated time/duration component.
unsigned short tim; // Calculated time/duration component.
};
class Tracker {
public:
SongSpcBlock* AllocSpcBlock(int len, int bank);
SongSpcBlock *AllocSpcBlock(int len, int bank);
unsigned char *GetSpcAddr(Rom &rom, unsigned short addr, short bank);
unsigned char* GetSpcAddr(Rom& rom, unsigned short addr, short bank);
short AllocSpcCommand();
short GetBlockTime(Rom &rom, short num, short prevtime);
short GetBlockTime(Rom& rom, short num, short prevtime);
short SaveSpcCommand(Rom &rom, short num, short songtime, short endtr);
short LoadSpcCommand(Rom &rom, unsigned short addr, short bank, int t);
short SaveSpcCommand(Rom& rom, short num, short songtime, short endtr);
short LoadSpcCommand(Rom& rom, unsigned short addr, short bank, int t);
void SaveSongs(Rom &rom);
void SaveSongs(Rom& rom);
void LoadSongs(Rom &rom);
void LoadSongs(Rom& rom);
int WriteSpcData(Rom &rom, void *buf, int len, int addr, int spc, int limit);
int WriteSpcData(Rom& rom, void* buf, int len, int addr, int spc, int limit);
void EditTrack(Rom &rom, short i);
void EditTrack(Rom& rom, short i);
void NewSR(Rom &rom, int bank);
void NewSR(Rom& rom, int bank);
private:
// A "modified" flag
@@ -236,8 +238,8 @@ class Tracker {
char op_len[32];
char *snddat1;
char *snddat2; // more music stuff.
char* snddat1;
char* snddat2; // more music stuff.
unsigned short ss_next = 0;
unsigned short spclen;
@@ -261,19 +263,19 @@ class Tracker {
size_t t_number;
std::vector<Song> songs;
SongPart *sp_mark;
SongRange *song_range_;
SpcCommand *current_spc_command_;
SongPart* sp_mark;
SongRange* song_range_;
SpcCommand* current_spc_command_;
const SpcCommand& GetSpcCommand(short index) const {
return current_spc_command_[index];
}
SongSpcBlock **ssblt;
SongSpcBlock** ssblt;
ZeldaWave *waves;
ZeldaInstrument *insts;
ZeldaSfxInstrument *sndinsts;
ZeldaWave* waves;
ZeldaInstrument* insts;
ZeldaSfxInstrument* sndinsts;
};
} // namespace music

View File

@@ -1,36 +1,41 @@
#include "overworld.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <future>
#include <mutex>
#include <iostream>
#include <ostream>
#include <set>
#include <thread>
#include <string>
#include <unordered_map>
#include <vector>
#include "absl/status/status.h"
#include "core/features.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "app/gfx/debug/performance/performance_profiler.h"
#include "app/gfx/util/compression.h"
#include "app/gfx/types/snes_tile.h"
#include "app/gfx/util/compression.h"
#include "app/rom.h"
#include "app/snes.h"
#include "zelda3/common.h"
#include "zelda3/overworld/overworld_entrance.h"
#include "zelda3/overworld/overworld_exit.h"
#include "core/features.h"
#include "util/hex.h"
#include "util/log.h"
#include "util/macro.h"
#include "zelda3/common.h"
#include "zelda3/overworld/overworld_entrance.h"
#include "zelda3/overworld/overworld_exit.h"
#include "zelda3/overworld/overworld_item.h"
#include "zelda3/overworld/overworld_map.h"
#include "zelda3/overworld/overworld_version_helper.h"
namespace yaze {
namespace zelda3 {
namespace yaze::zelda3 {
absl::Status Overworld::Load(Rom* rom) {
gfx::ScopedTimer timer("Overworld::Load");
if (rom->size() == 0) {
return absl::InvalidArgumentError("ROM file not loaded");
}
@@ -72,37 +77,37 @@ absl::Status Overworld::Load(Rom* rom) {
// Phase 5: Data Loading (with individual timing)
{
gfx::ScopedTimer data_loading_timer("LoadOverworldData");
{
gfx::ScopedTimer tile_types_timer("LoadTileTypes");
LoadTileTypes();
}
{
gfx::ScopedTimer entrances_timer("LoadEntrances");
ASSIGN_OR_RETURN(all_entrances_, LoadEntrances(rom_));
}
{
gfx::ScopedTimer holes_timer("LoadHoles");
ASSIGN_OR_RETURN(all_holes_, LoadHoles(rom_));
}
{
gfx::ScopedTimer exits_timer("LoadExits");
ASSIGN_OR_RETURN(all_exits_, LoadExits(rom_));
}
{
gfx::ScopedTimer items_timer("LoadItems");
ASSIGN_OR_RETURN(all_items_, LoadItems(rom_, overworld_maps_));
}
{
gfx::ScopedTimer overworld_maps_timer("LoadOverworldMaps");
RETURN_IF_ERROR(LoadOverworldMaps());
}
{
gfx::ScopedTimer sprites_timer("LoadSprites");
RETURN_IF_ERROR(LoadSprites());
@@ -258,15 +263,16 @@ void Overworld::AssignMapSizes(std::vector<OverworldMap>& maps) {
}
}
absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size) {
absl::Status Overworld::ConfigureMultiAreaMap(int parent_index,
AreaSizeEnum size) {
if (parent_index < 0 || parent_index >= kNumOverworldMaps) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid parent index: %d", parent_index));
}
// Check ROM version
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// Version requirements:
// - Vanilla (0xFF): Supports Small and Large only
// - v1-v2: Supports Small and Large only
@@ -276,19 +282,23 @@ absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum siz
return absl::FailedPreconditionError(
"Wide and Tall areas require ZSCustomOverworld v3+");
}
LOG_DEBUG("Overworld", "ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, version=%d",
parent_index, static_cast<int>(overworld_maps_[parent_index].area_size()),
static_cast<int>(size), asm_version);
LOG_DEBUG("Overworld",
"ConfigureMultiAreaMap: parent=%d, current_size=%d, new_size=%d, "
"version=%d",
parent_index,
static_cast<int>(overworld_maps_[parent_index].area_size()),
static_cast<int>(size), asm_version);
// CRITICAL: First, get OLD siblings (before changing) so we can reset them
std::vector<int> old_siblings;
auto old_size = overworld_maps_[parent_index].area_size();
int old_parent = overworld_maps_[parent_index].parent();
switch (old_size) {
case AreaSizeEnum::LargeArea:
old_siblings = {old_parent, old_parent + 1, old_parent + 8, old_parent + 9};
old_siblings = {old_parent, old_parent + 1, old_parent + 8,
old_parent + 9};
break;
case AreaSizeEnum::WideArea:
old_siblings = {old_parent, old_parent + 1};
@@ -300,17 +310,17 @@ absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum siz
old_siblings = {parent_index}; // Was small, just this map
break;
}
// Reset all old siblings to SmallArea first (clean slate)
for (int old_sibling : old_siblings) {
if (old_sibling >= 0 && old_sibling < kNumOverworldMaps) {
overworld_maps_[old_sibling].SetAsSmallMap(old_sibling);
}
}
// Now configure NEW siblings based on requested size
std::vector<int> new_siblings;
switch (size) {
case AreaSizeEnum::SmallArea:
// Just configure this single map as small
@@ -318,45 +328,54 @@ absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum siz
overworld_maps_[parent_index].SetAreaSize(AreaSizeEnum::SmallArea);
new_siblings = {parent_index};
break;
case AreaSizeEnum::LargeArea:
new_siblings = {parent_index, parent_index + 1, parent_index + 8, parent_index + 9};
new_siblings = {parent_index, parent_index + 1, parent_index + 8,
parent_index + 9};
for (size_t i = 0; i < new_siblings.size(); ++i) {
int sibling = new_siblings[i];
if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
if (sibling < 0 || sibling >= kNumOverworldMaps)
continue;
overworld_maps_[sibling].SetAsLargeMap(parent_index, i);
}
break;
case AreaSizeEnum::WideArea:
new_siblings = {parent_index, parent_index + 1};
for (int sibling : new_siblings) {
if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
if (sibling < 0 || sibling >= kNumOverworldMaps)
continue;
overworld_maps_[sibling].SetParent(parent_index);
overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::WideArea);
}
break;
case AreaSizeEnum::TallArea:
new_siblings = {parent_index, parent_index + 8};
for (int sibling : new_siblings) {
if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
if (sibling < 0 || sibling >= kNumOverworldMaps)
continue;
overworld_maps_[sibling].SetParent(parent_index);
overworld_maps_[sibling].SetAreaSize(AreaSizeEnum::TallArea);
}
break;
}
// Update ROM data for ALL affected siblings (old + new)
std::set<int> all_affected;
for (int s : old_siblings) all_affected.insert(s);
for (int s : new_siblings) all_affected.insert(s);
for (int sibling : old_siblings) {
all_affected.insert(sibling);
}
for (int sibling : new_siblings) {
all_affected.insert(sibling);
}
if (asm_version >= 3 && asm_version != 0xFF) {
// v3+: Update expanded tables
for (int sibling : all_affected) {
if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
if (sibling < 0 || sibling >= kNumOverworldMaps)
continue;
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentIdExpanded + sibling,
overworld_maps_[sibling].parent()));
RETURN_IF_ERROR(rom()->WriteByte(
@@ -366,8 +385,9 @@ absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum siz
} else if (asm_version < 3 && asm_version != 0xFF) {
// v1/v2: Update basic parent table
for (int sibling : all_affected) {
if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
if (sibling < 0 || sibling >= kNumOverworldMaps)
continue;
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling,
overworld_maps_[sibling].parent()));
RETURN_IF_ERROR(rom()->WriteByte(
@@ -377,22 +397,27 @@ absl::Status Overworld::ConfigureMultiAreaMap(int parent_index, AreaSizeEnum siz
} else {
// Vanilla: Update parent and screen size tables
for (int sibling : all_affected) {
if (sibling < 0 || sibling >= kNumOverworldMaps) continue;
if (sibling < 0 || sibling >= kNumOverworldMaps)
continue;
RETURN_IF_ERROR(rom()->WriteByte(kOverworldMapParentId + sibling,
overworld_maps_[sibling].parent()));
RETURN_IF_ERROR(rom()->WriteByte(
kOverworldScreenSize + (sibling & 0x3F),
(overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea) ? 0x00 : 0x01));
(overworld_maps_[sibling].area_size() == AreaSizeEnum::LargeArea)
? 0x00
: 0x01));
}
}
LOG_DEBUG("Overworld", "Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
(size == AreaSizeEnum::LargeArea) ? "Large" :
(size == AreaSizeEnum::WideArea) ? "Wide" :
(size == AreaSizeEnum::TallArea) ? "Tall" : "Small",
parent_index, old_siblings.size(), new_siblings.size());
LOG_DEBUG("Overworld",
"Configured %s area: parent=%d, old_siblings=%zu, new_siblings=%zu",
(size == AreaSizeEnum::LargeArea) ? "Large"
: (size == AreaSizeEnum::WideArea) ? "Wide"
: (size == AreaSizeEnum::TallArea) ? "Tall"
: "Small",
parent_index, old_siblings.size(), new_siblings.size());
return absl::OkStatus();
}
@@ -416,7 +441,8 @@ absl::Status Overworld::AssembleMap32Tiles() {
rom()->version_constants().kMap32TileBR};
// Check if expanded tile32 data is actually present in ROM
// The flag position should contain 0x04 for vanilla, something else for expanded
// The flag position should contain 0x04 for vanilla, something else for
// expanded
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
uint8_t expanded_flag = rom()->data()[kMap32ExpandedFlagPos];
util::logf("Expanded tile32 flag: %d", expanded_flag);
@@ -472,7 +498,8 @@ absl::Status Overworld::AssembleMap16Tiles() {
int num_tile16 = kNumTile16Individual;
// Check if expanded tile16 data is actually present in ROM
// The flag position should contain 0x0F for vanilla, something else for expanded
// The flag position should contain 0x0F for vanilla, something else for
// expanded
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
uint8_t expanded_flag = rom()->data()[kMap16ExpandedFlagPos];
util::logf("Expanded tile16 flag: %d", expanded_flag);
@@ -534,17 +561,7 @@ void Overworld::OrganizeMapTiles(std::vector<uint8_t>& bytes,
}
}
void Overworld::DecompressAllMapTiles() {
// Keep original method for fallback/compatibility
// Note: This method is void, so we can't return status
// The parallel version will be called from Load()
}
absl::Status Overworld::DecompressAllMapTilesParallel() {
// For now, fall back to the original sequential implementation
// The parallel version has synchronization issues that cause data corruption
util::logf("Using sequential decompression (parallel version disabled due to data integrity issues)");
const auto get_ow_map_gfx_ptr = [this](int index, uint32_t map_ptr) {
int p = (rom()->data()[map_ptr + 2 + (3 * index)] << 16) +
(rom()->data()[map_ptr + 1 + (3 * index)] << 8) +
@@ -560,7 +577,7 @@ absl::Status Overworld::DecompressAllMapTilesParallel() {
int sx = 0;
int sy = 0;
int c = 0;
for (int i = 0; i < kNumOverworldMaps; i++) {
auto p1 = get_ow_map_gfx_ptr(
i, rom()->version_constants().kCompressedAllMap32PointersHigh);
@@ -603,22 +620,28 @@ absl::Status Overworld::DecompressAllMapTilesParallel() {
absl::Status Overworld::LoadOverworldMaps() {
auto size = tiles16_.size();
// Performance optimization: Only build essential maps initially
// Essential maps are the first few maps of each world that are commonly accessed
constexpr int kEssentialMapsPerWorld = 8; // Build first 8 maps of each world
// Essential maps are the first few maps of each world that are commonly
// accessed
constexpr int kEssentialMapsPerWorld = 16;
constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
constexpr int kDarkWorldEssential = kDarkWorldMapIdStart + kEssentialMapsPerWorld;
constexpr int kSpecialWorldEssential = kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
util::logf("Building essential maps only (first %d maps per world) for faster loading", kEssentialMapsPerWorld);
constexpr int kDarkWorldEssential =
kDarkWorldMapIdStart + kEssentialMapsPerWorld;
constexpr int kSpecialWorldEssential =
kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
util::logf(
"Building essential maps only (first %d maps per world) for faster "
"loading",
kEssentialMapsPerWorld);
std::vector<std::future<absl::Status>> futures;
// Build essential maps only
for (int i = 0; i < kNumOverworldMaps; ++i) {
bool is_essential = false;
// Check if this is an essential map
if (i < kLightWorldEssential) {
is_essential = true;
@@ -627,7 +650,7 @@ absl::Status Overworld::LoadOverworldMaps() {
} else if (i >= kSpecialWorldMapIdStart && i < kSpecialWorldEssential) {
is_essential = true;
}
if (is_essential) {
int world_type = 0;
if (i >= kDarkWorldMapIdStart && i < kSpecialWorldMapIdStart) {
@@ -635,7 +658,7 @@ absl::Status Overworld::LoadOverworldMaps() {
} else if (i >= kSpecialWorldMapIdStart) {
world_type = 2;
}
auto task_function = [this, i, size, world_type]() {
return overworld_maps_[i].BuildMap(size, game_state_, world_type,
tiles16_, GetMapTiles(world_type));
@@ -652,7 +675,7 @@ absl::Status Overworld::LoadOverworldMaps() {
future.wait();
RETURN_IF_ERROR(future.get());
}
util::logf("Essential maps built. Remaining maps will be built on-demand.");
return absl::OkStatus();
}
@@ -661,21 +684,22 @@ absl::Status Overworld::EnsureMapBuilt(int map_index) {
if (map_index < 0 || map_index >= kNumOverworldMaps) {
return absl::InvalidArgumentError("Invalid map index");
}
// Check if map is already built
if (overworld_maps_[map_index].is_built()) {
return absl::OkStatus();
}
// Build the map on-demand
auto size = tiles16_.size();
int world_type = 0;
if (map_index >= kDarkWorldMapIdStart && map_index < kSpecialWorldMapIdStart) {
if (map_index >= kDarkWorldMapIdStart &&
map_index < kSpecialWorldMapIdStart) {
world_type = 1;
} else if (map_index >= kSpecialWorldMapIdStart) {
world_type = 2;
}
return overworld_maps_[map_index].BuildMap(size, game_state_, world_type,
tiles16_, GetMapTiles(world_type));
}
@@ -2402,7 +2426,8 @@ absl::Status Overworld::SaveMap16Tiles() {
}
absl::Status Overworld::SaveEntrances() {
RETURN_IF_ERROR(::yaze::zelda3::SaveEntrances(rom_, all_entrances_, expanded_entrances_));
RETURN_IF_ERROR(
::yaze::zelda3::SaveEntrances(rom_, all_entrances_, expanded_entrances_));
RETURN_IF_ERROR(SaveHoles(rom_, all_holes_));
return absl::OkStatus();
}
@@ -2687,5 +2712,4 @@ absl::Status Overworld::SaveAreaSizes() {
return absl::OkStatus();
}
} // namespace zelda3
} // namespace yaze
} // namespace yaze::zelda3

View File

@@ -3,8 +3,8 @@
#include <array>
#include <cstdint>
#include <vector>
#include <mutex>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
@@ -87,8 +87,8 @@ constexpr int kMap32TileTRExpanded = 0x020000;
constexpr int kMap32TileBLExpanded = 0x1F0000;
constexpr int kMap32TileBRExpanded = 0x1F8000;
constexpr int kMap32TileCountExpanded = 0x0067E0;
constexpr int kMap32ExpandedFlagPos = 0x01772E; // 0x04
constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F
constexpr int kMap32ExpandedFlagPos = 0x01772E; // 0x04
constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F
constexpr int overworldSpritesBeginingExpanded = 0x141438;
constexpr int overworldSpritesZeldaExpanded = 0x141578;
@@ -127,56 +127,59 @@ constexpr int kNumMapsPerWorld = 0x40;
*/
class Overworld {
public:
Overworld(Rom *rom) : rom_(rom) {}
Overworld(Rom* rom) : rom_(rom) {}
absl::Status Load(Rom *rom);
absl::Status Load(Rom* rom);
absl::Status LoadOverworldMaps();
void LoadTileTypes();
absl::Status LoadSprites();
absl::Status LoadSpritesFromMap(int sprite_start, int sprite_count,
int sprite_index);
/**
* @brief Build a map on-demand if it hasn't been built yet
*
*
* This method checks if the specified map needs to be built and builds it
* if necessary. Used for lazy loading optimization.
*/
absl::Status EnsureMapBuilt(int map_index);
absl::Status Save(Rom *rom);
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 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 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();
@@ -189,13 +192,13 @@ class Overworld {
absl::Status SaveMusic();
absl::Status SaveAreaSizes();
void AssignMapSizes(std::vector<OverworldMap>& maps);
/**
* @brief Configure a multi-area map structure (Large/Wide/Tall)
* @param parent_index The parent map index
* @param size The area size to configure
* @return Status of the configuration
*
*
* Properly sets up sibling relationships and updates ROM data for v3+.
*/
absl::Status ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size);
@@ -204,14 +207,14 @@ class Overworld {
auto mutable_rom() { return rom_; }
void Destroy() {
for (auto &map : overworld_maps_) {
for (auto& map : overworld_maps_) {
map.Destroy();
}
overworld_maps_.clear();
all_entrances_.clear();
all_exits_.clear();
all_items_.clear();
for (auto &sprites : all_sprites_) {
for (auto& sprites : all_sprites_) {
sprites.clear();
}
tiles16_.clear();
@@ -230,7 +233,7 @@ class Overworld {
}
}
OverworldBlockset &GetMapTiles(int world_type) {
OverworldBlockset& GetMapTiles(int world_type) {
switch (world_type) {
case 0:
return map_tiles_.light_world;
@@ -256,11 +259,13 @@ class Overworld {
auto current_graphics() const {
return overworld_maps_[current_map_].current_graphics();
}
const std::vector<OverworldEntrance> &entrances() const { return all_entrances_; }
auto &entrances() { return all_entrances_; }
const std::vector<OverworldEntrance>& entrances() const {
return all_entrances_;
}
auto& entrances() { return all_entrances_; }
auto mutable_entrances() { return &all_entrances_; }
const std::vector<OverworldEntrance> &holes() const { return all_holes_; }
auto &holes() { return all_holes_; }
const std::vector<OverworldEntrance>& holes() const { return all_holes_; }
auto& holes() { return all_holes_; }
auto mutable_holes() { return &all_holes_; }
auto deleted_entrances() const { return deleted_entrances_; }
auto mutable_deleted_entrances() { return &deleted_entrances_; }
@@ -316,18 +321,17 @@ class Overworld {
void FetchLargeMaps();
absl::StatusOr<uint16_t> GetTile16ForTile32(int index, int quadrant,
int dimension,
const uint32_t *map32address);
const uint32_t* map32address);
absl::Status AssembleMap32Tiles();
absl::Status AssembleMap16Tiles();
void AssignWorldTiles(int x, int y, int sx, int sy, int tpos,
OverworldBlockset &world);
void OrganizeMapTiles(std::vector<uint8_t> &bytes,
std::vector<uint8_t> &bytes2, int i, int sx, int sy,
int &ttpos);
void DecompressAllMapTiles();
OverworldBlockset& world);
void OrganizeMapTiles(std::vector<uint8_t>& bytes,
std::vector<uint8_t>& bytes2, int i, int sx, int sy,
int& ttpos);
absl::Status DecompressAllMapTilesParallel();
Rom *rom_;
Rom* rom_;
bool is_loaded_ = false;
bool expanded_tile16_ = false;

View File

@@ -1,5 +1,7 @@
#include "zelda3/overworld/overworld_entrance.h"
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
@@ -15,7 +17,8 @@ absl::StatusOr<std::vector<OverworldEntrance>> LoadEntrances(Rom* rom) {
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
// 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;
@@ -66,12 +69,13 @@ absl::StatusOr<std::vector<OverworldEntrance>> LoadHoles(Rom* rom) {
}
absl::Status SaveEntrances(Rom* rom,
const std::vector<OverworldEntrance>& entrances, bool expanded_entrances) {
const std::vector<OverworldEntrance>& 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.
// 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_));

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,36 @@ 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;
@@ -136,8 +140,7 @@ absl::StatusOr<std::vector<OverworldEntrance>> LoadHoles(Rom* rom);
absl::Status SaveEntrances(Rom* rom,
const std::vector<OverworldEntrance>& entrances,
bool expanded_entrances);
absl::Status SaveHoles(Rom* rom,
const std::vector<OverworldEntrance>& holes);
absl::Status SaveHoles(Rom* rom, const std::vector<OverworldEntrance>& holes);
inline absl::StatusOr<OverworldEntranceTileTypes> LoadEntranceTileTypes(
Rom* rom) {

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,41 +45,152 @@ 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_scroll_mod_y, exit_scroll_mod_x, exit_door_type_1,
exit_door_type_2, (px & py) == 0xFFFF);
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,
(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) {
// 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.
// 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
// TODO(scawful): Implement SpecialUpdatePosition for OverworldExit
// Similar to ZScream Save.cs:1034-1039
// if (all_exits_.size() > 0x4D) {
// all_exits_[0x4D].SpecialUpdatePosition();
// }
}
for (int i = 0; i < kNumOverworldExits; i++) {
RETURN_IF_ERROR(
rom->WriteShort(OWExitRoomId + (i * 2), exits[i].room_id_));
RETURN_IF_ERROR(rom->WriteShort(OWExitRoomId + (i * 2), exits[i].room_id_));
RETURN_IF_ERROR(rom->WriteByte(OWExitMapId + i, exits[i].map_id_));
RETURN_IF_ERROR(
rom->WriteShort(OWExitVram + (i * 2), exits[i].map_pos_));
RETURN_IF_ERROR(rom->WriteShort(OWExitVram + (i * 2), exits[i].map_pos_));
RETURN_IF_ERROR(
rom->WriteShort(OWExitYScroll + (i * 2), exits[i].y_scroll_));
RETURN_IF_ERROR(
@@ -86,24 +203,22 @@ absl::Status SaveExits(Rom* rom, const std::vector<OverworldExit>& exits) {
rom->WriteShort(OWExitYCamera + (i * 2), exits[i].y_camera_));
RETURN_IF_ERROR(
rom->WriteShort(OWExitXCamera + (i * 2), exits[i].x_camera_));
RETURN_IF_ERROR(rom->WriteByte(OWExitUnk1 + i, exits[i].scroll_mod_y_));
RETURN_IF_ERROR(rom->WriteByte(OWExitUnk2 + i, exits[i].scroll_mod_x_));
RETURN_IF_ERROR(
rom->WriteByte(OWExitUnk1 + i, exits[i].scroll_mod_y_));
rom->WriteShort(OWExitDoorType1 + (i * 2), exits[i].door_type_1_));
RETURN_IF_ERROR(
rom->WriteByte(OWExitUnk2 + i, exits[i].scroll_mod_x_));
RETURN_IF_ERROR(rom->WriteShort(OWExitDoorType1 + (i * 2),
exits[i].door_type_1_));
RETURN_IF_ERROR(rom->WriteShort(OWExitDoorType2 + (i * 2),
exits[i].door_type_2_));
rom->WriteShort(OWExitDoorType2 + (i * 2), exits[i].door_type_2_));
if (exits[i].room_id_ == 0x0180) {
RETURN_IF_ERROR(rom->WriteByte(OWExitDoorPosition + 0,
exits[i].map_id_ & 0xFF));
RETURN_IF_ERROR(
rom->WriteByte(OWExitDoorPosition + 0, exits[i].map_id_ & 0xFF));
} else if (exits[i].room_id_ == 0x0181) {
RETURN_IF_ERROR(rom->WriteByte(OWExitDoorPosition + 2,
exits[i].map_id_ & 0xFF));
RETURN_IF_ERROR(
rom->WriteByte(OWExitDoorPosition + 2, exits[i].map_id_ & 0xFF));
} else if (exits[i].room_id_ == 0x0182) {
RETURN_IF_ERROR(rom->WriteByte(OWExitDoorPosition + 4,
exits[i].map_id_ & 0xFF));
RETURN_IF_ERROR(
rom->WriteByte(OWExitDoorPosition + 4, exits[i].map_id_ & 0xFF));
}
}

View File

@@ -1,14 +1,21 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_EXIT_H
#define YAZE_APP_ZELDA3_OVERWORLD_EXIT_H
#include <algorithm>
#include <cstdint>
#include <iostream>
#include <cstdlib>
#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,112 @@ 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));
// 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));
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);
// Door position calculations (used by door editor, not for coordinates)
// ZScream: ExitOW.cs:145-157
// These update DoorXEditor/DoorYEditor, NOT AreaX/AreaY
}
// 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_);
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));
}
/**
* @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

@@ -187,7 +187,8 @@ absl::Status SaveItems(Rom* rom, const std::vector<OverworldItem>& items) {
static_cast<uint8_t>(
(PcToSnes(zelda3::kOverworldItemsStartDataNew) >> 16) & 0xFF)));
// Clear pointer table (write zero) to avoid stale values when pointer count shrinks
// Clear pointer table (write zero) to avoid stale values when pointer count
// shrinks
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
RETURN_IF_ERROR(
rom->WriteShort(zelda3::kOverworldItemsPointersNew + (i * 2), 0x0000));

View File

@@ -30,9 +30,9 @@ constexpr int kOverworldItemsPointersNew = 0x012784;
constexpr int kOverworldItemsStartDataNew = 0x0DC2F9;
constexpr int overworldItemsPointers = 0x0DC2F9;
constexpr int overworldItemsAddress = 0x0DC8B9; // 1BC2F9
constexpr int overworldItemsAddress = 0x0DC8B9; // 1BC2F9
constexpr int overworldItemsAddressBank = 0x0DC8BF;
constexpr int overworldItemsEndData = 0x0DC89C; // 0DC89E
constexpr int overworldItemsEndData = 0x0DC89C; // 0DC89E
constexpr int overworldBombDoorItemLocationsNew = 0x012644;
constexpr int overworldItemsPointersNew = 0x012784;
@@ -45,27 +45,30 @@ 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);
}
@@ -99,7 +102,7 @@ inline bool CompareOverworldItems(const std::vector<OverworldItem>& items1,
}
inline bool CompareItemsArrays(std::vector<OverworldItem> item_array1,
std::vector<OverworldItem> item_array2) {
std::vector<OverworldItem> item_array2) {
if (item_array1.size() != item_array2.size()) {
return false;
}
@@ -125,7 +128,8 @@ inline bool CompareItemsArrays(std::vector<OverworldItem> item_array1,
return true;
}
absl::StatusOr<std::vector<OverworldItem>> LoadItems(Rom* rom, std::vector<OverworldMap>& overworld_maps);
absl::StatusOr<std::vector<OverworldItem>> LoadItems(
Rom* rom, std::vector<OverworldMap>& overworld_maps);
absl::Status SaveItems(Rom* rom, const std::vector<OverworldItem>& items);
const std::vector<std::string> kSecretItemNames = {

View File

@@ -1,34 +1,42 @@
#include "overworld_map.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <unordered_map>
#include <vector>
#include "core/features.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/gfx/types/snes_color.h"
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
#include "core/features.h"
#include "util/macro.h"
#include "zelda3/common.h"
#include "zelda3/overworld/overworld.h"
#include "zelda3/overworld/overworld_version_helper.h"
namespace yaze {
namespace zelda3 {
namespace yaze::zelda3 {
OverworldMap::OverworldMap(int index, Rom* rom)
: index_(index), parent_(index), rom_(rom) {
LoadAreaInfo();
// Load parent ID from ROM data if available (for custom ASM versions)
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
if (asm_version != 0xFF && asm_version >= 0x03) {
auto version = OverworldVersionHelper::GetVersion(*rom_);
if (version != OverworldVersion::kVanilla &&
version >= OverworldVersion::kZSCustomV3) {
// For v3+, parent ID is stored in expanded table
parent_ = (*rom_)[kOverworldMapParentIdExpanded + index_];
}
if (asm_version != 0xFF) {
if (asm_version == 0x03) {
if (version != OverworldVersion::kVanilla) {
if (version == OverworldVersion::kZSCustomV3) {
LoadCustomOverworldData();
} else {
SetupCustomTileset(asm_version);
SetupCustomTileset(OverworldVersionHelper::GetAsmVersion(*rom_));
}
} else if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
// Pure vanilla ROM but flag enabled - set up custom data manually
@@ -42,11 +50,11 @@ absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
OverworldBlockset& world_blockset) {
game_state_ = game_state;
world_ = world;
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
auto version = OverworldVersionHelper::GetVersion(*rom_);
// For large maps in vanilla ROMs, we need to handle special world graphics
// This ensures proper rendering of special overworld areas like Zora's Domain
if (large_map_ && (asm_version == 0xFF || asm_version == 0x00)) {
if (large_map_ && (version == OverworldVersion::kVanilla)) {
if (parent_ != index_ && !initialized_) {
if (index_ >= kSpecialWorldMapIdStart && index_ <= 0x8A &&
index_ != 0x88) {
@@ -79,7 +87,7 @@ absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
}
void OverworldMap::LoadAreaInfo() {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
auto version = OverworldVersionHelper::GetVersion(*rom_);
// ZSCustomOverworld ASM Version System:
// 0x00-0x02: Legacy versions with limited features
@@ -87,7 +95,8 @@ void OverworldMap::LoadAreaInfo() {
// 0xFF: Pure vanilla ROM (no ASM applied)
// Load message ID and area size based on ASM version
if (asm_version < 3 || asm_version == 0xFF) {
if (version < OverworldVersion::kZSCustomV2 ||
version == OverworldVersion::kVanilla) {
// v2 and vanilla: use original message table
message_id_ = (*rom_)[kOverworldMessageIds + (parent_ * 2)] |
((*rom_)[kOverworldMessageIds + (parent_ * 2) + 1] << 8);
@@ -122,7 +131,8 @@ void OverworldMap::LoadAreaInfo() {
}
} else {
// v3: use expanded message table and area size table
// All area sizes are now stored in the expanded table, supporting all size types
// All area sizes are now stored in the expanded table, supporting all size
// types
message_id_ =
(*rom_)[kOverworldMessagesExpanded + (parent_ * 2)] |
((*rom_)[kOverworldMessagesExpanded + (parent_ * 2) + 1] << 8);
@@ -157,7 +167,8 @@ void OverworldMap::LoadAreaInfo() {
area_music_[3] = (*rom_)[kOverworldMusicAgahnim + parent_];
// For v2/vanilla, use original palette table
if (asm_version < 3 || asm_version == 0xFF) {
if (version < OverworldVersion::kZSCustomV2 ||
version == OverworldVersion::kVanilla) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
}
} else if (index_ < kSpecialWorldMapIdStart) {
@@ -183,7 +194,8 @@ void OverworldMap::LoadAreaInfo() {
(*rom_)[kOverworldMusicDarkWorld + (parent_ - kDarkWorldMapIdStart)];
// For v2/vanilla, use original palette table
if (asm_version < 3 || asm_version == 0xFF) {
if (version < OverworldVersion::kZSCustomV2 ||
version == OverworldVersion::kVanilla) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
}
} else {
@@ -191,7 +203,7 @@ void OverworldMap::LoadAreaInfo() {
// Message ID already loaded above based on ASM version
// For v3, use expanded sprite tables with full customization support
if (asm_version >= 3 && asm_version != 0xFF) {
if (version == OverworldVersion::kZSCustomV3) {
sprite_graphics_[0] =
(*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
kSpecialWorldMapIdStart];
@@ -230,7 +242,8 @@ void OverworldMap::LoadAreaInfo() {
// For v2/vanilla, use original palette table and handle special cases
// These hardcoded cases are needed for vanilla compatibility
if (asm_version < 3 || asm_version == 0xFF) {
if (version < OverworldVersion::kZSCustomV2 ||
version == OverworldVersion::kVanilla) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
// Handle special world area cases based on ZScream documentation
@@ -513,7 +526,8 @@ void OverworldMap::LoadMainBlocksetId() {
parent_ < kSpecialWorldMapIdStart) {
main_gfx_id_ = 0x21;
} else if (parent_ >= kSpecialWorldMapIdStart) {
// Special world maps - use appropriate graphics ID based on the specific map
// Special world maps - use appropriate graphics ID based on the specific
// map
if (parent_ == 0x88) {
main_gfx_id_ = 0x24;
} else {
@@ -862,24 +876,22 @@ absl::Status OverworldMap::LoadPalette() {
}
absl::Status OverworldMap::LoadOverlay() {
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// Load overlays based on ROM version
if (asm_version == 0xFF) {
if (OverworldVersionHelper::GetVersion(*rom_) == OverworldVersion::kVanilla) {
// Vanilla ROM - load overlay from overlay pointers
return LoadVanillaOverlayData();
} else {
// Custom overworld ROM - use overlay from custom data
overlay_id_ = subscreen_overlay_;
has_overlay_ = (overlay_id_ != 0x00FF);
overlay_data_.clear();
return absl::OkStatus();
}
// Custom overworld ROM - use overlay from custom data
overlay_id_ = subscreen_overlay_;
has_overlay_ = (overlay_id_ != 0x00FF);
overlay_data_.clear();
return absl::OkStatus();
}
absl::Status OverworldMap::LoadVanillaOverlayData() {
// Load vanilla overlay for this map (interactive overlays for revealing holes/changing elements)
// Load vanilla overlay for this map (interactive overlays for revealing
// holes/changing elements)
int address = (kOverlayPointersBank << 16) +
((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) +
(*rom_)[kOverlayPointers + (index_ * 2)];
@@ -1125,5 +1137,4 @@ absl::Status OverworldMap::BuildBitmap(OverworldBlockset& world_blockset) {
return absl::OkStatus();
}
} // namespace zelda3
} // namespace yaze
} // namespace yaze::zelda3

View File

@@ -10,6 +10,7 @@
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
#include "zelda3/overworld/overworld_version_helper.h"
namespace yaze {
namespace zelda3 {
@@ -23,20 +24,29 @@ constexpr int OverworldCustomAreaSpecificBGPalette = 0x140000;
constexpr int OverworldCustomAreaSpecificBGEnabled = 0x140140;
// Additional v3 constants
constexpr int OverworldCustomSubscreenOverlayArray = 0x140340; // 2 bytes for each overworld area (0x140)
constexpr int OverworldCustomSubscreenOverlayEnabled = 0x140144; // 1 byte, not 0 if enabled
constexpr int OverworldCustomAnimatedGFXArray = 0x1402A0; // 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomAnimatedGFXEnabled = 0x140143; // 1 byte, not 0 if enabled
constexpr int OverworldCustomTileGFXGroupArray = 0x140480; // 8 bytes for each overworld area (0x500)
constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148; // 1 byte, not 0 if enabled
constexpr int OverworldCustomMosaicArray = 0x140200; // 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomMosaicEnabled = 0x140142; // 1 byte, not 0 if enabled
constexpr int OverworldCustomSubscreenOverlayArray =
0x140340; // 2 bytes for each overworld area (0x140)
constexpr int OverworldCustomSubscreenOverlayEnabled =
0x140144; // 1 byte, not 0 if enabled
constexpr int OverworldCustomAnimatedGFXArray =
0x1402A0; // 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomAnimatedGFXEnabled =
0x140143; // 1 byte, not 0 if enabled
constexpr int OverworldCustomTileGFXGroupArray =
0x140480; // 8 bytes for each overworld area (0x500)
constexpr int OverworldCustomTileGFXGroupEnabled =
0x140148; // 1 byte, not 0 if enabled
constexpr int OverworldCustomMosaicArray =
0x140200; // 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomMosaicEnabled =
0x140142; // 1 byte, not 0 if enabled
// Vanilla overlay constants
constexpr int kOverlayPointers = 0x77664; // 2 bytes for each overworld area (0x100)
constexpr int kOverlayPointers =
0x77664; // 2 bytes for each overworld area (0x100)
constexpr int kOverlayPointersBank = 0x0E; // Bank for overlay pointers
constexpr int kOverlayData1 = 0x77676; // Check for custom overlay code
constexpr int kOverlayData2 = 0x77677; // Custom overlay data pointer
constexpr int kOverlayData1 = 0x77676; // Check for custom overlay code
constexpr int kOverlayData2 = 0x77677; // Custom overlay data pointer
constexpr int kOverlayCodeStart = 0x77657; // Start of overlay code
// 1 byte for each overworld area (0xA0)
@@ -44,7 +54,6 @@ constexpr int OverworldCustomMainPaletteArray = 0x140160;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomMainPaletteEnabled = 0x140141;
// v3 expanded constants
constexpr int kOverworldMessagesExpanded = 0x1417F8;
constexpr int kOverworldMapParentIdExpanded = 0x140998;
@@ -83,13 +92,6 @@ 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.
*/
@@ -122,7 +124,7 @@ class OverworldMap : public gfx::GfxContext {
auto parent() const { return parent_; }
auto mutable_mosaic() { return &mosaic_; }
auto mutable_current_palette() { return &current_palette_; }
void SetNotBuilt() { built_ = false; }
auto area_graphics() const { return area_graphics_; }
@@ -150,16 +152,22 @@ class OverworldMap : public gfx::GfxContext {
void set_animated_gfx(uint8_t gfx) { animated_gfx_ = gfx; }
auto custom_tileset(int index) const { return custom_gfx_ids_[index]; }
// Overlay accessors (interactive overlays)
auto overlay_id() const { return overlay_id_; }
auto has_overlay() const { return has_overlay_; }
const auto& overlay_data() const { return overlay_data_; }
// Mosaic expanded accessors
const std::array<bool, 4>& mosaic_expanded() const { return mosaic_expanded_; }
void set_mosaic_expanded(int index, bool value) { mosaic_expanded_[index] = value; }
void set_custom_tileset(int index, uint8_t value) { custom_gfx_ids_[index] = value; }
const std::array<bool, 4>& mosaic_expanded() const {
return mosaic_expanded_;
}
void set_mosaic_expanded(int index, bool value) {
mosaic_expanded_[index] = value;
}
void set_custom_tileset(int index, uint8_t value) {
custom_gfx_ids_[index] = value;
}
auto mutable_current_graphics() { return &current_gfx_; }
auto mutable_area_graphics() { return &area_graphics_; }
@@ -204,10 +212,8 @@ class OverworldMap : public gfx::GfxContext {
area_size_ = size;
large_map_ = (size == AreaSizeEnum::LargeArea);
}
void SetParent(int parent_index) {
parent_ = parent_index;
}
void SetParent(int parent_index) { parent_ = parent_index; }
void Destroy() {
current_blockset_.clear();

View File

@@ -0,0 +1,153 @@
#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;
}
if (asm_version == 1) {
return OverworldVersion::kZSCustomV1;
}
if (asm_version == 2) {
return OverworldVersion::kZSCustomV2;
}
if (asm_version >= 3) {
return OverworldVersion::kZSCustomV3;
}
// Fallback for unknown values
return OverworldVersion::kVanilla;
}
static uint8_t GetAsmVersion(const Rom& rom) {
return rom.data()[OverworldCustomASMHasBeenApplied];
}
/**
* @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

@@ -84,215 +84,233 @@ constexpr int kOverworldMiniMap = 2;
// ============================================================================
struct PaletteGroupMetadata {
const char* group_id; // Unique identifier (e.g., "ow_main")
const char* display_name; // Human-readable name
const char* category; // Category (e.g., "Overworld", "Dungeon")
uint32_t base_address; // ROM address
int palette_count; // Number of palettes
int colors_per_palette; // Colors in each palette
int colors_per_row; // How many colors per row in UI
int bits_per_pixel; // Color depth (typically 4 for SNES)
const char* description; // Usage description
bool has_animations; // Whether palettes animate
const char* group_id; // Unique identifier (e.g., "ow_main")
const char* display_name; // Human-readable name
const char* category; // Category (e.g., "Overworld", "Dungeon")
uint32_t base_address; // ROM address
int palette_count; // Number of palettes
int colors_per_palette; // Colors in each palette
int colors_per_row; // How many colors per row in UI
int bits_per_pixel; // Color depth (typically 4 for SNES)
const char* description; // Usage description
bool has_animations; // Whether palettes animate
};
// Predefined metadata for all palette groups
namespace PaletteMetadata {
constexpr PaletteGroupMetadata kOverworldMain = {
.group_id = PaletteGroupName::kOverworldMain,
.display_name = "Overworld Main",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldMain,
.palette_count = PaletteCount::kOverworldMain,
.colors_per_palette = 35, // 35 colors: 2 full rows (0-15, 16-31) + 3 colors (32-34)
.colors_per_row = 7, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description = "Main overworld palettes: 35 colors per set (2 full rows + 3 colors)",
.has_animations = false
};
.group_id = PaletteGroupName::kOverworldMain,
.display_name = "Overworld Main",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldMain,
.palette_count = PaletteCount::kOverworldMain,
.colors_per_palette =
35, // 35 colors: 2 full rows (0-15, 16-31) + 3 colors (32-34)
.colors_per_row = 7, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description =
"Main overworld palettes: 35 colors per set (2 full rows + 3 colors)",
.has_animations = false};
constexpr PaletteGroupMetadata kOverworldAnimated = {
.group_id = PaletteGroupName::kOverworldAnimated,
.display_name = "Overworld Animated",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldAnimated,
.palette_count = PaletteCount::kOverworldAnimated,
.colors_per_palette = 7, // 7 colors (overlay palette, no transparent)
.colors_per_row = 8, // Display in 8-color groups
.bits_per_pixel = 4,
.description = "Animated overlay palettes: 7 colors per set (water, lava, etc.)",
.has_animations = true
};
.group_id = PaletteGroupName::kOverworldAnimated,
.display_name = "Overworld Animated",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldAnimated,
.palette_count = PaletteCount::kOverworldAnimated,
.colors_per_palette = 7, // 7 colors (overlay palette, no transparent)
.colors_per_row = 8, // Display in 8-color groups
.bits_per_pixel = 4,
.description =
"Animated overlay palettes: 7 colors per set (water, lava, etc.)",
.has_animations = true};
constexpr PaletteGroupMetadata kDungeonMain = {
.group_id = PaletteGroupName::kDungeonMain,
.display_name = "Dungeon Main",
.category = "Dungeon",
.base_address = PaletteAddress::kDungeonMain,
.palette_count = PaletteCount::kDungeonMain,
.colors_per_palette = 90, // 90 colors: 5 full rows (0-15, 16-31, 32-47, 48-63, 64-79) + 10 colors (80-89)
.colors_per_row = 16, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description = "Dungeon-specific palettes: 90 colors per set (5 full rows + 10 colors)",
.has_animations = false
};
.group_id = PaletteGroupName::kDungeonMain,
.display_name = "Dungeon Main",
.category = "Dungeon",
.base_address = PaletteAddress::kDungeonMain,
.palette_count = PaletteCount::kDungeonMain,
.colors_per_palette = 90, // 90 colors: 5 full rows (0-15, 16-31, 32-47,
// 48-63, 64-79) + 10 colors (80-89)
.colors_per_row = 16, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description =
"Dungeon-specific palettes: 90 colors per set (5 full rows + 10 "
"colors)",
.has_animations = false};
constexpr PaletteGroupMetadata kGlobalSprites = {
.group_id = PaletteGroupName::kGlobalSprites,
.display_name = "Global Sprites",
.category = "Sprites",
.base_address = PaletteAddress::kGlobalSpritesLW,
.palette_count = 2, // 2 sets (LW and DW), each with 60 colors
.colors_per_palette = 60, // 60 colors: 4 rows (0-15, 16-31, 32-47, 48-59) with transparent at 0, 16, 32, 48
.colors_per_row = 16, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description = "Global sprite palettes: 60 colors per set (4 sprite sub-palettes of 15+transparent each)",
.has_animations = false
};
.group_id = PaletteGroupName::kGlobalSprites,
.display_name = "Global Sprites",
.category = "Sprites",
.base_address = PaletteAddress::kGlobalSpritesLW,
.palette_count = 2, // 2 sets (LW and DW), each with 60 colors
.colors_per_palette = 60, // 60 colors: 4 rows (0-15, 16-31, 32-47, 48-59)
// with transparent at 0, 16, 32, 48
.colors_per_row = 16, // Display in 16-color rows for proper SNES alignment
.bits_per_pixel = 4,
.description =
"Global sprite palettes: 60 colors per set (4 sprite sub-palettes of "
"15+transparent each)",
.has_animations = false};
constexpr PaletteGroupMetadata kSpritesAux1 = {
.group_id = PaletteGroupName::kSpritesAux1,
.display_name = "Sprites Aux 1",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux1,
.palette_count = PaletteCount::kSpritesAux1,
.colors_per_palette = 7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description = "Auxiliary sprite palettes 1: 7 colors per palette (transparent added at runtime)",
.has_animations = false
};
.group_id = PaletteGroupName::kSpritesAux1,
.display_name = "Sprites Aux 1",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux1,
.palette_count = PaletteCount::kSpritesAux1,
.colors_per_palette =
7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description =
"Auxiliary sprite palettes 1: 7 colors per palette (transparent added "
"at runtime)",
.has_animations = false};
constexpr PaletteGroupMetadata kSpritesAux2 = {
.group_id = PaletteGroupName::kSpritesAux2,
.display_name = "Sprites Aux 2",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux2,
.palette_count = PaletteCount::kSpritesAux2,
.colors_per_palette = 7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description = "Auxiliary sprite palettes 2: 7 colors per palette (transparent added at runtime)",
.has_animations = false
};
.group_id = PaletteGroupName::kSpritesAux2,
.display_name = "Sprites Aux 2",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux2,
.palette_count = PaletteCount::kSpritesAux2,
.colors_per_palette =
7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description =
"Auxiliary sprite palettes 2: 7 colors per palette (transparent added "
"at runtime)",
.has_animations = false};
constexpr PaletteGroupMetadata kSpritesAux3 = {
.group_id = PaletteGroupName::kSpritesAux3,
.display_name = "Sprites Aux 3",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux3,
.palette_count = PaletteCount::kSpritesAux3,
.colors_per_palette = 7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description = "Auxiliary sprite palettes 3: 7 colors per palette (transparent added at runtime)",
.has_animations = false
};
.group_id = PaletteGroupName::kSpritesAux3,
.display_name = "Sprites Aux 3",
.category = "Sprites",
.base_address = PaletteAddress::kSpritesAux3,
.palette_count = PaletteCount::kSpritesAux3,
.colors_per_palette =
7, // 7 colors (ROM stores 7, transparent added in memory)
.colors_per_row = 8, // Display as 8-color sub-palettes (with transparent)
.bits_per_pixel = 4,
.description =
"Auxiliary sprite palettes 3: 7 colors per palette (transparent added "
"at runtime)",
.has_animations = false};
constexpr PaletteGroupMetadata kArmor = {
.group_id = PaletteGroupName::kArmor,
.display_name = "Armor / Link",
.category = "Equipment",
.base_address = PaletteAddress::kArmor,
.palette_count = PaletteCount::kArmor,
.colors_per_palette = 15, // 15 colors (ROM stores 15, transparent added in memory for full row)
.colors_per_row = 16, // Display as full 16-color rows (with transparent at index 0)
.bits_per_pixel = 4,
.description = "Link's tunic colors: 15 colors per palette (Green, Blue, Red, Bunny, Electrocuted)",
.has_animations = false
};
.group_id = PaletteGroupName::kArmor,
.display_name = "Armor / Link",
.category = "Equipment",
.base_address = PaletteAddress::kArmor,
.palette_count = PaletteCount::kArmor,
.colors_per_palette = 15, // 15 colors (ROM stores 15, transparent added in
// memory for full row)
.colors_per_row =
16, // Display as full 16-color rows (with transparent at index 0)
.bits_per_pixel = 4,
.description =
"Link's tunic colors: 15 colors per palette (Green, Blue, Red, Bunny, "
"Electrocuted)",
.has_animations = false};
constexpr PaletteGroupMetadata kSwords = {
.group_id = PaletteGroupName::kSwords,
.display_name = "Swords",
.category = "Equipment",
.base_address = PaletteAddress::kSwords,
.palette_count = PaletteCount::kSwords,
.colors_per_palette = 3, // 3 colors (overlay palette, no transparent)
.colors_per_row = 4, // Display in compact groups
.bits_per_pixel = 4,
.description = "Sword blade colors: 3 colors per palette (Fighter, Master, Tempered, Golden)",
.has_animations = false
};
.group_id = PaletteGroupName::kSwords,
.display_name = "Swords",
.category = "Equipment",
.base_address = PaletteAddress::kSwords,
.palette_count = PaletteCount::kSwords,
.colors_per_palette = 3, // 3 colors (overlay palette, no transparent)
.colors_per_row = 4, // Display in compact groups
.bits_per_pixel = 4,
.description =
"Sword blade colors: 3 colors per palette (Fighter, Master, Tempered, "
"Golden)",
.has_animations = false};
constexpr PaletteGroupMetadata kShields = {
.group_id = PaletteGroupName::kShields,
.display_name = "Shields",
.category = "Equipment",
.base_address = PaletteAddress::kShields,
.palette_count = PaletteCount::kShields,
.colors_per_palette = 4, // 4 colors (overlay palette, no transparent)
.colors_per_row = 4, // Display in compact groups
.bits_per_pixel = 4,
.description = "Shield colors: 4 colors per palette (Fighter, Fire, Mirror)",
.has_animations = false
};
.group_id = PaletteGroupName::kShields,
.display_name = "Shields",
.category = "Equipment",
.base_address = PaletteAddress::kShields,
.palette_count = PaletteCount::kShields,
.colors_per_palette = 4, // 4 colors (overlay palette, no transparent)
.colors_per_row = 4, // Display in compact groups
.bits_per_pixel = 4,
.description =
"Shield colors: 4 colors per palette (Fighter, Fire, Mirror)",
.has_animations = false};
constexpr PaletteGroupMetadata kHud = {
.group_id = PaletteGroupName::kHud,
.display_name = "HUD",
.category = "Interface",
.base_address = PaletteAddress::kHud,
.palette_count = PaletteCount::kHud,
.colors_per_palette = 32, // 32 colors: 2 full rows (0-15, 16-31) with transparent at 0, 16
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 2, // HUD palettes are 2bpp
.description = "HUD/Interface palettes: 32 colors per set (2 full rows)",
.has_animations = false
};
.group_id = PaletteGroupName::kHud,
.display_name = "HUD",
.category = "Interface",
.base_address = PaletteAddress::kHud,
.palette_count = PaletteCount::kHud,
.colors_per_palette =
32, // 32 colors: 2 full rows (0-15, 16-31) with transparent at 0, 16
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 2, // HUD palettes are 2bpp
.description = "HUD/Interface palettes: 32 colors per set (2 full rows)",
.has_animations = false};
constexpr PaletteGroupMetadata kOverworldAux = {
.group_id = PaletteGroupName::kOverworldAux,
.display_name = "Overworld Auxiliary",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldAux,
.palette_count = PaletteCount::kOverworldAux,
.colors_per_palette = 21, // 21 colors: 1 full row (0-15) + 5 colors (16-20)
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 4,
.description = "Overworld auxiliary palettes: 21 colors per set (1 full row + 5 colors)",
.has_animations = false
};
.group_id = PaletteGroupName::kOverworldAux,
.display_name = "Overworld Auxiliary",
.category = "Overworld",
.base_address = PaletteAddress::kOverworldAux,
.palette_count = PaletteCount::kOverworldAux,
.colors_per_palette =
21, // 21 colors: 1 full row (0-15) + 5 colors (16-20)
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 4,
.description =
"Overworld auxiliary palettes: 21 colors per set (1 full row + 5 "
"colors)",
.has_animations = false};
constexpr PaletteGroupMetadata kGrass = {
.group_id = PaletteGroupName::kGrass,
.display_name = "Grass",
.category = "Overworld",
.base_address = PaletteAddress::kGrassLW,
.palette_count = PaletteCount::kGrass,
.colors_per_palette = 1, // Single color per entry
.colors_per_row = 3, // Display all 3 in one row
.bits_per_pixel = 4,
.description = "Hardcoded grass colors: 3 individual colors (LW, DW, Special)",
.has_animations = false
};
.group_id = PaletteGroupName::kGrass,
.display_name = "Grass",
.category = "Overworld",
.base_address = PaletteAddress::kGrassLW,
.palette_count = PaletteCount::kGrass,
.colors_per_palette = 1, // Single color per entry
.colors_per_row = 3, // Display all 3 in one row
.bits_per_pixel = 4,
.description =
"Hardcoded grass colors: 3 individual colors (LW, DW, Special)",
.has_animations = false};
constexpr PaletteGroupMetadata k3DObject = {
.group_id = PaletteGroupName::k3DObject,
.display_name = "3D Objects",
.category = "Special",
.base_address = PaletteAddress::kTriforce,
.palette_count = PaletteCount::k3DObject,
.colors_per_palette = 8, // 8 colors per palette (7 + transparent)
.colors_per_row = 8, // Display in 8-color groups
.bits_per_pixel = 4,
.description = "3D object palettes: 8 colors per palette (Triforce, Crystal)",
.has_animations = false
};
.group_id = PaletteGroupName::k3DObject,
.display_name = "3D Objects",
.category = "Special",
.base_address = PaletteAddress::kTriforce,
.palette_count = PaletteCount::k3DObject,
.colors_per_palette = 8, // 8 colors per palette (7 + transparent)
.colors_per_row = 8, // Display in 8-color groups
.bits_per_pixel = 4,
.description =
"3D object palettes: 8 colors per palette (Triforce, Crystal)",
.has_animations = false};
constexpr PaletteGroupMetadata kOverworldMiniMap = {
.group_id = PaletteGroupName::kOverworldMiniMap,
.display_name = "Overworld Mini Map",
.category = "Interface",
.base_address = PaletteAddress::kOverworldMiniMap,
.palette_count = PaletteCount::kOverworldMiniMap,
.colors_per_palette = 128, // 128 colors: 8 full rows (0-127) with transparent at 0, 16, 32, 48, 64, 80, 96, 112
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 4,
.description = "Overworld mini-map palettes: 128 colors per set (8 full rows)",
.has_animations = false
};
.group_id = PaletteGroupName::kOverworldMiniMap,
.display_name = "Overworld Mini Map",
.category = "Interface",
.base_address = PaletteAddress::kOverworldMiniMap,
.palette_count = PaletteCount::kOverworldMiniMap,
.colors_per_palette = 128, // 128 colors: 8 full rows (0-127) with
// transparent at 0, 16, 32, 48, 64, 80, 96, 112
.colors_per_row = 16, // Display in 16-color rows
.bits_per_pixel = 4,
.description =
"Overworld mini-map palettes: 128 colors per set (8 full rows)",
.has_animations = false};
} // namespace PaletteMetadata
@@ -305,4 +323,3 @@ std::vector<const PaletteGroupMetadata*> GetAllPaletteGroups();
} // namespace yaze::zelda3
#endif // YAZE_ZELDA3_PALETTE_CONSTANTS_H

View File

@@ -3,20 +3,20 @@
#include <fstream>
#include <vector>
#include "util/file_util.h"
#include "app/platform/window.h"
#include "app/gfx/backend/irenderer.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/render/tilemap.h"
#include "app/gfx/resource/arena.h"
#include "app/gfx/types/snes_tile.h"
#include "app/gfx/render/tilemap.h"
#include "app/gfx/backend/irenderer.h"
#include "app/platform/window.h"
#include "app/snes.h"
#include "util/file_util.h"
#include "util/hex.h"
namespace yaze::zelda3 {
absl::StatusOr<std::vector<DungeonMap>> LoadDungeonMaps(
Rom &rom, DungeonMapLabels &dungeon_map_labels) {
Rom& rom, DungeonMapLabels& dungeon_map_labels) {
std::vector<DungeonMap> dungeon_maps;
std::vector<std::array<uint8_t, kNumRooms>> current_floor_rooms_d;
std::vector<std::array<uint8_t, kNumRooms>> current_floor_gfx_d;
@@ -75,7 +75,7 @@ absl::StatusOr<std::vector<DungeonMap>> LoadDungeonMaps(
return dungeon_maps;
}
absl::Status SaveDungeonMaps(Rom &rom, std::vector<DungeonMap> &dungeon_maps) {
absl::Status SaveDungeonMaps(Rom& rom, std::vector<DungeonMap>& dungeon_maps) {
for (int d = 0; d < kNumDungeons; d++) {
int ptr = kDungeonMapRoomsPtr + (d * 2);
int ptr_gfx = kDungeonMapGfxPtr + (d * 2);
@@ -98,8 +98,8 @@ absl::Status SaveDungeonMaps(Rom &rom, std::vector<DungeonMap> &dungeon_maps) {
return absl::OkStatus();
}
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom,
const std::vector<uint8_t> &gfx_data,
absl::Status LoadDungeonMapTile16(gfx::Tilemap& tile16_blockset, Rom& rom,
const std::vector<uint8_t>& gfx_data,
bool bin_mode) {
tile16_blockset.tile_size = {16, 16};
tile16_blockset.map_size = {186, 186};
@@ -140,7 +140,7 @@ absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom,
return absl::OkStatus();
}
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom) {
absl::Status SaveDungeonMapTile16(gfx::Tilemap& tile16_blockset, Rom& rom) {
for (int i = 0; i < kNumDungeonMapTile16; i++) {
int addr = kDungeonMapTile16;
if (rom.data()[kDungeonMapExpCheck] != 0xB9) {
@@ -167,10 +167,10 @@ absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom) {
return absl::OkStatus();
}
absl::Status LoadDungeonMapGfxFromBinary(Rom &rom,
gfx::Tilemap &tile16_blockset,
std::array<gfx::Bitmap, 4> &sheets,
std::vector<uint8_t> &gfx_bin_data) {
absl::Status LoadDungeonMapGfxFromBinary(Rom& rom,
gfx::Tilemap& tile16_blockset,
std::array<gfx::Bitmap, 4>& sheets,
std::vector<uint8_t>& gfx_bin_data) {
std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
if (bin_file.empty()) {
return absl::InternalError("No file selected");
@@ -195,8 +195,8 @@ absl::Status LoadDungeonMapGfxFromBinary(Rom &rom,
sheets[i].SetPalette(*rom.mutable_dungeon_palette(3));
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&sheets[i]);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &sheets[i]);
}
}
file.close();

View File

@@ -47,8 +47,8 @@ struct DungeonMap {
DungeonMap(unsigned short boss_room, unsigned char nbr_of_floor,
unsigned char nbr_of_basement,
const std::vector<std::array<uint8_t, kNumRooms>> &floor_rooms,
const std::vector<std::array<uint8_t, kNumRooms>> &floor_gfx)
const std::vector<std::array<uint8_t, kNumRooms>>& floor_rooms,
const std::vector<std::array<uint8_t, kNumRooms>>& floor_gfx)
: boss_room(boss_room),
nbr_of_floor(nbr_of_floor),
nbr_of_basement(nbr_of_basement),
@@ -67,7 +67,7 @@ using DungeonMapLabels =
* @return absl::StatusOr<std::vector<DungeonMap>>
*/
absl::StatusOr<std::vector<DungeonMap>> LoadDungeonMaps(
Rom &rom, DungeonMapLabels &dungeon_map_labels);
Rom& rom, DungeonMapLabels& dungeon_map_labels);
/**
* @brief Save the dungeon maps to the ROM.
@@ -75,7 +75,7 @@ absl::StatusOr<std::vector<DungeonMap>> LoadDungeonMaps(
* @param rom
* @param dungeon_maps
*/
absl::Status SaveDungeonMaps(Rom &rom, std::vector<DungeonMap> &dungeon_maps);
absl::Status SaveDungeonMaps(Rom& rom, std::vector<DungeonMap>& dungeon_maps);
/**
* @brief Load the dungeon map tile16 from the ROM.
@@ -85,8 +85,8 @@ absl::Status SaveDungeonMaps(Rom &rom, std::vector<DungeonMap> &dungeon_maps);
* @param gfx_data
* @param bin_mode
*/
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom,
const std::vector<uint8_t> &gfx_data,
absl::Status LoadDungeonMapTile16(gfx::Tilemap& tile16_blockset, Rom& rom,
const std::vector<uint8_t>& gfx_data,
bool bin_mode);
/**
@@ -95,7 +95,7 @@ absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom,
* @param tile16_blockset
* @param rom
*/
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom);
absl::Status SaveDungeonMapTile16(gfx::Tilemap& tile16_blockset, Rom& rom);
/**
* @brief Load the dungeon map gfx from binary.
@@ -105,10 +105,10 @@ absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom);
* @param sheets
* @param gfx_bin_data
*/
absl::Status LoadDungeonMapGfxFromBinary(Rom &rom,
gfx::Tilemap &tile16_blockset,
std::array<gfx::Bitmap, 4> &sheets,
std::vector<uint8_t> &gfx_bin_data);
absl::Status LoadDungeonMapGfxFromBinary(Rom& rom,
gfx::Tilemap& tile16_blockset,
std::array<gfx::Bitmap, 4>& sheets,
std::vector<uint8_t>& gfx_bin_data);
} // namespace yaze::zelda3
#endif // YAZE_APP_ZELDA3_SCREEN_DUNGEON_MAP_H

View File

@@ -1,10 +1,10 @@
#include "inventory.h"
#include "app/gfx/backend/irenderer.h"
#include "app/platform/window.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/resource/arena.h"
#include "app/gfx/types/snes_tile.h"
#include "app/platform/window.h"
#include "app/rom.h"
#include "app/snes.h"
@@ -33,15 +33,16 @@ absl::Status Inventory::Create(Rom* rom) {
bitmap_.SetPalette(palette_);
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&bitmap_);
return absl::OkStatus();
}
absl::Status Inventory::BuildTileset(Rom* rom) {
tilesheets_.reserve(6 * 0x2000);
for (int i = 0; i < 6 * 0x2000; i++) tilesheets_.push_back(0xFF);
for (int i = 0; i < 6 * 0x2000; i++)
tilesheets_.push_back(0xFF);
ASSIGN_OR_RETURN(tilesheets_, Load2BppGraphics(*rom));
std::vector<uint8_t> test;
for (int i = 0; i < 0x4000; i++) {
@@ -56,8 +57,8 @@ absl::Status Inventory::BuildTileset(Rom* rom) {
tilesheets_bmp_.SetPalette(palette_);
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &tilesheets_bmp_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&tilesheets_bmp_);
return absl::OkStatus();
}
@@ -74,32 +75,22 @@ absl::Status Inventory::LoadItemIcons(Rom* rom) {
};
// Bow icons (.bows section)
std::vector<IconDef> bow_icons = {
{0x00, "No bow"},
{0x08, "Empty bow"},
{0x10, "Bow and arrows"},
{0x18, "Empty silvers bow"},
{0x20, "Silver bow and arrows"}
};
std::vector<IconDef> bow_icons = {{0x00, "No bow"},
{0x08, "Empty bow"},
{0x10, "Bow and arrows"},
{0x18, "Empty silvers bow"},
{0x20, "Silver bow and arrows"}};
// Boomerang icons (.booms section)
std::vector<IconDef> boom_icons = {
{0x28, "No boomerang"},
{0x30, "Blue boomerang"},
{0x38, "Red boomerang"}
};
std::vector<IconDef> boom_icons = {{0x28, "No boomerang"},
{0x30, "Blue boomerang"},
{0x38, "Red boomerang"}};
// Hookshot icons (.hook section)
std::vector<IconDef> hook_icons = {
{0x40, "No hookshot"},
{0x48, "Hookshot"}
};
std::vector<IconDef> hook_icons = {{0x40, "No hookshot"}, {0x48, "Hookshot"}};
// Bomb icons (.bombs section)
std::vector<IconDef> bomb_icons = {
{0x50, "No bombs"},
{0x58, "Bombs"}
};
std::vector<IconDef> bomb_icons = {{0x50, "No bombs"}, {0x58, "Bombs"}};
// Load all icon categories
auto load_icons = [&](const std::vector<IconDef>& icons) -> absl::Status {
@@ -136,7 +127,8 @@ absl::Status Inventory::LoadItemIcons(Rom* rom) {
// - Flute (.flute)
// - Bug net (.net)
// - Book of Mudora (.book)
// - Bottles (.bottles) - Multiple variants (empty, red potion, green potion, etc.)
// - Bottles (.bottles) - Multiple variants (empty, red potion, green potion,
// etc.)
// - Cane of Somaria (.canes)
// - Cane of Byrna (.byrn)
// - Magic cape (.cape)

View File

@@ -40,10 +40,10 @@ class Inventory {
*/
absl::Status Create(Rom* rom);
auto &bitmap() { return bitmap_; }
auto &tilesheet() { return tilesheets_bmp_; }
auto &palette() { return palette_; }
auto &item_icons() { return item_icons_; }
auto& bitmap() { return bitmap_; }
auto& tilesheet() { return tilesheets_bmp_; }
auto& palette() { return palette_; }
auto& item_icons() { return item_icons_; }
private:
/**

View File

@@ -21,20 +21,20 @@ absl::Status OverworldMapScreen::Create(Rom* rom) {
// Load Mode 7 graphics (256 tiles, 8x8 pixels each, 8BPP)
const int mode7_gfx_addr = 0x0C4000;
std::vector<uint8_t> mode7_gfx_raw(0x4000); // Raw tileset data from ROM
for (int i = 0; i < 0x4000; i++) {
ASSIGN_OR_RETURN(mode7_gfx_raw[i], rom->ReadByte(mode7_gfx_addr + i));
}
// Mode 7 tiles are stored in tiled format (each tile's rows are consecutive)
// but we need linear bitmap format (all tiles' first rows, then all second rows)
// Convert from tiled to linear bitmap layout
// but we need linear bitmap format (all tiles' first rows, then all second
// rows) Convert from tiled to linear bitmap layout
std::vector<uint8_t> mode7_gfx(0x4000);
int pos = 0;
for (int sy = 0; sy < 16 * 1024; sy += 1024) { // 16 rows of tiles
for (int sx = 0; sx < 16 * 8; sx += 8) { // 16 columns of tiles
for (int y = 0; y < 8 * 128; y += 128) { // 8 pixel rows within tile
for (int x = 0; x < 8; x++) { // 8 pixels per row
for (int sy = 0; sy < 16 * 1024; sy += 1024) { // 16 rows of tiles
for (int sx = 0; sx < 16 * 8; sx += 8) { // 16 columns of tiles
for (int y = 0; y < 8 * 128; y += 128) { // 8 pixel rows within tile
for (int x = 0; x < 8; x++) { // 8 pixels per row
mode7_gfx[x + sx + y + sy] = mode7_gfx_raw[pos];
pos++;
}
@@ -48,7 +48,7 @@ absl::Status OverworldMapScreen::Create(Rom* rom) {
tiles8_bitmap_.metadata().palette_format = 0;
tiles8_bitmap_.metadata().source_type = "mode7_tileset";
tiles8_bitmap_.metadata().palette_colors = 128;
// Create map bitmap (512x512 for 64x64 tiles at 8x8 each)
map_bitmap_.Create(512, 512, 8, std::vector<uint8_t>(512 * 512));
map_bitmap_.metadata().source_bpp = 8;
@@ -64,7 +64,7 @@ absl::Status OverworldMapScreen::Create(Rom* rom) {
lw_palette_.AddColor(gfx::SnesColor(snes_color));
}
// Dark World palette at 0x055C27
// Dark World palette at 0x055C27
const int dw_pal_addr = 0x055C27;
for (int i = 0; i < 128; i++) {
ASSIGN_OR_RETURN(uint16_t snes_color, rom->ReadWord(dw_pal_addr + (i * 2)));
@@ -77,20 +77,20 @@ absl::Status OverworldMapScreen::Create(Rom* rom) {
// Render initial map (Light World)
RETURN_IF_ERROR(RenderMapLayer(false));
// Apply palettes AFTER bitmaps are fully initialized
tiles8_bitmap_.SetPalette(lw_palette_);
map_bitmap_.SetPalette(lw_palette_); // Map also needs palette
// Ensure bitmaps are marked as active
tiles8_bitmap_.set_active(true);
map_bitmap_.set_active(true);
// Queue texture creation
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &tiles8_bitmap_);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &map_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&tiles8_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&map_bitmap_);
return absl::OkStatus();
}
@@ -100,18 +100,18 @@ absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
// Based on ZScream's Constants.IDKZarby = 0x054727
// The data alternates between left (32 columns) and right (32 columns)
// for the first 2048 tiles, then continues for bottom half
const int base_addr = 0x054727; // IDKZarby constant from ZScream
int p1 = base_addr + 0x0000; // Top-left quadrant data
int p2 = base_addr + 0x0400; // Top-right quadrant data
int p2 = base_addr + 0x0400; // Top-right quadrant data
int p3 = base_addr + 0x0800; // Bottom-left quadrant data
int p4 = base_addr + 0x0C00; // Bottom-right quadrant data
int p5 = base_addr + 0x1000; // Dark World additional section
bool rSide = false; // false = left side, true = right side
int cSide = 0; // Column counter within side (0-31)
int count = 0; // Output tile index
// Load 64x64 map with interleaved left/right format
while (count < 64 * 64) {
if (count < 0x800) { // Top half (first 2048 tiles)
@@ -121,7 +121,7 @@ absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p1++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
@@ -134,7 +134,7 @@ absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p2++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
@@ -149,7 +149,7 @@ absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p3++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
@@ -162,7 +162,7 @@ absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p4++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
@@ -171,11 +171,11 @@ absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
}
}
}
cSide++;
count++;
}
// Load Dark World specific data (bottom-right 32x32 section)
count = 0;
int line = 0;
@@ -205,17 +205,17 @@ absl::Status OverworldMapScreen::RenderMapLayer(bool use_dark_world) {
for (int yy = 0; yy < 64; yy++) {
for (int xx = 0; xx < 64; xx++) {
uint8_t tile_id = tile_source[xx + (yy * 64)];
// Calculate tile position in tiles8_bitmap (16 tiles per row)
int tile_x = (tile_id % 16) * 8;
int tile_y = (tile_id / 16) * 8;
// Copy 8x8 tile pixels
for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) {
int src_index = (tile_x + px) + ((tile_y + py) * 128);
int dest_index = (xx * 8 + px) + ((yy * 8 + py) * 512);
if (src_index < tiles8_data.size() && dest_index < map_data.size()) {
map_data[dest_index] = tiles8_data[src_index];
}
@@ -223,7 +223,7 @@ absl::Status OverworldMapScreen::RenderMapLayer(bool use_dark_world) {
}
}
}
// Copy pixel data to SDL surface
map_bitmap_.UpdateSurfacePixels();
@@ -238,18 +238,18 @@ absl::Status OverworldMapScreen::Save(Rom* rom) {
int p3 = base_addr + 0x0800;
int p4 = base_addr + 0x0C00;
int p5 = base_addr + 0x1000;
bool rSide = false;
int cSide = 0;
int count = 0;
// Write 64x64 map with interleaved left/right format
while (count < 64 * 64) {
if (count < 0x800) {
if (!rSide) {
RETURN_IF_ERROR(rom->WriteByte(p1, lw_map_tiles_[count]));
p1++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
@@ -259,7 +259,7 @@ absl::Status OverworldMapScreen::Save(Rom* rom) {
} else {
RETURN_IF_ERROR(rom->WriteByte(p2, lw_map_tiles_[count]));
p2++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
@@ -271,7 +271,7 @@ absl::Status OverworldMapScreen::Save(Rom* rom) {
if (!rSide) {
RETURN_IF_ERROR(rom->WriteByte(p3, lw_map_tiles_[count]));
p3++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
@@ -281,7 +281,7 @@ absl::Status OverworldMapScreen::Save(Rom* rom) {
} else {
RETURN_IF_ERROR(rom->WriteByte(p4, lw_map_tiles_[count]));
p4++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
@@ -290,16 +290,17 @@ absl::Status OverworldMapScreen::Save(Rom* rom) {
}
}
}
cSide++;
count++;
}
// Write Dark World specific data
count = 0;
int line = 0;
while (true) {
RETURN_IF_ERROR(rom->WriteByte(p5, dw_map_tiles_[1040 + count + (line * 64)]));
RETURN_IF_ERROR(
rom->WriteByte(p5, dw_map_tiles_[1040 + count + (line * 64)]));
p5++;
count++;
if (count >= 32) {
@@ -320,44 +321,45 @@ absl::Status OverworldMapScreen::LoadCustomMap(const std::string& file_path) {
if (!file.is_open()) {
return absl::NotFoundError("Could not open custom map file: " + file_path);
}
std::streamsize size = file.tellg();
if (size != 4096) {
return absl::InvalidArgumentError(
"Custom map file must be exactly 4096 bytes (64×64 tiles)");
}
file.seekg(0, std::ios::beg);
// Read into Light World map buffer (could add option for Dark World later)
file.read(reinterpret_cast<char*>(lw_map_tiles_.data()), 4096);
if (!file) {
return absl::InternalError("Failed to read custom map data");
}
// Re-render with new data
RETURN_IF_ERROR(RenderMapLayer(false));
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &map_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE,
&map_bitmap_);
return absl::OkStatus();
}
absl::Status OverworldMapScreen::SaveCustomMap(const std::string& file_path,
bool use_dark_world) {
absl::Status OverworldMapScreen::SaveCustomMap(const std::string& file_path,
bool use_dark_world) {
std::ofstream file(file_path, std::ios::binary);
if (!file.is_open()) {
return absl::InternalError("Could not create custom map file: " + file_path);
return absl::InternalError("Could not create custom map file: " +
file_path);
}
const auto& tiles = use_dark_world ? dw_map_tiles_ : lw_map_tiles_;
file.write(reinterpret_cast<const char*>(tiles.data()), tiles.size());
if (!file) {
return absl::InternalError("Failed to write custom map data");
}
return absl::OkStatus();
}

View File

@@ -38,7 +38,7 @@ class OverworldMapScreen {
// Accessors for tile data
auto& lw_tiles() { return lw_map_tiles_; }
auto& dw_tiles() { return dw_map_tiles_; }
// Mutable accessors for editing
auto& mutable_lw_tiles() { return lw_map_tiles_; }
auto& mutable_dw_tiles() { return dw_map_tiles_; }
@@ -46,7 +46,7 @@ class OverworldMapScreen {
// Bitmap accessors
auto& tiles8_bitmap() { return tiles8_bitmap_; }
auto& map_bitmap() { return map_bitmap_; }
// Palette accessors
auto& lw_palette() { return lw_palette_; }
auto& dw_palette() { return dw_palette_; }
@@ -79,10 +79,10 @@ class OverworldMapScreen {
std::array<uint8_t, 64 * 64> lw_map_tiles_; // Light World tile indices
std::array<uint8_t, 64 * 64> dw_map_tiles_; // Dark World tile indices
gfx::Bitmap tiles8_bitmap_; // 128x128 tileset (mode 7 graphics)
gfx::Bitmap map_bitmap_; // 512x512 rendered map (64 tiles × 8 pixels)
gfx::SnesPalette lw_palette_; // Light World palette
gfx::SnesPalette dw_palette_; // Dark World palette
};
@@ -91,4 +91,3 @@ class OverworldMapScreen {
} // namespace yaze
#endif // YAZE_APP_ZELDA3_OVERWORLD_MAP_SCREEN_H

View File

@@ -6,6 +6,7 @@
#include "app/gfx/resource/arena.h"
#include "app/rom.h"
#include "app/snes.h"
#include "util/log.h"
namespace yaze {
namespace zelda3 {
@@ -20,30 +21,33 @@ absl::Status TitleScreen::Create(Rom* rom) {
tiles_bg1_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
tiles_bg2_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
oam_bg_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(0x80000));
// Set metadata for title screen bitmaps
// Title screen uses 3BPP graphics (like all LTTP data) with composite 64-color palette
// Title screen uses 3BPP graphics (like all LTTP data) with composite
// 64-color palette
tiles8_bitmap_.metadata().source_bpp = 3;
tiles8_bitmap_.metadata().palette_format = 0; // Full 64-color palette
tiles8_bitmap_.metadata().source_type = "graphics_sheet";
tiles8_bitmap_.metadata().palette_colors = 64;
tiles_bg1_bitmap_.metadata().source_bpp = 3;
tiles_bg1_bitmap_.metadata().palette_format = 0; // Uses full palette with sub-palette indexing
tiles_bg1_bitmap_.metadata().palette_format =
0; // Uses full palette with sub-palette indexing
tiles_bg1_bitmap_.metadata().source_type = "screen_buffer";
tiles_bg1_bitmap_.metadata().palette_colors = 64;
tiles_bg2_bitmap_.metadata().source_bpp = 3;
tiles_bg2_bitmap_.metadata().palette_format = 0;
tiles_bg2_bitmap_.metadata().source_type = "screen_buffer";
tiles_bg2_bitmap_.metadata().palette_colors = 64;
oam_bg_bitmap_.metadata().source_bpp = 3;
oam_bg_bitmap_.metadata().palette_format = 0;
oam_bg_bitmap_.metadata().source_type = "screen_buffer";
oam_bg_bitmap_.metadata().palette_colors = 64;
// Initialize composite bitmap for stacked BG rendering (256x256 = 65536 bytes)
// Initialize composite bitmap for stacked BG rendering (256x256 = 65536
// bytes)
title_composite_bitmap_.Create(256, 256, 8, std::vector<uint8_t>(256 * 256));
title_composite_bitmap_.metadata().source_bpp = 3;
title_composite_bitmap_.metadata().palette_format = 0;
@@ -54,22 +58,19 @@ absl::Status TitleScreen::Create(Rom* rom) {
tiles_bg1_buffer_.fill(0x492); // Default empty tile
tiles_bg2_buffer_.fill(0x492);
// Load palette (title screen uses 3BPP graphics with 8 palettes of 8 colors each)
// Build composite palette from multiple sources (matches ZScream's SetColorsPalette)
// Palette 0: OverworldMainPalettes[5]
// Palette 1: OverworldAnimatedPalettes[0]
// Palette 2: OverworldAuxPalettes[3]
// Palette 3: OverworldAuxPalettes[3]
// Palette 4: HudPalettes[0]
// Palette 5: Transparent/black
// Palette 6: SpritesAux1Palettes[1]
// Palette 7: SpritesAux1Palettes[1]
// Load palette (title screen uses 3BPP graphics with 8 palettes of 8 colors
// each) Build composite palette from multiple sources (matches ZScream's
// SetColorsPalette) Palette 0: OverworldMainPalettes[5] Palette 1:
// OverworldAnimatedPalettes[0] Palette 2: OverworldAuxPalettes[3] Palette 3:
// OverworldAuxPalettes[3] Palette 4: HudPalettes[0] Palette 5:
// Transparent/black Palette 6: SpritesAux1Palettes[1] Palette 7:
// SpritesAux1Palettes[1]
auto pal_group = rom->palette_group();
// Add each 8-color palette in sequence (EXACTLY 8 colors each for 64 total)
size_t palette_start = palette_.size();
// Palette 0: OverworldMainPalettes[5]
if (pal_group.overworld_main.size() > 5) {
const auto& src = pal_group.overworld_main[5];
@@ -83,9 +84,10 @@ absl::Status TitleScreen::Create(Rom* rom) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
added++;
}
LOG_INFO("TitleScreen", "Palette 0: added %zu colors from overworld_main[5]", added);
LOG_INFO("TitleScreen",
"Palette 0: added %zu colors from overworld_main[5]", added);
}
// Palette 1: OverworldAnimatedPalettes[0]
if (pal_group.overworld_animated.size() > 0) {
const auto& src = pal_group.overworld_animated[0];
@@ -98,9 +100,10 @@ absl::Status TitleScreen::Create(Rom* rom) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
added++;
}
LOG_INFO("TitleScreen", "Palette 1: added %zu colors from overworld_animated[0]", added);
LOG_INFO("TitleScreen",
"Palette 1: added %zu colors from overworld_animated[0]", added);
}
// Palette 2 & 3: OverworldAuxPalettes[3] (used twice)
if (pal_group.overworld_aux.size() > 3) {
auto src = pal_group.overworld_aux[3]; // Copy, as this returns by value
@@ -114,10 +117,12 @@ absl::Status TitleScreen::Create(Rom* rom) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
added++;
}
LOG_INFO("TitleScreen", "Palette %d: added %zu colors from overworld_aux[3]", 2+pal, added);
LOG_INFO("TitleScreen",
"Palette %d: added %zu colors from overworld_aux[3]", 2 + pal,
added);
}
}
// Palette 4: HudPalettes[0]
if (pal_group.hud.size() > 0) {
auto src = pal_group.hud.palette(0); // Copy, as this returns by value
@@ -132,13 +137,13 @@ absl::Status TitleScreen::Create(Rom* rom) {
}
LOG_INFO("TitleScreen", "Palette 4: added %zu colors from hud[0]", added);
}
// Palette 5: 8 transparent/black colors
for (int i = 0; i < 8; i++) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
}
LOG_INFO("TitleScreen", "Palette 5: added 8 transparent/black colors");
// Palette 6 & 7: SpritesAux1Palettes[1] (used twice)
if (pal_group.sprites_aux1.size() > 1) {
auto src = pal_group.sprites_aux1[1]; // Copy, as this returns by value
@@ -152,11 +157,14 @@ absl::Status TitleScreen::Create(Rom* rom) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
added++;
}
LOG_INFO("TitleScreen", "Palette %d: added %zu colors from sprites_aux1[1]", 6+pal, added);
LOG_INFO("TitleScreen",
"Palette %d: added %zu colors from sprites_aux1[1]", 6 + pal,
added);
}
}
LOG_INFO("TitleScreen", "Built composite palette: %zu colors (should be 64)", palette_.size());
LOG_INFO("TitleScreen", "Built composite palette: %zu colors (should be 64)",
palette_.size());
// Build tile16 blockset from graphics
RETURN_IF_ERROR(BuildTileset(rom));
@@ -175,22 +183,24 @@ absl::Status TitleScreen::BuildTileset(Rom* rom) {
// Read title screen GFX group indices from ROM
constexpr int kTitleScreenTilesGFX = 0x064207;
constexpr int kTitleScreenSpritesGFX = 0x06420C;
ASSIGN_OR_RETURN(uint8_t tiles_gfx_index, rom->ReadByte(kTitleScreenTilesGFX));
ASSIGN_OR_RETURN(uint8_t sprites_gfx_index, rom->ReadByte(kTitleScreenSpritesGFX));
LOG_INFO("TitleScreen", "GFX group indices: tiles=%d, sprites=%d",
ASSIGN_OR_RETURN(uint8_t tiles_gfx_index,
rom->ReadByte(kTitleScreenTilesGFX));
ASSIGN_OR_RETURN(uint8_t sprites_gfx_index,
rom->ReadByte(kTitleScreenSpritesGFX));
LOG_INFO("TitleScreen", "GFX group indices: tiles=%d, sprites=%d",
tiles_gfx_index, sprites_gfx_index);
// Load main graphics sheets (slots 0-7) from GFX groups
// First, read the GFX groups pointer (2 bytes at 0x6237)
constexpr int kGfxGroupsPointer = 0x6237;
ASSIGN_OR_RETURN(uint16_t gfx_groups_snes, rom->ReadWord(kGfxGroupsPointer));
uint32_t main_gfx_table = SnesToPc(gfx_groups_snes);
LOG_INFO("TitleScreen", "GFX groups table: SNES=0x%04X, PC=0x%06X",
LOG_INFO("TitleScreen", "GFX groups table: SNES=0x%04X, PC=0x%06X",
gfx_groups_snes, main_gfx_table);
// Read 8 bytes from mainGfx[tiles_gfx_index]
int main_gfx_offset = main_gfx_table + (tiles_gfx_index * 8);
for (int i = 0; i < 8; i++) {
@@ -202,77 +212,83 @@ absl::Status TitleScreen::BuildTileset(Rom* rom) {
// and 82 room groups (82 * 4 = 328 bytes) = 624 bytes offset
int sprite_gfx_table = main_gfx_table + (37 * 8) + (82 * 4);
int sprite_gfx_offset = sprite_gfx_table + (sprites_gfx_index * 4);
staticgfx[8] = 115 + 0; // Title logo base
staticgfx[8] = 115 + 0; // Title logo base
ASSIGN_OR_RETURN(uint8_t sprite3, rom->ReadByte(sprite_gfx_offset + 3));
staticgfx[9] = 115 + sprite3; // Sprite graphics slot 3
staticgfx[10] = 115 + 6; // Additional graphics
staticgfx[11] = 115 + 7; // Additional graphics
staticgfx[10] = 115 + 6; // Additional graphics
staticgfx[11] = 115 + 7; // Additional graphics
ASSIGN_OR_RETURN(uint8_t sprite0, rom->ReadByte(sprite_gfx_offset + 0));
staticgfx[12] = 115 + sprite0; // Sprite graphics slot 0
staticgfx[13] = 112; // UI graphics
staticgfx[14] = 112; // UI graphics
staticgfx[15] = 112; // UI graphics
staticgfx[13] = 112; // UI graphics
staticgfx[14] = 112; // UI graphics
staticgfx[15] = 112; // UI graphics
// Use pre-converted graphics from ROM buffer - simple and matches rest of yaze
// Title screen uses standard 3BPP graphics, no special offset needed
// Use pre-converted graphics from ROM buffer - simple and matches rest of
// yaze Title screen uses standard 3BPP graphics, no special offset needed
const auto& gfx_buffer = rom->graphics_buffer();
auto& tiles8_data = tiles8_bitmap_.mutable_data();
LOG_INFO("TitleScreen", "Graphics buffer size: %zu bytes", gfx_buffer.size());
LOG_INFO("TitleScreen", "Tiles8 bitmap size: %zu bytes", tiles8_data.size());
// Copy graphics sheets to tiles8_bitmap
LOG_INFO("TitleScreen", "Loading 16 graphics sheets:");
for (int i = 0; i < 16; i++) {
LOG_INFO("TitleScreen", " staticgfx[%d] = %d", i, staticgfx[i]);
}
for (int i = 0; i < 16; i++) {
int sheet_id = staticgfx[i];
// Validate sheet ID (ROM has 223 sheets: 0-222)
if (sheet_id > 222) {
LOG_ERROR("TitleScreen", "Sheet %d: Invalid sheet_id=%d (max 222), using sheet 0 instead",
i, sheet_id);
LOG_ERROR(
"TitleScreen",
"Sheet %d: Invalid sheet_id=%d (max 222), using sheet 0 instead", i,
sheet_id);
sheet_id = 0; // Fallback to a valid sheet
}
int source_offset = sheet_id * 0x1000; // Each 8BPP sheet is 0x1000 bytes
int dest_offset = i * 0x1000;
if (source_offset + 0x1000 <= gfx_buffer.size() &&
if (source_offset + 0x1000 <= gfx_buffer.size() &&
dest_offset + 0x1000 <= tiles8_data.size()) {
std::copy(gfx_buffer.begin() + source_offset,
gfx_buffer.begin() + source_offset + 0x1000,
tiles8_data.begin() + dest_offset);
tiles8_data.begin() + dest_offset);
// Sample first few pixels
LOG_INFO("TitleScreen", "Sheet %d (ID %d): Sample pixels: %02X %02X %02X %02X",
i, sheet_id,
tiles8_data[dest_offset], tiles8_data[dest_offset+1],
tiles8_data[dest_offset+2], tiles8_data[dest_offset+3]);
LOG_INFO("TitleScreen",
"Sheet %d (ID %d): Sample pixels: %02X %02X %02X %02X", i,
sheet_id, tiles8_data[dest_offset], tiles8_data[dest_offset + 1],
tiles8_data[dest_offset + 2], tiles8_data[dest_offset + 3]);
} else {
LOG_ERROR("TitleScreen", "Sheet %d (ID %d): out of bounds! source=%d, dest=%d, buffer_size=%zu",
LOG_ERROR("TitleScreen",
"Sheet %d (ID %d): out of bounds! source=%d, dest=%d, "
"buffer_size=%zu",
i, sheet_id, source_offset, dest_offset, gfx_buffer.size());
}
}
// Set palette on tiles8 bitmap
tiles8_bitmap_.SetPalette(palette_);
LOG_INFO("TitleScreen", "Applied palette to tiles8_bitmap: %zu colors", palette_.size());
LOG_INFO("TitleScreen", "Applied palette to tiles8_bitmap: %zu colors",
palette_.size());
// Log first few colors
if (palette_.size() >= 8) {
LOG_INFO("TitleScreen", " Palette colors 0-7: %04X %04X %04X %04X %04X %04X %04X %04X",
palette_[0].snes(), palette_[1].snes(), palette_[2].snes(), palette_[3].snes(),
palette_[4].snes(), palette_[5].snes(), palette_[6].snes(), palette_[7].snes());
LOG_INFO("TitleScreen",
" Palette colors 0-7: %04X %04X %04X %04X %04X %04X %04X %04X",
palette_[0].snes(), palette_[1].snes(), palette_[2].snes(),
palette_[3].snes(), palette_[4].snes(), palette_[5].snes(),
palette_[6].snes(), palette_[7].snes());
}
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &tiles8_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&tiles8_bitmap_);
// TODO: Build tile16 blockset from tile8 data
// This would involve composing 16x16 tiles from 8x8 tiles
@@ -287,11 +303,12 @@ absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
ASSIGN_OR_RETURN(uint8_t bank_byte, rom->ReadByte(0x138C + 3));
ASSIGN_OR_RETURN(uint8_t high_byte, rom->ReadByte(0x1383 + 3));
ASSIGN_OR_RETURN(uint8_t low_byte, rom->ReadByte(0x137A + 3));
uint32_t snes_addr = (bank_byte << 16) | (high_byte << 8) | low_byte;
uint32_t pc_addr = SnesToPc(snes_addr);
LOG_INFO("TitleScreen", "Title screen pointer: SNES=0x%06X, PC=0x%06X", snes_addr, pc_addr);
LOG_INFO("TitleScreen", "Title screen pointer: SNES=0x%06X, PC=0x%06X",
snes_addr, pc_addr);
// Initialize buffers with default empty tile
for (int i = 0; i < 1024; i++) {
@@ -302,81 +319,84 @@ absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
// ZScream expanded format at 0x108000 (PC)
if (pc_addr >= 0x108000 && pc_addr <= 0x10FFFF) {
LOG_INFO("TitleScreen", "Detected ZScream expanded format");
int pos = pc_addr;
// Read BG1 header: dest (word), length (word)
ASSIGN_OR_RETURN(uint16_t bg1_dest, rom->ReadWord(pos));
pos += 2;
ASSIGN_OR_RETURN(uint16_t bg1_length, rom->ReadWord(pos));
pos += 2;
LOG_INFO("TitleScreen", "BG1 Header: dest=0x%04X, length=0x%04X", bg1_dest, bg1_length);
LOG_INFO("TitleScreen", "BG1 Header: dest=0x%04X, length=0x%04X", bg1_dest,
bg1_length);
// Read 1024 BG1 tiles (2 bytes each = 2048 bytes)
for (int i = 0; i < 1024; i++) {
ASSIGN_OR_RETURN(uint16_t tile, rom->ReadWord(pos));
tiles_bg1_buffer_[i] = tile;
pos += 2;
}
// Read BG2 header: dest (word), length (word)
ASSIGN_OR_RETURN(uint16_t bg2_dest, rom->ReadWord(pos));
pos += 2;
ASSIGN_OR_RETURN(uint16_t bg2_length, rom->ReadWord(pos));
pos += 2;
LOG_INFO("TitleScreen", "BG2 Header: dest=0x%04X, length=0x%04X", bg2_dest, bg2_length);
LOG_INFO("TitleScreen", "BG2 Header: dest=0x%04X, length=0x%04X", bg2_dest,
bg2_length);
// Read 1024 BG2 tiles (2 bytes each = 2048 bytes)
for (int i = 0; i < 1024; i++) {
ASSIGN_OR_RETURN(uint16_t tile, rom->ReadWord(pos));
tiles_bg2_buffer_[i] = tile;
pos += 2;
}
LOG_INFO("TitleScreen", "Loaded 2048 tilemap entries from ZScream expanded format");
LOG_INFO("TitleScreen",
"Loaded 2048 tilemap entries from ZScream expanded format");
}
// Vanilla format: Sequential DMA blocks at pointer location
// NOTE: This reads from the pointer but may not be the correct format
// See docs/screen-editor-status.md for details on this ongoing issue
else {
LOG_INFO("TitleScreen", "Using vanilla DMA format (EXPERIMENTAL)");
int pos = pc_addr;
int total_entries = 0;
int blocks_read = 0;
// Read DMA blocks until we hit terminator or safety limit
while (pos < rom->size() && blocks_read < 20) {
// Read destination address (word)
ASSIGN_OR_RETURN(uint16_t dest_addr, rom->ReadWord(pos));
pos += 2;
// Check for terminator
if (dest_addr == 0xFFFF || (dest_addr & 0xFF) == 0xFF) {
LOG_INFO("TitleScreen", "Found DMA terminator at pos=0x%06X", pos - 2);
break;
}
// Read length/flags (word)
ASSIGN_OR_RETURN(uint16_t length_flags, rom->ReadWord(pos));
pos += 2;
bool increment64 = (length_flags & 0x8000) == 0x8000;
bool fixsource = (length_flags & 0x4000) == 0x4000;
int length = (length_flags & 0x0FFF);
LOG_INFO("TitleScreen", "Block %d: dest=0x%04X, len=%d, inc64=%d, fix=%d",
LOG_INFO("TitleScreen", "Block %d: dest=0x%04X, len=%d, inc64=%d, fix=%d",
blocks_read, dest_addr, length, increment64, fixsource);
int tile_count = (length / 2) + 1;
int source_start = pos;
// Read tiles
for (int j = 0; j < tile_count; j++) {
ASSIGN_OR_RETURN(uint16_t tiledata, rom->ReadWord(pos));
// Determine which layer based on destination address
if (dest_addr >= 0x1000 && dest_addr < 0x1400) {
// BG1 layer
@@ -393,29 +413,30 @@ absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
total_entries++;
}
}
// Advance destination address
if (increment64) {
dest_addr += 64;
} else {
dest_addr += 2;
}
// Advance source position
if (!fixsource) {
pos += 2;
}
}
// If fixsource, only advance by one tile
if (fixsource) {
pos = source_start + 2;
}
blocks_read++;
}
LOG_INFO("TitleScreen", "Loaded %d tilemap entries from %d DMA blocks (may be incorrect)",
LOG_INFO("TitleScreen",
"Loaded %d tilemap entries from %d DMA blocks (may be incorrect)",
total_entries, blocks_read);
}
@@ -430,7 +451,7 @@ absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
tiles_bg2_bitmap_.SetPalette(palette_);
oam_bg_bitmap_.SetPalette(palette_);
title_composite_bitmap_.SetPalette(palette_);
// Ensure bitmaps are marked as active
tiles_bg1_bitmap_.set_active(true);
tiles_bg2_bitmap_.set_active(true);
@@ -438,15 +459,15 @@ absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
title_composite_bitmap_.set_active(true);
// Queue texture creation for all layer bitmaps
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &tiles_bg1_bitmap_);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &tiles_bg2_bitmap_);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &oam_bg_bitmap_);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &title_composite_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&tiles_bg1_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&tiles_bg2_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&oam_bg_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&title_composite_bitmap_);
// Initial composite render (both layers visible)
RETURN_IF_ERROR(RenderCompositeLayer(true, true));
@@ -465,22 +486,24 @@ absl::Status TitleScreen::RenderBG1Layer() {
uint16_t tile_word = tiles_bg1_buffer_[tilemap_index];
// Extract tile info from SNES tile word (vhopppcc cccccccc format)
int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
bool h_flip = (tile_word & 0x4000) != 0; // Bit 14: horizontal flip
bool v_flip = (tile_word & 0x8000) != 0; // Bit 15: vertical flip
// Debug: Log suspicious tile IDs
if (tile_id > 512) {
LOG_WARN("TitleScreen", "BG1: Suspicious tile_id=%d at (%d,%d), word=0x%04X",
tile_id, tile_x, tile_y, tile_word);
LOG_WARN("TitleScreen",
"BG1: Suspicious tile_id=%d at (%d,%d), word=0x%04X", tile_id,
tile_x, tile_y, tile_word);
}
// Calculate source position in tiles8_bitmap_
// tiles8_bitmap_ is 128 pixels wide, 512 pixels tall (16 sheets × 32 pixels)
// Each sheet has 256 tiles (16×16 tiles, 128×32 pixels, 0x1000 bytes)
int sheet_index = tile_id / 256; // Which sheet (0-15)
int tile_in_sheet = tile_id % 256; // Tile within sheet (0-255)
// tiles8_bitmap_ is 128 pixels wide, 512 pixels tall (16 sheets × 32
// pixels) Each sheet has 256 tiles (16×16 tiles, 128×32 pixels, 0x1000
// bytes)
int sheet_index = tile_id / 256; // Which sheet (0-15)
int tile_in_sheet = tile_id % 256; // Tile within sheet (0-255)
int src_tile_x = (tile_in_sheet % 16) * 8;
int src_tile_y = (sheet_index * 32) + ((tile_in_sheet / 16) * 8);
@@ -494,31 +517,34 @@ absl::Status TitleScreen::RenderBG1Layer() {
// Calculate source and destination positions
int src_x = src_tile_x + src_px;
int src_y = src_tile_y + src_py;
int src_pos = src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
int src_pos =
src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
int dest_x = tile_x * 8 + px;
int dest_y = tile_y * 8 + py;
int dest_pos = dest_y * 256 + dest_x; // BG1 is 256 pixels wide
// Copy pixel with palette application
// Graphics are 3BPP in ROM, converted to 8BPP indexed with +0x88 offset
if (src_pos < tile8_bitmap_data.size() && dest_pos < bg1_data.size()) {
// Graphics are 3BPP in ROM, converted to 8BPP indexed with +0x88
// offset
if (src_pos < tile8_bitmap_data.size() &&
dest_pos < bg1_data.size()) {
uint8_t pixel_value = tile8_bitmap_data[src_pos];
// Pixel values already include palette information from +0x88 offset
// Just copy directly (color index 0 = transparent)
// Pixel values already include palette information from +0x88
// offset Just copy directly (color index 0 = transparent)
bg1_data[dest_pos] = pixel_value;
}
}
}
}
}
// Update surface with rendered pixel data
tiles_bg1_bitmap_.UpdateSurfacePixels();
// Queue texture update
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &tiles_bg1_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE,
&tiles_bg1_bitmap_);
return absl::OkStatus();
}
@@ -535,16 +561,17 @@ absl::Status TitleScreen::RenderBG2Layer() {
uint16_t tile_word = tiles_bg2_buffer_[tilemap_index];
// Extract tile info from SNES tile word (vhopppcc cccccccc format)
int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
int tile_id = tile_word & 0x3FF; // Bits 0-9: tile ID
int palette = (tile_word >> 10) & 0x07; // Bits 10-12: palette
bool h_flip = (tile_word & 0x4000) != 0; // Bit 14: horizontal flip
bool v_flip = (tile_word & 0x8000) != 0; // Bit 15: vertical flip
// Calculate source position in tiles8_bitmap_
// tiles8_bitmap_ is 128 pixels wide, 512 pixels tall (16 sheets × 32 pixels)
// Each sheet has 256 tiles (16×16 tiles, 128×32 pixels, 0x1000 bytes)
int sheet_index = tile_id / 256; // Which sheet (0-15)
int tile_in_sheet = tile_id % 256; // Tile within sheet (0-255)
// tiles8_bitmap_ is 128 pixels wide, 512 pixels tall (16 sheets × 32
// pixels) Each sheet has 256 tiles (16×16 tiles, 128×32 pixels, 0x1000
// bytes)
int sheet_index = tile_id / 256; // Which sheet (0-15)
int tile_in_sheet = tile_id % 256; // Tile within sheet (0-255)
int src_tile_x = (tile_in_sheet % 16) * 8;
int src_tile_y = (sheet_index * 32) + ((tile_in_sheet / 16) * 8);
@@ -558,31 +585,34 @@ absl::Status TitleScreen::RenderBG2Layer() {
// Calculate source and destination positions
int src_x = src_tile_x + src_px;
int src_y = src_tile_y + src_py;
int src_pos = src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
int src_pos =
src_y * 128 + src_x; // tiles8_bitmap_ is 128 pixels wide
int dest_x = tile_x * 8 + px;
int dest_y = tile_y * 8 + py;
int dest_pos = dest_y * 256 + dest_x; // BG2 is 256 pixels wide
// Copy pixel with palette application
// Graphics are 3BPP in ROM, converted to 8BPP indexed with +0x88 offset
if (src_pos < tile8_bitmap_data.size() && dest_pos < bg2_data.size()) {
// Graphics are 3BPP in ROM, converted to 8BPP indexed with +0x88
// offset
if (src_pos < tile8_bitmap_data.size() &&
dest_pos < bg2_data.size()) {
uint8_t pixel_value = tile8_bitmap_data[src_pos];
// Pixel values already include palette information from +0x88 offset
// Just copy directly (color index 0 = transparent)
// Pixel values already include palette information from +0x88
// offset Just copy directly (color index 0 = transparent)
bg2_data[dest_pos] = pixel_value;
}
}
}
}
}
// Update surface with rendered pixel data
tiles_bg2_bitmap_.UpdateSurfacePixels();
// Queue texture update
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &tiles_bg2_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE,
&tiles_bg2_bitmap_);
return absl::OkStatus();
}
@@ -595,85 +625,87 @@ absl::Status TitleScreen::Save(Rom* rom) {
// Title screen uses compressed tilemap format
// We'll write the data back in the same compressed format
std::vector<uint8_t> compressed_data;
// Helper to write word (little endian)
auto WriteWord = [&compressed_data](uint16_t value) {
compressed_data.push_back(value & 0xFF);
compressed_data.push_back((value >> 8) & 0xFF);
};
// Compress BG2 layer (dest < 0x1000)
uint16_t bg2_dest = 0x0000;
for (int i = 0; i < 1024; i++) {
if (i == 0 || tiles_bg2_buffer_[i] != tiles_bg2_buffer_[i - 1]) {
// Start a new run
WriteWord(bg2_dest + i); // Destination address
// Count consecutive identical tiles
int run_length = 1;
uint16_t tile_value = tiles_bg2_buffer_[i];
while (i + run_length < 1024 && tiles_bg2_buffer_[i + run_length] == tile_value) {
while (i + run_length < 1024 &&
tiles_bg2_buffer_[i + run_length] == tile_value) {
run_length++;
}
// Write length/flags (bit 14 = fixsource if run > 1)
uint16_t length_flags = (run_length - 1) * 2; // Length in bytes
if (run_length > 1) {
length_flags |= 0x4000; // fixsource flag
}
WriteWord(length_flags);
// Write tile data
WriteWord(tile_value);
i += run_length - 1; // Skip already processed tiles
}
}
// Compress BG1 layer (dest >= 0x1000)
uint16_t bg1_dest = 0x1000;
for (int i = 0; i < 1024; i++) {
if (i == 0 || tiles_bg1_buffer_[i] != tiles_bg1_buffer_[i - 1]) {
// Start a new run
WriteWord(bg1_dest + i); // Destination address
// Count consecutive identical tiles
int run_length = 1;
uint16_t tile_value = tiles_bg1_buffer_[i];
while (i + run_length < 1024 && tiles_bg1_buffer_[i + run_length] == tile_value) {
while (i + run_length < 1024 &&
tiles_bg1_buffer_[i + run_length] == tile_value) {
run_length++;
}
// Write length/flags (bit 14 = fixsource if run > 1)
uint16_t length_flags = (run_length - 1) * 2; // Length in bytes
if (run_length > 1) {
length_flags |= 0x4000; // fixsource flag
}
WriteWord(length_flags);
// Write tile data
WriteWord(tile_value);
i += run_length - 1; // Skip already processed tiles
}
}
// Write terminator byte
compressed_data.push_back(0x80);
// Calculate ROM address to write to
ASSIGN_OR_RETURN(uint8_t byte0, rom->ReadByte(0x137A + 3));
ASSIGN_OR_RETURN(uint8_t byte1, rom->ReadByte(0x1383 + 3));
ASSIGN_OR_RETURN(uint8_t byte2, rom->ReadByte(0x138C + 3));
int pos = (byte2 << 16) + (byte1 << 8) + byte0;
int write_pos = SnesToPc(pos);
// Write compressed data to ROM
for (size_t i = 0; i < compressed_data.size(); i++) {
RETURN_IF_ERROR(rom->WriteByte(write_pos + i, compressed_data[i]));
}
return absl::OkStatus();
}
@@ -681,17 +713,17 @@ absl::Status TitleScreen::RenderCompositeLayer(bool show_bg1, bool show_bg2) {
auto& composite_data = title_composite_bitmap_.mutable_data();
const auto& bg1_data = tiles_bg1_bitmap_.vector();
const auto& bg2_data = tiles_bg2_bitmap_.vector();
// Clear to transparent (color index 0)
std::fill(composite_data.begin(), composite_data.end(), 0);
// Layer BG2 first (if visible) - background layer
if (show_bg2) {
for (int i = 0; i < 256 * 256; i++) {
composite_data[i] = bg2_data[i];
}
}
// Layer BG1 on top (if visible), respecting transparency
if (show_bg1) {
for (int i = 0; i < 256 * 256; i++) {
@@ -703,14 +735,14 @@ absl::Status TitleScreen::RenderCompositeLayer(bool show_bg1, bool show_bg2) {
}
}
}
// Copy pixel data to SDL surface
title_composite_bitmap_.UpdateSurfacePixels();
// Queue texture update
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &title_composite_bitmap_);
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::UPDATE,
&title_composite_bitmap_);
return absl::OkStatus();
}

View File

@@ -33,7 +33,7 @@ class TitleScreen {
auto& bg1_buffer() { return tiles_bg1_buffer_; }
auto& bg2_buffer() { return tiles_bg2_buffer_; }
auto& oam_buffer() { return oam_data_; }
// Mutable accessors for editing
auto& mutable_bg1_buffer() { return tiles_bg1_buffer_; }
auto& mutable_bg2_buffer() { return tiles_bg2_buffer_; }

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_];
@@ -701,8 +702,8 @@ void Sprite::Draw() {
} else if (id_ == 0x8E) // Terrorpin
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 12);
}
}
if (id_ == 0x8F) // Slime
{
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
@@ -876,7 +877,7 @@ void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
if (sizex <= 0 || sizey <= 0) {
return;
}
if (srcx < 0 || srcy < 0 || pal < 0) {
return;
}
@@ -884,12 +885,12 @@ void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
x += 16;
y += 16;
int drawid_ = (srcx + (srcy * 16)) + 512;
// Validate drawid_ is within reasonable bounds
if (drawid_ < 0 || drawid_ > 4096) {
return;
}
for (auto yl = 0; yl < sizey * 8; yl++) {
for (auto xl = 0; xl < (sizex * 8) / 2; xl++) {
int mx = xl;
@@ -907,20 +908,20 @@ void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
int tx = ((drawid_ / 0x10) * 0x400) +
((drawid_ - ((drawid_ / 0x10) * 0x10)) * 8);
// Validate graphics buffer access
int gfx_index = tx + (yl * 0x80) + xl;
if (gfx_index < 0 || gfx_index >= static_cast<int>(current_gfx_.size())) {
continue; // Skip this pixel if out of bounds
continue; // Skip this pixel if out of bounds
}
auto pixel = current_gfx_[gfx_index];
// nx,ny = object position, xx,yy = tile position, xl,yl = pixel
// position
int index = (x) + (y * 64) + (mx + (my * 0x80));
// Validate preview buffer access
if (index >= 0 && index < static_cast<int>(preview_gfx_.size()) &&
if (index >= 0 && index < static_cast<int>(preview_gfx_.size()) &&
index <= 4096) {
preview_gfx_[index] = (uint8_t)((pixel & 0x0F) + 112 + (pal * 8));
}

View File

@@ -331,7 +331,8 @@ 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_; }

View File

@@ -48,7 +48,8 @@ std::string SpriteBuilder::BuildProperties() const {
// Build the properties
for (int i = 0; i < 27; ++i) {
std::string property = "00";
if (!properties[i].empty()) property = properties[i];
if (!properties[i].empty())
property = properties[i];
ss << kSpriteProperties[i] << " = $" << property << std::endl;
}
return ss.str();
@@ -87,7 +88,9 @@ SpriteAction& SpriteAction::SetNextAction(const std::string& nextActionName) {
return *this;
}
std::string SpriteAction::GetConfiguration() const { return ""; }
std::string SpriteAction::GetConfiguration() const {
return "";
}
// ============================================================================

View File

@@ -1,4 +1,5 @@
#include "zelda3/zelda3_labels.h"
#include <cstddef>
#include <string>
#include <unordered_map>

View File

@@ -12,68 +12,72 @@ namespace zelda3 {
/**
* @struct Zelda3Labels
* @brief Centralized default labels for all Zelda 3 resources
*
* This structure contains all the default names/labels for various game resources.
* These labels are embedded directly into the project file format and are always
* available to the AI agents (Ollama/Gemini) without requiring external files.
*
* This structure contains all the default names/labels for various game
* resources. These labels are embedded directly into the project file format
* and are always available to the AI agents (Ollama/Gemini) without requiring
* external files.
*/
struct Zelda3Labels {
// Dungeon/Room names (296 rooms total)
static const std::vector<std::string>& GetRoomNames();
// Entrance names (133 entrances)
static const std::vector<std::string>& GetEntranceNames();
// Sprite names (256 sprites)
static const std::vector<std::string>& GetSpriteNames();
// Overlord names (14 overlords)
static const std::vector<std::string>& GetOverlordNames();
// Overworld map names (160 maps: 64 light world + 64 dark world + 32 special)
static const std::vector<std::string>& GetOverworldMapNames();
// Item names (all collectible items)
static const std::vector<std::string>& GetItemNames();
// Music track names
static const std::vector<std::string>& GetMusicTrackNames();
// Graphics sheet names
static const std::vector<std::string>& GetGraphicsSheetNames();
// Room object type names
static const std::vector<std::string>& GetType1RoomObjectNames();
static const std::vector<std::string>& GetType2RoomObjectNames();
static const std::vector<std::string>& GetType3RoomObjectNames();
// Room effect names
static const std::vector<std::string>& GetRoomEffectNames();
// Room tag names
static const std::vector<std::string>& GetRoomTagNames();
// Tile type names
static const std::vector<std::string>& GetTileTypeNames();
/**
* @brief Convert all labels to a structured map for project embedding
* @return Map of resource type -> (id -> name) for all resources
*/
static std::unordered_map<std::string, std::unordered_map<std::string, std::string>> ToResourceLabels();
static std::unordered_map<std::string,
std::unordered_map<std::string, std::string>>
ToResourceLabels();
/**
* @brief Get a label by resource type and ID
* @param resource_type The type of resource (e.g., "room", "entrance", "sprite")
* @param resource_type The type of resource (e.g., "room", "entrance",
* "sprite")
* @param id The numeric ID of the resource
* @param default_value Fallback value if label not found
* @return The label string
*/
static std::string GetLabel(const std::string& resource_type, int id,
const std::string& default_value = "");
static std::string GetLabel(const std::string& resource_type, int id,
const std::string& default_value = "");
};
} // namespace zelda3
} // namespace yaze
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_ZELDA3_LABELS_H
#endif // YAZE_APP_ZELDA3_ZELDA3_LABELS_H

View File

@@ -2,6 +2,7 @@ set(
YAZE_APP_ZELDA3_SRC
zelda3/dungeon/dungeon_editor_system.cc
zelda3/dungeon/dungeon_object_editor.cc
zelda3/dungeon/dungeon_object_registry.cc
zelda3/dungeon/object_drawer.cc
zelda3/dungeon/object_parser.cc
zelda3/dungeon/room.cc