backend-infra-engineer: Release v0.3.3 snapshot
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
37
src/zelda3/dungeon/dungeon_object_registry.cc
Normal file
37
src/zelda3/dungeon/dungeon_object_registry.cc
Normal 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
|
||||
38
src/zelda3/dungeon/dungeon_object_registry.h
Normal file
38
src/zelda3/dungeon/dungeon_object_registry.h
Normal 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
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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_));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¤t_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 ¤t_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();
|
||||
|
||||
153
src/zelda3/overworld/overworld_version_helper.h
Normal file
153
src/zelda3/overworld/overworld_version_helper.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "zelda3/zelda3_labels.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user