backend-infra-engineer: Release v0.3.2 snapshot

This commit is contained in:
scawful
2025-10-17 12:10:25 -04:00
parent 4371618a9b
commit 3d71417f62
857 changed files with 174954 additions and 45626 deletions

447
src/zelda3/common.h Normal file
View File

@@ -0,0 +1,447 @@
#ifndef YAZE_APP_ZELDA3_COMMON_H
#define YAZE_APP_ZELDA3_COMMON_H
#include <cstdint>
#include <string>
/**
* @namespace yaze::zelda3
* @brief Zelda 3 specific classes and functions.
*/
namespace yaze::zelda3{
/**
* @class GameEntity
* @brief Base class for all overworld and dungeon entities.
*/
class GameEntity {
public:
enum EntityType {
kEntrance = 0,
kExit = 1,
kItem = 2,
kSprite = 3,
kTransport = 4,
kMusic = 5,
kTilemap = 6,
kProperties = 7,
kDungeonSprite = 8,
} entity_type_;
int x_ = 0;
int y_ = 0;
int game_x_ = 0;
int game_y_ = 0;
int entity_id_ = 0;
uint16_t map_id_ = 0;
auto set_x(int x) { x_ = x; }
auto set_y(int y) { y_ = y; }
GameEntity() = default;
virtual ~GameEntity() {}
virtual void UpdateMapProperties(uint16_t map_id) = 0;
};
constexpr int kNumOverworldMaps = 160;
// 1 byte, not 0 if enabled
// vanilla, v2, v3
constexpr int OverworldCustomASMHasBeenApplied = 0x140145;
constexpr const char* kEntranceNames[] = {
"Link's House Intro",
"Link's House Post-intro",
"Sanctuary",
"Hyrule Castle West",
"Hyrule Castle Central",
"Hyrule Castle East",
"Death Mountain Express (Lower)",
"Death Mountain Express (Upper)",
"Eastern Palace",
"Desert Palace Central",
"Desert Palace East",
"Desert Palace West",
"Desert Palace Boss Lair",
"Kakariko Elder's House West",
"Kakariko Elder's House East",
"Kakariko Angry Bros West",
"Kakariko Angry Bros East",
"Mad Batter Lair",
"Under Lumberjacks' Weird Tree",
"Death Mountain Maze 0000",
"Death Mountain Maze 0001",
"Turtle Rock Mountainface 1",
"Death Mountain Cape Heart Piece Cave (Lower)",
"Death Mountain Cape Heart Piece Cave (Upper)",
"Turtle Rock Mountainface 2",
"Turtle Rock Mountainface 3",
"Death Mountain Maze 0002",
"Death Mountain Maze 0003",
"Death Mountain Maze 0004",
"Death Mountain Maze 0005",
"Death Mountain Maze 0006",
"Death Mountain Maze 0007",
"Death Mountain Maze 0008",
"Spectacle Rock Maze 1",
"Spectacle Rock Maze 2",
"Spectacle Rock Maze 3",
"Hyrule Castle Tower",
"Swamp Palace",
"Palace of Darkness",
"Misery Mire",
"Skull Woods 1",
"Skull Woods 2",
"Skull Woods Big Chest",
"Skull Woods Boss Lair",
"Lost Woods Thieves' Lair",
"Ice Palace",
"Death Mountain Escape West",
"Death Mountain Escape East",
"Death Mountain Elder's Cave (Lower)",
"Death Mountain Elder's Cave (Upper)",
"Hyrule Castle Secret Cellar",
"Tower of Hera",
"Thieves's Town",
"Turtle Rock Main",
"Ganon's Pyramid Sanctum (Lower)",
"Ganon's Tower",
"Fairy Cave 1",
"Kakariko Western Well",
"Death Mountain Maze 0009",
"Death Mountain Maze 0010",
"Treasure Shell Game 1",
"Storyteller Cave 1",
"Snitch House 1",
"Snitch House 2",
"SickBoy House",
"Byrna Gauntlet",
"Kakariko Pub South",
"Kakariko Pub North",
"Kakariko Inn",
"Sahasrahlah's Disco Infernum",
"Kakariko's Lame Shop",
"Village of Outcasts Chest Game",
"Village of Outcasts Orphanage",
"Kakariko Library",
"Kakariko Storage Shed",
"Kakariko Sweeper Lady's House",
"Potion Shop",
"Aginah's Desert Cottage",
"Watergate",
"Death Mountain Maze 0011",
"Fairy Cave 2",
"Refill Cave 0001",
"Refill Cave 0002",
"The Bomb \"Shop\"",
"Village of Outcasts Retirement Center",
"Fairy Cave 3",
"Good Bee Cave",
"General Store 1",
"General Store 2",
"Archery Game",
"Storyteller Cave 2",
"Hall of the Invisibility Cape",
"Pond of Wishing",
"Pond of Happiness",
"Fairy Cave 4",
"Swamp of Evil Heart Piece Hall",
"General Store 3",
"Blind's Old Hideout",
"Storyteller Cave 3",
"Warped Pond of Wishing",
"Chez Smithies",
"Fortune Teller 1",
"Fortune Teller 2",
"Chest Shell Game 2",
"Storyteller Cave 4",
"Storyteller Cave 5",
"Storyteller Cave 6",
"Village House 1",
"Thief Hideout 1",
"Thief Hideout 2",
"Heart Piece Cave 1",
"Thief Hideout 3",
"Refill Cave 3",
"Fairy Cave 5",
"Heart Piece Cave 2",
"Hyrule Castle Prison",
"Hyrule Castle Throne Room",
"Hyrule Tower Agahnim's Sanctum",
"Skull Woods 3 (Drop In)",
"Skull Woods 4 (Drop In)",
"Skull Woods 5 (Drop In)",
"Skull Woods 6 (Drop In)",
"Lost Woods Thieves' Hideout (Drop In)",
"Ganon's Pyramid Sanctum (Upper)",
"Fairy Cave 6 (Drop In)",
"Hyrule Castle Secret Cellar (Drop In)",
"Mad Batter Lair (Drop In)",
"Under Lumberjacks' Weird Tree (Drop In)",
"Kakariko Western Well (Drop In)",
"Hyrule Sewers Goodies Room (Drop In)",
"Chris Houlihan Room (Drop In)",
"Heart Piece Cave 3 (Drop In)",
"Ice Rod Cave"};
static const std::string TileTypeNames[] = {
"$00 Nothing (standard floor)",
"$01 Collision",
"$02 Collision",
"$03 Collision",
"$04 Collision",
"$05 Nothing (unused?)",
"$06 Nothing (unused?)",
"$07 Nothing (unused?)",
"$08 Deep water",
"$09 Shallow water",
"$0A Unknown? Possibly unused",
"$0B Collision (different in Overworld and unknown)",
"$0C Overlay mask",
"$0D Spike floor",
"$0E GT ice",
"$0F Ice palace ice",
"$10 Slope ◤",
"$11 Slope ◥",
"$12 Slope ◣",
"$13 Slope ◢",
"$14 Nothing (unused?)",
"$15 Nothing (unused?)",
"$16 Nothing (unused?)",
"$17 Nothing (unused?)",
"$18 Slope ◤",
"$19 Slope ◥",
"$1A Slope ◣",
"$1B Slope ◢",
"$1C Layer 2 overlay",
"$1D North single-layer auto stairs",
"$1E North layer-swap auto stairs",
"$1F North layer-swap auto stairs",
"$20 Pit",
"$21 Nothing (unused?)",
"$22 Manual stairs",
"$23 Pot switch",
"$24 Pressure switch",
"$25 Nothing (unused but referenced by somaria blocks)",
"$26 Collision (near stairs?)",
"$27 Brazier/Fence/Statue/Block/General hookable things",
"$28 North ledge",
"$29 South ledge",
"$2A East ledge",
"$2B West ledge",
"$2C ◤ ledge",
"$2D ◣ ledge",
"$2E ◥ ledge",
"$2F ◢ ledge",
"$30 Straight inter-room stairs south/up 0",
"$31 Straight inter-room stairs south/up 1",
"$32 Straight inter-room stairs south/up 2",
"$33 Straight inter-room stairs south/up 3",
"$34 Straight inter-room stairs north/down 0",
"$35 Straight inter-room stairs north/down 1",
"$36 Straight inter-room stairs north/down 2",
"$37 Straight inter-room stairs north/down 3",
"$38 Straight inter-room stairs north/down edge",
"$39 Straight inter-room stairs south/up edge",
"$3A Star tile (inactive on load)",
"$3B Star tile (active on load)",
"$3C Nothing (unused?)",
"$3D South single-layer auto stairs",
"$3E South layer-swap auto stairs",
"$3F South layer-swap auto stairs",
"$40 Thick grass",
"$41 Nothing (unused?)",
"$42 Gravestone / Tower of hera ledge shadows??",
"$43 Skull Woods entrance/Hera columns???",
"$44 Spike",
"$45 Nothing (unused?)",
"$46 Desert Tablet",
"$47 Nothing (unused?)",
"$48 Diggable ground",
"$49 Nothing (unused?)",
"$4A Diggable ground",
"$4B Warp tile",
"$4C Nothing (unused?) | Something unknown in overworld",
"$4D Nothing (unused?) | Something unknown in overworld",
"$4E Square corners in EP overworld",
"$4F Square corners in EP overworld",
"$50 Green bush",
"$51 Dark bush",
"$52 Gray rock",
"$53 Black rock",
"$54 Hint tile/Sign",
"$55 Big gray rock",
"$56 Big black rock",
"$57 Bonk rocks",
"$58 Chest 0",
"$59 Chest 1",
"$5A Chest 2",
"$5B Chest 3",
"$5C Chest 4",
"$5D Chest 5",
"$5E Spiral stairs",
"$5F Spiral stairs",
"$60 Rupee tile",
"$61 Nothing (unused?)",
"$62 Bombable floor",
"$63 Minigame chest",
"$64 Nothing (unused?)",
"$65 Nothing (unused?)",
"$66 Crystal peg down",
"$67 Crystal peg up",
"$68 Upwards conveyor",
"$69 Downwards conveyor",
"$6A Leftwards conveyor",
"$6B Rightwards conveyor",
"$6C North vines",
"$6D South vines",
"$6E West vines",
"$6F East vines",
"$70 Pot/Hammer peg/Push block 00",
"$71 Pot/Hammer peg/Push block 01",
"$72 Pot/Hammer peg/Push block 02",
"$73 Pot/Hammer peg/Push block 03",
"$74 Pot/Hammer peg/Push block 04",
"$75 Pot/Hammer peg/Push block 05",
"$76 Pot/Hammer peg/Push block 06",
"$77 Pot/Hammer peg/Push block 07",
"$78 Pot/Hammer peg/Push block 08",
"$79 Pot/Hammer peg/Push block 09",
"$7A Pot/Hammer peg/Push block 0A",
"$7B Pot/Hammer peg/Push block 0B",
"$7C Pot/Hammer peg/Push block 0C",
"$7D Pot/Hammer peg/Push block 0D",
"$7E Pot/Hammer peg/Push block 0E",
"$7F Pot/Hammer peg/Push block 0F",
"$80 North/South door",
"$81 East/West door",
"$82 North/South shutter door",
"$83 East/West shutter door",
"$84 North/South layer 2 door",
"$85 East/West layer 2 door",
"$86 North/South layer 2 shutter door",
"$87 East/West layer 2 shutter door",
"$88 Some type of door (?)",
"$89 East/West transport door",
"$8A Some type of door (?)",
"$8B Some type of door (?)",
"$8C Some type of door (?)",
"$8D Some type of door (?)",
"$8E Entrance door",
"$8F Entrance door",
"$90 Layer toggle shutter door (?)",
"$91 Layer toggle shutter door (?)",
"$92 Layer toggle shutter door (?)",
"$93 Layer toggle shutter door (?)",
"$94 Layer toggle shutter door (?)",
"$95 Layer toggle shutter door (?)",
"$96 Layer toggle shutter door (?)",
"$97 Layer toggle shutter door (?)",
"$98 Layer+Dungeon toggle shutter door (?)",
"$99 Layer+Dungeon toggle shutter door (?)",
"$9A Layer+Dungeon toggle shutter door (?)",
"$9B Layer+Dungeon toggle shutter door (?)",
"$9C Layer+Dungeon toggle shutter door (?)",
"$9D Layer+Dungeon toggle shutter door (?)",
"$9E Layer+Dungeon toggle shutter door (?)",
"$9F Layer+Dungeon toggle shutter door (?)",
"$A0 North/South Dungeon swap door",
"$A1 Dungeon toggle door (?)",
"$A2 Dungeon toggle door (?)",
"$A3 Dungeon toggle door (?)",
"$A4 Dungeon toggle door (?)",
"$A5 Dungeon toggle door (?)",
"$A6 Nothing (unused?)",
"$A7 Nothing (unused?)",
"$A8 Layer+Dungeon toggle shutter door (?)",
"$A9 Layer+Dungeon toggle shutter door (?)",
"$AA Layer+Dungeon toggle shutter door (?)",
"$AB Layer+Dungeon toggle shutter door (?)",
"$AC Layer+Dungeon toggle shutter door (?)",
"$AD Layer+Dungeon toggle shutter door (?)",
"$AE Layer+Dungeon toggle shutter door (?)",
"$AF Layer+Dungeon toggle shutter door (?)",
"$B0 Somaria ─",
"$B1 Somaria │",
"$B2 Somaria ┌",
"$B3 Somaria └",
"$B4 Somaria ┐",
"$B5 Somaria ┘",
"$B6 Somaria ⍰ 1 way",
"$B7 Somaria ┬",
"$B8 Somaria ┴",
"$B9 Somaria ├",
"$BA Somaria ┤",
"$BB Somaria ┼",
"$BC Somaria ⍰ 2 way",
"$BD Somaria ┼ crossover",
"$BE Pipe entrance",
"$BF Nothing (unused?)",
"$C0 Torch 00",
"$C1 Torch 01",
"$C2 Torch 02",
"$C3 Torch 03",
"$C4 Torch 04",
"$C5 Torch 05",
"$C6 Torch 06",
"$C7 Torch 07",
"$C8 Torch 08",
"$C9 Torch 09",
"$CA Torch 0A",
"$CB Torch 0B",
"$CC Torch 0C",
"$CD Torch 0D",
"$CE Torch 0E",
"$CF Torch 0F",
"$D0 Nothing (unused?)",
"$D1 Nothing (unused?)",
"$D2 Nothing (unused?)",
"$D3 Nothing (unused?)",
"$D4 Nothing (unused?)",
"$D5 Nothing (unused?)",
"$D6 Nothing (unused?)",
"$D7 Nothing (unused?)",
"$D8 Nothing (unused?)",
"$D9 Nothing (unused?)",
"$DA Nothing (unused?)",
"$DB Nothing (unused?)",
"$DC Nothing (unused?)",
"$DD Nothing (unused?)",
"$DE Nothing (unused?)",
"$DF Nothing (unused?)",
"$E0 Nothing (unused?)",
"$E1 Nothing (unused?)",
"$E2 Nothing (unused?)",
"$E3 Nothing (unused?)",
"$E4 Nothing (unused?)",
"$E5 Nothing (unused?)",
"$E6 Nothing (unused?)",
"$E7 Nothing (unused?)",
"$E8 Nothing (unused?)",
"$E9 Nothing (unused?)",
"$EA Nothing (unused?)",
"$EB Nothing (unused?)",
"$EC Nothing (unused?)",
"$ED Nothing (unused?)",
"$EE Nothing (unused?)",
"$EF Nothing (unused?)",
"$F0 Door 0 bottom",
"$F1 Door 1 bottom",
"$F2 Door 2 bottom",
"$F3 Door 3 bottom",
"$F4 Door X bottom? (unused?)",
"$F5 Door X bottom? (unused?)",
"$F6 Door X bottom? (unused?)",
"$F7 Door X bottom? (unused?)",
"$F8 Door 0 top",
"$F9 Door 1 top",
"$FA Door 2 top",
"$FB Door 3 top",
"$FC Door X top? (unused?)",
"$FD Door X top? (unused?)",
"$FE Door X top? (unused?)",
"$FF Door X top? (unused?)"};
} // namespace yaze::zelda3
#endif // YAZE_APP_ZELDA3_COMMON_H

View File

@@ -0,0 +1,816 @@
#include "dungeon_editor_system.h"
#include <algorithm>
#include <chrono>
#include "absl/strings/str_format.h"
namespace yaze {
namespace zelda3 {
DungeonEditorSystem::DungeonEditorSystem(Rom* rom) : rom_(rom) {}
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";
dungeon_settings_.description = "A dungeon created with the editor";
dungeon_settings_.total_rooms = 0;
dungeon_settings_.starting_room_id = 0;
dungeon_settings_.boss_room_id = 0;
dungeon_settings_.music_theme_id = 0;
dungeon_settings_.color_palette_id = 0;
dungeon_settings_.has_map = true;
dungeon_settings_.has_compass = true;
dungeon_settings_.has_big_key = true;
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::LoadDungeon(int dungeon_id) {
// TODO: Implement actual dungeon loading from ROM
editor_state_.current_room_id = 0;
editor_state_.is_dirty = false;
editor_state_.auto_save_enabled = true;
editor_state_.last_save_time = std::chrono::steady_clock::now();
return absl::OkStatus();
}
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();
}
absl::Status DungeonEditorSystem::SaveRoom(int room_id) {
// TODO: Implement actual room saving to ROM
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::ReloadRoom(int room_id) {
// TODO: Implement actual room reloading from ROM
return absl::OkStatus();
}
void DungeonEditorSystem::SetEditorMode(EditorMode mode) {
editor_state_.current_mode = mode;
}
DungeonEditorSystem::EditorMode DungeonEditorSystem::GetEditorMode() const {
return editor_state_.current_mode;
}
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();
}
int DungeonEditorSystem::GetCurrentRoom() const {
return editor_state_.current_room_id;
}
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) {
// TODO: Implement room creation
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::DeleteRoom(int room_id) {
// TODO: Implement room deletion
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::DuplicateRoom(int source_room_id, int target_room_id) {
// TODO: Implement room duplication
return absl::OkStatus();
}
std::shared_ptr<DungeonObjectEditor> DungeonEditorSystem::GetObjectEditor() {
if (!object_editor_) {
object_editor_ = std::make_shared<DungeonObjectEditor>(rom_);
}
return object_editor_;
}
absl::Status DungeonEditorSystem::SetObjectEditorMode() {
editor_state_.current_mode = EditorMode::kObjects;
return absl::OkStatus();
}
// Sprite management
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();
}
absl::Status DungeonEditorSystem::RemoveSprite(int sprite_id) {
auto it = sprites_.find(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) {
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) {
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) {
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) {
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) {
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();
}
absl::Status DungeonEditorSystem::SetSpriteActive(int sprite_id, bool active) {
auto it = sprites_.find(sprite_id);
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();
}
// Item management
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();
}
absl::Status DungeonEditorSystem::RemoveItem(int item_id) {
auto it = items_.find(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) {
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) {
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) {
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) {
std::vector<ItemData> typed_items;
for (const auto& [id, item] : items_) {
if (item.type == type) {
typed_items.push_back(item);
}
}
return typed_items;
}
absl::Status DungeonEditorSystem::MoveItem(int item_id, int new_x, int new_y) {
auto it = items_.find(item_id);
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();
}
absl::Status DungeonEditorSystem::SetItemHidden(int item_id, bool hidden) {
auto it = items_.find(item_id);
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) {
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();
}
absl::Status DungeonEditorSystem::RemoveEntrance(int entrance_id) {
auto it = entrances_.find(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) {
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) {
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) {
std::vector<EntranceData> room_entrances;
for (const auto& [id, entrance] : entrances_) {
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) {
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) {
EntranceData entrance_data;
entrance_data.source_room_id = room1_id;
entrance_data.target_room_id = room2_id;
entrance_data.source_x = x1;
entrance_data.source_y = y1;
entrance_data.target_x = x2;
entrance_data.target_y = y2;
entrance_data.type = EntranceType::kNormal;
entrance_data.is_bidirectional = true;
return AddEntrance(entrance_data);
}
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)) {
it = entrances_.erase(it);
} else {
++it;
}
}
return absl::OkStatus();
}
// Door management
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();
}
absl::Status DungeonEditorSystem::RemoveDoor(int door_id) {
auto it = doors_.find(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) {
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) {
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) {
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;
}
absl::Status DungeonEditorSystem::SetDoorLocked(int door_id, bool locked) {
auto it = doors_.find(door_id);
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) {
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();
}
// Chest management
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();
}
absl::Status DungeonEditorSystem::RemoveChest(int chest_id) {
auto it = chests_.find(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) {
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) {
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) {
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) {
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();
}
absl::Status DungeonEditorSystem::SetChestOpened(int chest_id, bool opened) {
auto it = chests_.find(chest_id);
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) {
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) {
auto it = room_properties_.find(room_id);
if (it == room_properties_.end()) {
// Return default properties
RoomProperties default_properties;
default_properties.room_id = room_id;
default_properties.name = absl::StrFormat("Room %d", room_id);
default_properties.description = "";
default_properties.dungeon_id = 0;
default_properties.floor_level = 0;
default_properties.is_boss_room = false;
default_properties.is_save_room = false;
default_properties.is_shop_room = false;
default_properties.music_id = 0;
default_properties.ambient_sound_id = 0;
return default_properties;
}
return it->second;
}
// Dungeon-wide settings
absl::Status DungeonEditorSystem::SetDungeonSettings(const DungeonSettings& settings) {
dungeon_settings_ = settings;
return absl::OkStatus();
}
absl::StatusOr<DungeonEditorSystem::DungeonSettings> DungeonEditorSystem::GetDungeonSettings() {
return dungeon_settings_;
}
// Validation and error checking
absl::Status DungeonEditorSystem::ValidateRoom(int room_id) {
// TODO: Implement room validation
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::ValidateDungeon() {
// TODO: Implement dungeon validation
return absl::OkStatus();
}
std::vector<std::string> DungeonEditorSystem::GetValidationErrors(int room_id) {
// TODO: Implement validation error collection
return {};
}
std::vector<std::string> DungeonEditorSystem::GetDungeonValidationErrors() {
// TODO: Implement dungeon validation error collection
return {};
}
// Rendering and preview
absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderRoom(int room_id) {
// TODO: Implement room rendering
return gfx::Bitmap();
}
absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderRoomPreview(int room_id, EditorMode mode) {
// TODO: Implement room preview rendering
return gfx::Bitmap();
}
absl::StatusOr<gfx::Bitmap> DungeonEditorSystem::RenderDungeonMap() {
// TODO: Implement dungeon map rendering
return gfx::Bitmap();
}
// Import/Export functionality
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) {
// TODO: Implement room export
return absl::OkStatus();
}
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) {
// TODO: Implement dungeon export
return absl::OkStatus();
}
// Undo/Redo system
absl::Status DungeonEditorSystem::Undo() {
if (!CanUndo()) {
return absl::FailedPreconditionError("Nothing to undo");
}
// TODO: Implement undo functionality
return absl::OkStatus();
}
absl::Status DungeonEditorSystem::Redo() {
if (!CanRedo()) {
return absl::FailedPreconditionError("Nothing to redo");
}
// TODO: Implement redo functionality
return absl::OkStatus();
}
bool DungeonEditorSystem::CanUndo() const {
return !undo_history_.empty();
}
bool DungeonEditorSystem::CanRedo() const {
return !redo_history_.empty();
}
void DungeonEditorSystem::ClearHistory() {
undo_history_.clear();
redo_history_.clear();
}
// Event callbacks
void DungeonEditorSystem::SetRoomChangedCallback(RoomChangedCallback callback) {
room_changed_callback_ = callback;
}
void DungeonEditorSystem::SetSpriteChangedCallback(SpriteChangedCallback callback) {
sprite_changed_callback_ = callback;
}
void DungeonEditorSystem::SetItemChangedCallback(ItemChangedCallback callback) {
item_changed_callback_ = callback;
}
void DungeonEditorSystem::SetEntranceChangedCallback(EntranceChangedCallback callback) {
entrance_changed_callback_ = callback;
}
void DungeonEditorSystem::SetDoorChangedCallback(DoorChangedCallback callback) {
door_changed_callback_ = callback;
}
void DungeonEditorSystem::SetChestChangedCallback(ChestChangedCallback callback) {
chest_changed_callback_ = callback;
}
void DungeonEditorSystem::SetModeChangedCallback(ModeChangedCallback callback) {
mode_changed_callback_ = callback;
}
void DungeonEditorSystem::SetValidationCallback(ValidationCallback callback) {
validation_callback_ = callback;
}
// Helper methods
int DungeonEditorSystem::GenerateSpriteId() {
return next_sprite_id_++;
}
int DungeonEditorSystem::GenerateItemId() {
return next_item_id_++;
}
int DungeonEditorSystem::GenerateEntranceId() {
return next_entrance_id_++;
}
int DungeonEditorSystem::GenerateDoorId() {
return next_door_id_++;
}
int DungeonEditorSystem::GenerateChestId() {
return next_chest_id_++;
}
Rom* DungeonEditorSystem::GetROM() const {
return rom_;
}
bool DungeonEditorSystem::IsDirty() const {
return editor_state_.is_dirty;
}
void DungeonEditorSystem::SetROM(Rom* rom) {
rom_ = rom;
// Update object editor with new ROM if it exists
if (object_editor_) {
object_editor_->SetROM(rom);
}
}
// Factory function
std::unique_ptr<DungeonEditorSystem> CreateDungeonEditorSystem(Rom* rom) {
return std::make_unique<DungeonEditorSystem>(rom);
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,492 @@
#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 <optional>
#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/rom.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/sprite/sprite.h"
#include "dungeon_object_editor.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)
* - Item placement and management
* - Entrance/exit data editing
* - Door configuration
* - Chest and treasure management
* - Room properties and metadata
* - Dungeon-wide settings
*/
class DungeonEditorSystem {
public:
// Editor modes
enum class EditorMode {
kObjects, // Object editing mode
kSprites, // Sprite editing mode
kItems, // Item placement mode
kEntrances, // Entrance/exit editing mode
kDoors, // Door configuration mode
kChests, // Chest management mode
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
};
// 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
};
// 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
};
// Editor state
struct EditorState {
EditorMode current_mode = EditorMode::kObjects;
int current_room_id = 0;
bool is_dirty = false; // Has unsaved changes
bool auto_save_enabled = true;
std::chrono::steady_clock::time_point last_save_time;
};
// Sprite editing data
struct SpriteData {
int sprite_id;
std::string name;
DungeonEditorSystem::SpriteType type;
int x, y;
int layer;
std::unordered_map<std::string, std::string> properties;
bool is_active = true;
};
// Item placement data
struct ItemData {
int item_id;
DungeonEditorSystem::ItemType type;
std::string name;
int x, y;
int room_id;
bool is_hidden = false;
std::unordered_map<std::string, std::string> properties;
};
// Entrance/exit data
struct EntranceData {
int entrance_id;
DungeonEditorSystem::EntranceType type;
std::string name;
int source_room_id;
int target_room_id;
int source_x, source_y;
int target_x, target_y;
bool is_bidirectional = true;
std::unordered_map<std::string, std::string> properties;
};
// Door configuration data
struct DoorData {
int door_id;
std::string name;
int room_id;
int x, y;
int direction; // 0=up, 1=right, 2=down, 3=left
int target_room_id;
int target_x, target_y;
bool requires_key = false;
int key_type = 0;
bool is_locked = false;
std::unordered_map<std::string, std::string> properties;
};
// Chest data
struct ChestData {
int chest_id;
int room_id;
int x, y;
bool is_big_chest = false;
int item_id;
int item_quantity = 1;
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;
absl::StatusOr<Room> GetRoom(int room_id);
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::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::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::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::Status DisconnectRooms(int room1_id, int room2_id);
// Door management
absl::Status AddDoor(const DoorData& door_data);
absl::Status RemoveDoor(int door_id);
absl::Status UpdateDoor(int door_id, const DoorData& door_data);
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);
// Chest management
absl::Status AddChest(const ChestData& chest_data);
absl::Status RemoveChest(int chest_id);
absl::Status UpdateChest(int chest_id, const ChestData& chest_data);
absl::StatusOr<ChestData> GetChest(int chest_id);
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;
std::string name;
std::string description;
int dungeon_id;
int floor_level;
bool is_boss_room = false;
bool is_save_room = false;
bool is_shop_room = false;
int music_id = 0;
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;
std::string name;
std::string description;
int total_rooms;
int starting_room_id;
int boss_room_id;
int music_theme_id;
int color_palette_id;
bool has_map = true;
bool has_compass = true;
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)>;
using ItemChangedCallback = std::function<void(int item_id)>;
using EntranceChangedCallback = std::function<void(int entrance_id)>;
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)>;
void SetRoomChangedCallback(RoomChangedCallback callback);
void SetSpriteChangedCallback(SpriteChangedCallback callback);
void SetItemChangedCallback(ItemChangedCallback callback);
void SetEntranceChangedCallback(EntranceChangedCallback callback);
void SetDoorChangedCallback(DoorChangedCallback callback);
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);
private:
// Internal helper methods
absl::Status InitializeObjectEditor();
absl::Status InitializeSpriteSystem();
absl::Status InitializeItemSystem();
absl::Status InitializeEntranceSystem();
absl::Status InitializeDoorSystem();
absl::Status InitializeChestSystem();
// Data management
absl::Status LoadRoomData(int room_id);
absl::Status SaveRoomData(int room_id);
absl::Status LoadSpriteData();
absl::Status SaveSpriteData();
absl::Status LoadItemData();
absl::Status SaveItemData();
absl::Status LoadEntranceData();
absl::Status SaveEntranceData();
absl::Status LoadDoorData();
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_;
std::unordered_map<int, ItemData> items_;
std::unordered_map<int, EntranceData> entrances_;
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_;
ItemChangedCallback item_changed_callback_;
EntranceChangedCallback entrance_changed_callback_;
DoorChangedCallback door_changed_callback_;
ChestChangedCallback chest_changed_callback_;
ModeChangedCallback mode_changed_callback_;
ValidationCallback validation_callback_;
// Undo/Redo system
struct UndoPoint {
EditorState state;
std::unordered_map<int, Room> rooms;
std::unordered_map<int, SpriteData> sprites;
std::unordered_map<int, ItemData> items;
std::unordered_map<int, EntranceData> entrances;
std::unordered_map<int, DoorData> doors;
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;
};
/**
* @brief Factory function to create dungeon editor system
*/
std::unique_ptr<DungeonEditorSystem> CreateDungeonEditorSystem(Rom* rom);
/**
* @brief Sprite type utilities
*/
namespace SpriteTypes {
/**
* @brief Get sprite information by ID
*/
struct SpriteInfo {
int id;
std::string name;
DungeonEditorSystem::SpriteType type;
std::string description;
int default_layer;
std::vector<std::pair<std::string, std::string>> default_properties;
bool is_interactive;
bool is_hostile;
int difficulty_rating;
};
absl::StatusOr<SpriteInfo> GetSpriteInfo(int sprite_id);
std::vector<SpriteInfo> GetAllSpriteInfos();
std::vector<SpriteInfo> GetSpritesByType(DungeonEditorSystem::SpriteType type);
absl::StatusOr<std::string> GetSpriteCategory(int sprite_id);
} // namespace SpriteTypes
/**
* @brief Item type utilities
*/
namespace ItemTypes {
/**
* @brief Get item information by ID
*/
struct ItemInfo {
int id;
std::string name;
DungeonEditorSystem::ItemType type;
std::string description;
int rarity;
int value;
std::vector<std::pair<std::string, std::string>> default_properties;
bool is_stackable;
int max_stack_size;
};
absl::StatusOr<ItemInfo> GetItemInfo(int item_id);
std::vector<ItemInfo> GetAllItemInfos();
std::vector<ItemInfo> GetItemsByType(DungeonEditorSystem::ItemType type);
absl::StatusOr<std::string> GetItemCategory(int item_id);
} // namespace ItemTypes
/**
* @brief Entrance type utilities
*/
namespace EntranceTypes {
/**
* @brief Get entrance information by ID
*/
struct EntranceInfo {
int id;
std::string name;
DungeonEditorSystem::EntranceType type;
std::string description;
std::vector<std::pair<std::string, std::string>> default_properties;
bool requires_key;
int key_type;
bool is_bidirectional;
};
absl::StatusOr<EntranceInfo> GetEntranceInfo(int entrance_id);
std::vector<EntranceInfo> GetAllEntranceInfos();
std::vector<EntranceInfo> GetEntrancesByType(DungeonEditorSystem::EntranceType type);
} // namespace EntranceTypes
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_DUNGEON_EDITOR_SYSTEM_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,345 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_DUNGEON_OBJECT_EDITOR_H
#define YAZE_APP_ZELDA3_DUNGEON_DUNGEON_OBJECT_EDITOR_H
#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/rom.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_object.h"
namespace yaze {
namespace zelda3 {
/**
* @brief Interactive dungeon object editor with scroll wheel support
*
* This class provides a comprehensive object editing system for dungeon rooms,
* including:
* - Object insertion and deletion
* - Object size editing with scroll wheel
* - Object position editing with mouse
* - Layer management
* - Real-time preview and validation
* - Undo/redo functionality
* - Object property editing
*/
class DungeonObjectEditor {
public:
// Editor modes
enum class Mode {
kSelect, // Select and move objects
kInsert, // Insert new objects
kDelete, // Delete objects
kEdit, // Edit object properties
kLayer, // Layer management
kPreview // Preview mode
};
// Object selection state
struct SelectionState {
std::vector<size_t> selected_objects; // Indices of selected objects
bool is_multi_select = false;
bool is_dragging = false;
int drag_start_x = 0;
int drag_start_y = 0;
};
// Object editing state
struct EditingState {
Mode current_mode = Mode::kSelect;
int current_layer = 0;
int current_object_type = 0x10; // Default to wall
int scroll_wheel_delta = 0;
bool is_editing_size = false;
bool is_editing_position = false;
int preview_x = 0;
int preview_y = 0;
int preview_size = 0x12; // Default size
};
// Editor configuration
struct EditorConfig {
bool snap_to_grid = true;
int grid_size = 16; // 16x16 pixel grid
bool show_grid = true;
bool show_preview = true;
bool auto_save = false;
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;
bool show_property_panel = true;
uint32_t selection_color = 0xFFFFFF00; // Yellow
uint32_t layer0_color = 0xFFFF0000; // Red tint
uint32_t layer1_color = 0xFF00FF00; // Green tint
uint32_t layer2_color = 0xFF0000FF; // Blue tint
};
// Undo/Redo system
struct UndoPoint {
std::vector<RoomObject> objects;
SelectionState selection;
EditingState editing;
std::chrono::steady_clock::time_point timestamp;
};
explicit DungeonObjectEditor(Rom* rom);
~DungeonObjectEditor() = default;
// Core editing operations
absl::Status LoadRoom(int room_id);
absl::Status SaveRoom();
absl::Status ClearRoom();
// Object manipulation
absl::Status InsertObject(int x, int y, int object_type, int size = 0x12,
int layer = 0);
absl::Status DeleteObject(size_t object_index);
absl::Status DeleteSelectedObjects();
absl::Status MoveObject(size_t object_index, int new_x, int new_y);
absl::Status ResizeObject(size_t object_index, int new_size);
absl::Status ChangeObjectType(size_t object_index, int new_type);
absl::Status ChangeObjectLayer(size_t object_index, int new_layer);
// Selection management
absl::Status SelectObject(int screen_x, int screen_y);
absl::Status SelectObjects(int start_x, int start_y, int end_x, int end_y);
absl::Status ClearSelection();
absl::Status AddToSelection(size_t object_index);
absl::Status RemoveFromSelection(size_t object_index);
// Mouse and scroll wheel handling
absl::Status HandleMouseClick(int x, int y, bool left_button,
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 HandleScrollWheel(int delta, int x, int y, bool ctrl_pressed);
absl::Status HandleKeyPress(int key_code, bool ctrl_pressed,
bool shift_pressed);
// Mode management
void SetMode(Mode mode);
Mode GetMode() const { return editing_state_.current_mode; }
// Layer management
void SetCurrentLayer(int layer);
int GetCurrentLayer() const { return editing_state_.current_layer; }
absl::StatusOr<std::vector<RoomObject>> GetObjectsByLayer(int layer);
absl::Status MoveObjectToLayer(size_t object_index, int layer);
// Object type management
void SetCurrentObjectType(int object_type);
int GetCurrentObjectType() const {
return editing_state_.current_object_type;
}
absl::StatusOr<std::vector<int>> GetAvailableObjectTypes();
absl::Status ValidateObjectType(int object_type);
// Rendering and preview
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
absl::Status HandleDragOperation(int current_x, int current_y);
// Undo/Redo functionality
absl::Status Undo();
absl::Status Redo();
bool CanUndo() const;
bool CanRedo() const;
void ClearHistory();
// Configuration
void SetROM(Rom* rom);
void SetConfig(const EditorConfig& config);
EditorConfig GetConfig() const { return config_; }
void SetSnapToGrid(bool enabled);
void SetGridSize(int size);
void SetShowGrid(bool enabled);
// Validation and error checking
absl::Status ValidateRoom();
absl::Status ValidateObject(const RoomObject& object);
std::vector<std::string> GetValidationErrors();
// Event callbacks
using ObjectChangedCallback =
std::function<void(size_t object_index, const RoomObject& object)>;
using RoomChangedCallback = std::function<void()>;
using SelectionChangedCallback = std::function<void(const SelectionState&)>;
void SetObjectChangedCallback(ObjectChangedCallback callback);
void SetRoomChangedCallback(RoomChangedCallback callback);
void SetSelectionChangedCallback(SelectionChangedCallback callback);
// Getters
const Room& GetRoom() const { return *current_room_; }
Room* GetMutableRoom() { return current_room_.get(); }
const SelectionState& GetSelection() const { return selection_state_; }
const EditingState& GetEditingState() const { return editing_state_; }
size_t GetObjectCount() const {
return current_room_ ? current_room_->GetTileObjects().size() : 0;
}
const std::vector<RoomObject>& GetObjects() const {
return current_room_ ? current_room_->GetTileObjects() : empty_objects_;
}
private:
// Internal helper methods
absl::Status InitializeEditor();
absl::Status CreateUndoPoint();
absl::Status ApplyUndoPoint(const UndoPoint& undo_point);
// Coordinate conversion
std::pair<int, int> ScreenToRoomCoordinates(int screen_x, int screen_y);
std::pair<int, int> RoomToScreenCoordinates(int room_x, int room_y);
int SnapToGrid(int coordinate);
// Object finding and collision detection
std::optional<size_t> FindObjectAt(int room_x, int room_y);
std::vector<size_t> FindObjectsInArea(int start_x, int start_y, int end_x,
int end_y);
bool IsObjectAtPosition(const RoomObject& object, int x, int y);
bool ObjectsCollide(const RoomObject& obj1, const RoomObject& obj2);
// Preview and rendering helpers
absl::StatusOr<gfx::Bitmap> RenderObjectPreview(int object_type, int x, int y,
int size);
void UpdatePreviewObject();
absl::Status ValidatePreviewPosition(int x, int y);
// Size editing with scroll wheel
absl::Status HandleSizeEdit(int delta, int x, int y);
int GetNextSize(int current_size, int delta);
int GetPreviousSize(int current_size, int delta);
bool IsValidSize(int size);
// Member variables
Rom* rom_;
std::unique_ptr<Room> current_room_;
SelectionState selection_state_;
EditingState editing_state_;
EditorConfig config_;
std::vector<UndoPoint> undo_history_;
std::vector<UndoPoint> redo_history_;
static constexpr size_t kMaxUndoHistory = 50;
// Preview system
std::optional<RoomObject> preview_object_;
bool preview_visible_ = false;
// Event callbacks
ObjectChangedCallback object_changed_callback_;
RoomChangedCallback room_changed_callback_;
SelectionChangedCallback selection_changed_callback_;
// Constants
static constexpr int kMinObjectSize = 0x00;
static constexpr int kMaxObjectSize = 0xFF;
static constexpr int kDefaultObjectSize = 0x12;
static constexpr int kMinLayer = 0;
static constexpr int kMaxLayer = 2;
// Empty objects vector for const getter
std::vector<RoomObject> empty_objects_;
};
/**
* @brief Factory function to create dungeon object editor
*/
std::unique_ptr<DungeonObjectEditor> CreateDungeonObjectEditor(Rom* rom);
/**
* @brief Object type categories for easier selection
*/
namespace ObjectCategories {
struct ObjectCategory {
std::string name;
std::vector<int> object_ids;
std::string description;
};
/**
* @brief Get all available object categories
*/
std::vector<ObjectCategory> GetObjectCategories();
/**
* @brief Get objects in a specific category
*/
absl::StatusOr<std::vector<int>> GetObjectsInCategory(
const std::string& category_name);
/**
* @brief Get category for a specific object
*/
absl::StatusOr<std::string> GetObjectCategory(int object_id);
/**
* @brief Get object information
*/
struct ObjectInfo {
int id;
std::string name;
std::string description;
std::vector<std::pair<int, int>> valid_sizes;
std::vector<int> valid_layers;
bool is_interactive;
bool is_collidable;
};
absl::StatusOr<ObjectInfo> GetObjectInfo(int object_id);
} // namespace ObjectCategories
/**
* @brief Scroll wheel behavior configuration
*/
struct ScrollWheelConfig {
bool enabled = true;
int sensitivity = 1; // How much size changes per scroll
int min_size = 0x00;
int max_size = 0xFF;
bool wrap_around = false; // Wrap from max to min
bool smooth_scrolling = true;
int smooth_factor = 2; // Divide delta by this for smoother scrolling
};
/**
* @brief Mouse interaction configuration
*/
struct MouseConfig {
bool left_click_select = true;
bool right_click_context = true;
bool middle_click_drag = false;
bool drag_to_select = true;
bool snap_drag_to_grid = true;
int double_click_threshold = 500; // milliseconds
int drag_threshold = 5; // pixels before drag starts
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_DUNGEON_OBJECT_EDITOR_H

View File

@@ -0,0 +1,107 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#define YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H
#include <cstdint>
#include <array> // Added for std::array
namespace yaze {
namespace zelda3 {
// ============================================================================
// Dungeon ROM Address Constants
// ============================================================================
// This file contains all ROM addresses for dungeon-related data in ALTTP.
// Organized by category for readability and maintainability.
// === Room Data Pointers ===
constexpr int kRoomObjectLayoutPointer = 0x882D; // Layout pointer table
constexpr int kRoomObjectPointer = 0x874C; // Object data pointer (Long)
constexpr int kRoomHeaderPointer = 0xB5DD; // Room header pointer (LONG)
constexpr int kRoomHeaderPointerBank = 0xB5E7; // Room header bank byte
// === Palette Data ===
constexpr int kDungeonsMainBgPalettePointers = 0xDEC4B; // JP Same
constexpr int kDungeonsPalettes = 0xDD734; // Dungeon palette data
// === Item & Sprite Data ===
constexpr int kRoomItemsPointers = 0xDB69; // JP 0xDB67
constexpr int kRoomsSpritePointer = 0x4C298; // JP Same (2-byte bank 09D62E)
constexpr int kSpriteBlocksetPointer = 0x5B57; // Sprite graphics pointer
// === Graphics Data ===
constexpr int kGfxGroupsPointer = 0x6237; // Graphics group table
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
// === Chests ===
constexpr int kChestsLengthPointer = 0xEBF6; // Chest count pointer
constexpr int kChestsDataPointer1 = 0xEBFB; // Chest data start
// === Torches ===
constexpr int kTorchData = 0x2736A; // JP 0x2704A
constexpr int kTorchesLengthPointer = 0x88C1; // Torch count pointer
// === Pits & Warps ===
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 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)
// === 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
// === Messages ===
constexpr int kMessagesIdDungeon = 0x3F61D; // Dungeon message IDs
// === Room Metadata ===
constexpr int kNumberOfRooms = 296; // Total dungeon rooms (0x00-0x127)
// Stair objects (special handling)
constexpr uint16_t kStairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
// === Layout Pointers (referenced in comments) ===
// Layout00 ptr: 0x47EF04
// Layout01 ptr: 0xAFEF04
// Layout02 ptr: 0xF0EF04
// Layout03 ptr: 0x4CF004
// Layout04 ptr: 0xA8F004
// Layout05 ptr: 0xECF004
// Layout06 ptr: 0x48F104
// Layout07 ptr: 0xA4F104
// === Notes ===
// - Layout arrays are NOT exactly the same as rooms
// - Object array is terminated by 0xFFFF (no layers)
// - In normal room, 0xFFFF goes to next layer (layers 0, 1, 2)
// Static pointers for the 8 predefined room layouts
static const std::array<int, 8> kRoomLayoutPointers = {
0x47EF04, 0xAFEF04, 0xF0EF04, 0x4CF004,
0xA8F004, 0xECF004, 0x48F104, 0xA4F104,
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_ROM_ADDRESSES_H

View File

@@ -0,0 +1,833 @@
#include "object_drawer.h"
#include <cstdio>
#include "absl/strings/str_format.h"
#include "app/gfx/types/snes_tile.h"
#include "util/log.h"
namespace yaze {
namespace zelda3 {
ObjectDrawer::ObjectDrawer(Rom* rom, const uint8_t* room_gfx_buffer)
: rom_(rom), room_gfx_buffer_(room_gfx_buffer) {
InitializeDrawRoutines();
}
absl::Status ObjectDrawer::DrawObject(const RoomObject& object,
gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2,
const gfx::PaletteGroup& palette_group) {
LOG_DEBUG("ObjectDrawer", "Drawing object 0x%02X at (%d,%d) size=%d tiles=%zu",
object.id_, object.x_, object.y_, object.size_, object.tiles().size());
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (!routines_initialized_) {
return absl::FailedPreconditionError("Draw routines not initialized");
}
// Ensure object has tiles loaded
auto mutable_obj = const_cast<RoomObject&>(object);
mutable_obj.set_rom(rom_);
mutable_obj.EnsureTilesLoaded();
// Select buffer based on layer
auto& target_bg = (object.layer_ == RoomObject::LayerType::BG2) ? bg2 : bg1;
// Skip objects that don't have tiles loaded
if (mutable_obj.tiles().empty()) {
return absl::OkStatus();
}
// Look up draw routine for this object
int routine_id = GetDrawRoutineId(object.id_);
LOG_DEBUG("ObjectDrawer", "Object %04X -> routine_id=%d", object.id_, routine_id);
if (routine_id < 0 || routine_id >= static_cast<int>(draw_routines_.size())) {
LOG_DEBUG("ObjectDrawer", "Using fallback 1x1 drawing for object %04X", object.id_);
// Fallback to simple 1x1 drawing using first 8x8 tile
if (!mutable_obj.tiles().empty()) {
const auto& tile_info = mutable_obj.tiles()[0];
WriteTile8(target_bg, object.x_, object.y_, tile_info);
}
return absl::OkStatus();
}
LOG_DEBUG("ObjectDrawer", "Executing draw routine %d for object %04X", routine_id, object.id_);
// Execute the appropriate draw routine
draw_routines_[routine_id](this, object, target_bg, mutable_obj.tiles());
return absl::OkStatus();
}
absl::Status ObjectDrawer::DrawObjectList(
const std::vector<RoomObject>& objects,
gfx::BackgroundBuffer& bg1,
gfx::BackgroundBuffer& bg2,
const gfx::PaletteGroup& palette_group) {
for (const auto& object : objects) {
DrawObject(object, bg1, bg2, palette_group);
}
// CRITICAL: Sync bitmap data to SDL surfaces after all objects are drawn
// ObjectDrawer writes directly to bitmap.mutable_data(), but textures are created from SDL surfaces
auto& bg1_bmp = bg1.bitmap();
auto& bg2_bmp = bg2.bitmap();
if (bg1_bmp.modified() && bg1_bmp.surface() && bg1_bmp.mutable_data().size() > 0) {
SDL_LockSurface(bg1_bmp.surface());
memcpy(bg1_bmp.surface()->pixels, bg1_bmp.mutable_data().data(), bg1_bmp.mutable_data().size());
SDL_UnlockSurface(bg1_bmp.surface());
printf("[ObjectDrawer] Synced BG1 bitmap data to SDL surface (%zu bytes)\n", bg1_bmp.mutable_data().size());
}
if (bg2_bmp.modified() && bg2_bmp.surface() && bg2_bmp.mutable_data().size() > 0) {
SDL_LockSurface(bg2_bmp.surface());
memcpy(bg2_bmp.surface()->pixels, bg2_bmp.mutable_data().data(), bg2_bmp.mutable_data().size());
SDL_UnlockSurface(bg2_bmp.surface());
printf("[ObjectDrawer] Synced BG2 bitmap data to SDL surface (%zu bytes)\n", bg2_bmp.mutable_data().size());
}
return absl::OkStatus();
}
// ============================================================================
// Draw Routine Registry Initialization
// ============================================================================
void ObjectDrawer::InitializeDrawRoutines() {
// This function maps object IDs to their corresponding draw routines.
// The mapping is based on ZScream's DungeonObjectData.cs and the game's assembly code.
// The order of functions in draw_routines_ MUST match the indices used here.
object_to_routine_map_.clear();
draw_routines_.clear();
// Subtype 1 Object Mappings (Horizontal)
object_to_routine_map_[0x00] = 0;
for (int id = 0x01; id <= 0x02; id++) { object_to_routine_map_[id] = 1; }
for (int id = 0x03; id <= 0x04; id++) { object_to_routine_map_[id] = 2; }
for (int id = 0x05; id <= 0x06; id++) { object_to_routine_map_[id] = 3; }
for (int id = 0x07; id <= 0x08; id++) { object_to_routine_map_[id] = 4; }
object_to_routine_map_[0x09] = 5;
for (int id = 0x0A; id <= 0x0B; id++) { object_to_routine_map_[id] = 6; }
// Subtype 1 Object Mappings (Vertical)
object_to_routine_map_[0x60] = 7;
for (int id = 0x61; id <= 0x62; id++) { object_to_routine_map_[id] = 8; }
for (int id = 0x63; id <= 0x64; id++) { object_to_routine_map_[id] = 9; }
for (int id = 0x65; id <= 0x66; id++) { object_to_routine_map_[id] = 10; }
for (int id = 0x67; id <= 0x68; id++) { object_to_routine_map_[id] = 11; }
object_to_routine_map_[0x69] = 12;
for (int id = 0x6A; id <= 0x6B; id++) { object_to_routine_map_[id] = 13; }
object_to_routine_map_[0x6C] = 14;
object_to_routine_map_[0x6D] = 15;
// Subtype 2 Object Mappings
for (int id = 0x100; id <= 0x10F; id++) { object_to_routine_map_[id] = 16; }
// Additional object mappings from logs
object_to_routine_map_[0x33] = 16;
object_to_routine_map_[0xC6] = 7; // Vertical draw
// Initialize draw routine function array in the correct order
draw_routines_.reserve(35);
// Routine 0
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawRightwards2x2_1to15or32(obj, bg, tiles);
});
// Routine 1
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawRightwards2x4_1to15or26(obj, bg, tiles);
});
// Routine 2
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawRightwards2x4spaced4_1to16(obj, bg, tiles);
});
// Routine 3
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawRightwards2x4spaced4_1to16_BothBG(obj, bg, tiles);
});
// Routine 4
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawRightwards2x2_1to16(obj, bg, tiles);
});
// Routine 5
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDiagonalAcute_1to16(obj, bg, tiles);
});
// Routine 6
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDiagonalGrave_1to16(obj, bg, tiles);
});
// Routine 7
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwards2x2_1to15or32(obj, bg, tiles);
});
// Routine 8
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwards4x2_1to15or26(obj, bg, tiles);
});
// Routine 9
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwards4x2_1to16_BothBG(obj, bg, tiles);
});
// Routine 10
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwardsDecor4x2spaced4_1to16(obj, bg, tiles);
});
// Routine 11
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwards2x2_1to16(obj, bg, tiles);
});
// Routine 12
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwardsHasEdge1x1_1to16_plus3(obj, bg, tiles);
});
// Routine 13
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwardsEdge1x1_1to16(obj, bg, tiles);
});
// Routine 14
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwardsLeftCorners2x1_1to16_plus12(obj, bg, tiles);
});
// Routine 15
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawDownwardsRightCorners2x1_1to16_plus12(obj, bg, tiles);
});
// Routine 16
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, std::span<const gfx::TileInfo> tiles) {
self->DrawRightwards4x4_1to16(obj, bg, tiles);
});
routines_initialized_ = true;
}
int ObjectDrawer::GetDrawRoutineId(int16_t object_id) const {
auto it = object_to_routine_map_.find(object_id);
if (it != object_to_routine_map_.end()) {
return it->second;
}
// Default to simple 1x1 solid for unmapped objects
return -1;
}
// ============================================================================
// Draw Routine Implementations (Based on ZScream patterns)
// ============================================================================
void ObjectDrawer::DrawRightwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 2x2 tiles rightward (object 0x00)
// Size byte determines how many times to repeat (1-15 or 32)
int size = obj.size_;
if (size == 0) size = 32; // Special case for object 0x00
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// Draw 2x2 pattern using 8x8 tiles from the span
WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right
WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Bottom-left
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Bottom-right
}
}
}
void ObjectDrawer::DrawRightwards2x4_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 2x4 tiles rightward (objects 0x01-0x02)
int size = obj.size_;
if (size == 0) size = 26; // Special case
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// For 2x4, we'll use the same tile pattern repeated
WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right
WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Mid-left
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Mid-right
WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 2, tiles[0]); // Bottom-left (repeat)
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 2, tiles[1]); // Bottom-right (repeat)
WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 3, tiles[2]); // Bottom-left (repeat)
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 3, tiles[3]); // Bottom-right (repeat)
}
}
}
void ObjectDrawer::DrawRightwards2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 2x4 tiles rightward with spacing (objects 0x03-0x04)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// Draw 2x4 pattern with spacing using 8x8 tiles from span
WriteTile8(bg, obj.x_ + (s * 6), obj.y_, tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_, tiles[1]); // Top-right
WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 1, tiles[2]); // Mid-left
WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 1, tiles[3]); // Mid-right
WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 2, tiles[0]); // Bottom-left (repeat)
WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 2, tiles[1]); // Bottom-right (repeat)
WriteTile8(bg, obj.x_ + (s * 6), obj.y_ + 3, tiles[2]); // Bottom-left (repeat)
WriteTile8(bg, obj.x_ + (s * 6) + 1, obj.y_ + 3, tiles[3]); // Bottom-right (repeat)
}
}
}
void ObjectDrawer::DrawRightwards2x4spaced4_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Same as above but draws to both BG1 and BG2 (objects 0x05-0x06)
DrawRightwards2x4spaced4_1to16(obj, bg, tiles);
// Note: BothBG would require access to both buffers - simplified for now
}
void ObjectDrawer::DrawRightwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 2x2 tiles rightward (objects 0x07-0x08)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// Draw 2x2 pattern using 8x8 tiles from span
WriteTile8(bg, obj.x_ + (s * 2), obj.y_, tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_, tiles[1]); // Top-right
WriteTile8(bg, obj.x_ + (s * 2), obj.y_ + 1, tiles[2]); // Bottom-left
WriteTile8(bg, obj.x_ + (s * 2) + 1, obj.y_ + 1, tiles[3]); // Bottom-right
}
}
}
void ObjectDrawer::DrawDiagonalAcute_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Diagonal line going down-right (/) (object 0x09)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size + 6; s++) {
if (tiles.size() >= 4) {
// Use first tile span for diagonal pattern
for (int i = 0; i < 5; i++) {
// Cycle through the 4 tiles in the span
const gfx::TileInfo& tile_info = tiles[i % 4];
WriteTile8(bg, obj.x_ + s, obj.y_ + (i - s), tile_info);
}
}
}
}
void ObjectDrawer::DrawDiagonalGrave_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Diagonal line going down-left (\) (objects 0x0A-0x0B)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size + 6; s++) {
if (tiles.size() >= 4) {
// Use first tile span for diagonal pattern
for (int i = 0; i < 5; i++) {
// Cycle through the 4 tiles in the span
const gfx::TileInfo& tile_info = tiles[i % 4];
WriteTile8(bg, obj.x_ + s, obj.y_ + (i + s), tile_info);
}
}
}
}
void ObjectDrawer::DrawDiagonalAcute_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Diagonal acute for both BG layers (objects 0x15-0x1F)
DrawDiagonalAcute_1to16(obj, bg, tiles);
}
void ObjectDrawer::DrawDiagonalGrave_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Diagonal grave for both BG layers (objects 0x16-0x20)
DrawDiagonalGrave_1to16(obj, bg, tiles);
}
void ObjectDrawer::DrawRightwards1x2_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 1x2 tiles rightward with +2 offset (object 0x21)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 2) {
// Use first tile span for 1x2 pattern
WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]);
WriteTile8(bg, obj.x_ + s + 2, obj.y_ + 1, tiles[1]);
}
}
}
void ObjectDrawer::DrawRightwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 1x1 tiles with edge detection +3 offset (object 0x22)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]);
}
}
}
void ObjectDrawer::DrawRightwardsHasEdge1x1_1to16_plus2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 1x1 tiles with edge detection +2 offset (objects 0x23-0x2E)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_ + s + 2, obj.y_, tiles[0]);
}
}
}
void ObjectDrawer::DrawRightwardsTopCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Top corner 1x2 tiles with +13 offset (object 0x2F)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 2) {
// Use first tile span for 1x2 pattern
WriteTile8(bg, obj.x_ + s + 13, obj.y_, tiles[0]);
WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[1]);
}
}
}
void ObjectDrawer::DrawRightwardsBottomCorners1x2_1to16_plus13(const RoomObject& obj, gfx::BackgroundBuffer& bg,
const std::span<const gfx::TileInfo> tiles) {
// Pattern: Bottom corner 1x2 tiles with +13 offset (object 0x30)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 2) {
// Use first tile span for 1x2 pattern
WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 1, tiles[0]);
WriteTile8(bg, obj.x_ + s + 13, obj.y_ + 2, tiles[1]);
}
}
}
void ObjectDrawer::CustomDraw(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Custom draw routine (objects 0x31-0x32)
// For now, fall back to simple 1x1
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_, obj.y_, tiles[0]);
}
}
void ObjectDrawer::DrawRightwards4x4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 4x4 block rightward (object 0x33)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 16) {
// Draw 4x4 pattern using 8x8 tiles from span
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[y * 4 + x]);
}
}
}
}
}
void ObjectDrawer::DrawRightwards1x1Solid_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 1x1 solid tiles +3 offset (object 0x34)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_ + s + 3, obj.y_, tiles[0]);
}
}
}
void ObjectDrawer::DrawDoorSwitcherer(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Door switcher (object 0x35)
// Special door logic - simplified for now
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_, obj.y_, tiles[0]);
}
}
void ObjectDrawer::DrawRightwardsDecor4x4spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 4x4 decoration with spacing (objects 0x36-0x37)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 16) {
// Draw 4x4 pattern with spacing using 8x8 tiles from span
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 4; ++x) {
WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]);
}
}
}
}
}
void ObjectDrawer::DrawRightwardsStatue2x3spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 2x3 statue with spacing (object 0x38)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 6) {
// Draw 2x3 pattern using 8x8 tiles from span
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 2; ++x) {
WriteTile8(bg, obj.x_ + (s * 4) + x, obj.y_ + y, tiles[y * 2 + x]);
}
}
}
}
}
void ObjectDrawer::DrawRightwardsPillar2x4spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 2x4 pillar with spacing (objects 0x39, 0x3D)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 8) {
// Draw 2x4 pattern using 8x8 tiles from span
for (int y = 0; y < 4; ++y) {
for (int x = 0; x < 2; ++x) {
WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 2 + x]);
}
}
}
}
}
void ObjectDrawer::DrawRightwardsDecor4x3spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 4x3 decoration with spacing (objects 0x3A-0x3B)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 12) {
// Draw 4x3 pattern using 8x8 tiles from span
for (int y = 0; y < 3; ++y) {
for (int x = 0; x < 4; ++x) {
WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]);
}
}
}
}
}
void ObjectDrawer::DrawRightwardsDoubled2x2spaced2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Doubled 2x2 with spacing (object 0x3C)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 8) {
// Draw doubled 2x2 pattern using 8x8 tiles from span
for (int y = 0; y < 2; ++y) {
for (int x = 0; x < 4; ++x) { // Draw a 4x2 area
WriteTile8(bg, obj.x_ + (s * 6) + x, obj.y_ + y, tiles[y * 4 + x]);
}
}
}
}
}
void ObjectDrawer::DrawRightwardsDecor2x2spaced12_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 2x2 decoration with large spacing (object 0x3E)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// Draw 2x2 decoration with 12-tile spacing using 8x8 tiles from span
WriteTile8(bg, obj.x_ + (s * 14), obj.y_, tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_, tiles[1]); // Top-right
WriteTile8(bg, obj.x_ + (s * 14), obj.y_ + 1, tiles[2]); // Bottom-left
WriteTile8(bg, obj.x_ + (s * 14) + 1, obj.y_ + 1, tiles[3]); // Bottom-right
}
}
}
// ============================================================================
// Downwards Draw Routines (Missing Implementation)
// ============================================================================
void ObjectDrawer::DrawDownwards2x2_1to15or32(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 2x2 tiles downward (object 0x60)
// Size byte determines how many times to repeat (1-15 or 32)
int size = obj.size_;
if (size == 0) size = 32; // Special case for object 0x60
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// Draw 2x2 pattern using 8x8 tiles from the span
WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); // Top-right
WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]); // Bottom-left
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]); // Bottom-right
}
}
}
void ObjectDrawer::DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 4x2 tiles downward (objects 0x61-0x62)
int size = obj.size_;
if (size == 0) size = 26; // Special case
LOG_DEBUG("ObjectDrawer", "DrawDownwards4x2_1to15or26: obj=%04X tiles=%zu size=%d",
obj.id_, tiles.size(), size);
for (int s = 0; s < size; s++) {
if (tiles.size() >= 8) {
// Draw 4x2 pattern using 8 tiles from span
// Top row: tiles 0,1,2,3
WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]); // Top-left
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]); // Top-right
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[2]); // Top-middle-left
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[3]); // Top-middle-right
// Bottom row: tiles 4,5,6,7
WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[4]); // Bottom-left
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[5]); // Bottom-right
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[6]); // Bottom-middle-left
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[7]); // Bottom-middle-right
} else if (tiles.size() >= 4) {
// Fallback: use only first 4 tiles and repeat
WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]);
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]);
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tiles[0]);
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tiles[1]);
WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]);
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]);
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tiles[2]);
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tiles[3]);
}
}
}
void ObjectDrawer::DrawDownwards4x2_1to16_BothBG(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Same as above but draws to both BG1 and BG2 (objects 0x63-0x64)
DrawDownwards4x2_1to15or26(obj, bg, tiles);
// Note: BothBG would require access to both buffers - simplified for now
}
void ObjectDrawer::DrawDownwardsDecor4x2spaced4_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 4x2 decoration downward with spacing (objects 0x65-0x66)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 8) {
// Draw 4x2 pattern with spacing using 8x8 tiles from span
WriteTile8(bg, obj.x_, obj.y_ + (s * 6), tiles[0]);
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6), tiles[1]);
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6), tiles[2]);
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6), tiles[3]);
WriteTile8(bg, obj.x_, obj.y_ + (s * 6) + 1, tiles[4]);
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 6) + 1, tiles[5]);
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 6) + 1, tiles[6]);
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 6) + 1, tiles[7]);
}
}
}
void ObjectDrawer::DrawDownwards2x2_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Draws 2x2 tiles downward (objects 0x67-0x68)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 4) {
// Draw 2x2 pattern using 8x8 tiles from span
WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tiles[0]);
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tiles[1]);
WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tiles[2]);
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tiles[3]);
}
}
}
void ObjectDrawer::DrawDownwardsHasEdge1x1_1to16_plus3(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 1x1 tiles with edge detection +3 offset downward (object 0x69)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_ + 3, obj.y_ + s, tiles[0]);
}
}
}
void ObjectDrawer::DrawDownwardsEdge1x1_1to16(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: 1x1 edge tiles downward (objects 0x6A-0x6B)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 1) {
// Use first 8x8 tile from span
WriteTile8(bg, obj.x_, obj.y_ + s, tiles[0]);
}
}
}
void ObjectDrawer::DrawDownwardsLeftCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Left corner 2x1 tiles with +12 offset downward (object 0x6C)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 2) {
// Use first tile span for 2x1 pattern
WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]);
WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]);
}
}
}
void ObjectDrawer::DrawDownwardsRightCorners2x1_1to16_plus12(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles) {
// Pattern: Right corner 2x1 tiles with +12 offset downward (object 0x6D)
int size = obj.size_ & 0x0F;
for (int s = 0; s < size; s++) {
if (tiles.size() >= 2) {
// Use first tile span for 2x1 pattern
WriteTile8(bg, obj.x_ + 12, obj.y_ + s, tiles[0]);
WriteTile8(bg, obj.x_ + 12 + 1, obj.y_ + s, tiles[1]);
}
}
}
// ============================================================================
// Utility Methods
// ============================================================================
void ObjectDrawer::WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
const gfx::TileInfo& tile_info) {
// Draw directly to bitmap instead of tile buffer to avoid being overwritten
auto& bitmap = bg.bitmap();
if (!bitmap.is_active() || bitmap.width() == 0) {
return; // Bitmap not ready
}
// The room-specific graphics buffer (current_gfx16_) contains the assembled
// tile graphics for the current room. Object tile IDs are relative to this buffer.
const uint8_t* gfx_data = room_gfx_buffer_;
if (!gfx_data) {
LOG_DEBUG("ObjectDrawer", "ERROR: No graphics data available");
return;
}
// Draw single 8x8 tile directly to bitmap
DrawTileToBitmap(bitmap, tile_info, tile_x * 8, tile_y * 8, gfx_data);
}
bool ObjectDrawer::IsValidTilePosition(int tile_x, int tile_y) const {
return tile_x >= 0 && tile_x < kMaxTilesX &&
tile_y >= 0 && tile_y < kMaxTilesY;
}
void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& tile_info,
int pixel_x, int pixel_y, const uint8_t* tiledata) {
// Draw an 8x8 tile directly to bitmap at pixel coordinates
if (!tiledata) return;
// DEBUG: Check if bitmap is valid
if (!bitmap.is_active() || bitmap.width() == 0 || bitmap.height() == 0) {
LOG_DEBUG("ObjectDrawer", "ERROR: Invalid bitmap - active=%d, size=%dx%d",
bitmap.is_active(), bitmap.width(), bitmap.height());
return;
}
// Calculate tile position in graphics sheet (128 pixels wide, 16 tiles per row)
int tile_sheet_x = (tile_info.id_ % 16) * 8; // 16 tiles per row
int tile_sheet_y = (tile_info.id_ / 16) * 8; // Each row is 16 tiles
// Palettes are 3bpp (8 colors). Convert palette index to base color offset.
uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8;
// DEBUG: Log tile info for first few tiles
static int debug_tile_count = 0;
if (debug_tile_count < 5) {
printf("[ObjectDrawer] DrawTile: id=0x%03X pos=(%d,%d) sheet=(%d,%d) pal=%d mirror=(h:%d,v:%d)\n",
tile_info.id_, pixel_x, pixel_y, tile_sheet_x, tile_sheet_y,
tile_info.palette_, tile_info.horizontal_mirror_, tile_info.vertical_mirror_);
debug_tile_count++;
}
// Draw 8x8 pixels
int pixels_written = 0;
int pixels_transparent = 0;
for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) {
// Apply mirroring
int src_x = tile_info.horizontal_mirror_ ? (7 - px) : px;
int src_y = tile_info.vertical_mirror_ ? (7 - py) : py;
// Read pixel from graphics sheet
int src_index = (tile_sheet_y + src_y) * 128 + (tile_sheet_x + src_x);
uint8_t pixel_index = tiledata[src_index];
if (pixel_index == 0) {
pixels_transparent++;
continue;
}
uint8_t final_color = pixel_index + palette_offset;
int dest_x = pixel_x + px;
int dest_y = pixel_y + py;
if (dest_x >= 0 && dest_x < bitmap.width() && dest_y >= 0 && dest_y < bitmap.height()) {
int dest_index = dest_y * bitmap.width() + dest_x;
if (dest_index >= 0 && dest_index < static_cast<int>(bitmap.mutable_data().size())) {
bitmap.mutable_data()[dest_index] = final_color;
pixels_written++;
}
}
}
}
// Mark bitmap as modified if we wrote any pixels
if (pixels_written > 0) {
bitmap.set_modified(true);
}
// DEBUG: Log pixel writing stats for first few tiles
if (debug_tile_count < 5) {
printf("[ObjectDrawer] Tile 0x%03X: wrote %d pixels, %d transparent, src_index_range=[%d,%d]\n",
tile_info.id_, pixels_written, pixels_transparent,
(tile_sheet_y * 128 + tile_sheet_x),
(tile_sheet_y + 7) * 128 + (tile_sheet_x + 7));
}
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,183 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
#define YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
#include <vector>
#include <unordered_map>
#include <functional>
#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/rom.h"
#include "zelda3/dungeon/room_object.h"
namespace yaze {
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
* 3. Execute appropriate draw routine with size/orientation
* 4. Write tiles to BackgroundBuffer according to pattern
* 5. Handle palette coordination and graphics sheet access
*/
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
* @param bg1 Background layer 1 buffer
* @param bg2 Background layer 2 buffer
* @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);
/**
* @brief Draw all objects in a room
* @param objects Vector of room objects
* @param bg1 Background layer 1 buffer
* @param bg2 Background layer 2 buffer
* @param palette_group Current palette group for color mapping
* @return Status of the drawing operation
*/
absl::Status DrawObjectList(const std::vector<RoomObject>& objects,
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
*/
void InitializeDrawRoutines();
/**
* @brief Draw a single tile directly to bitmap
* @param bitmap Target bitmap to draw to
* @param tile_info Tile information (ID, palette, mirroring)
* @param pixel_x X pixel coordinate in bitmap
* @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);
private:
// Draw routine function type
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,
std::span<const gfx::TileInfo> tiles);
void DrawRightwards2x4_1to15or26(const RoomObject& obj, gfx::BackgroundBuffer& bg,
std::span<const gfx::TileInfo> tiles);
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 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,
std::span<const gfx::TileInfo> tiles);
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,
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,
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);
// 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_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);
// 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_)
// Canvas dimensions in tiles (64x64 = 512x512 pixels)
static constexpr int kMaxTilesX = 64;
static constexpr int kMaxTilesY = 64;
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H

View File

@@ -0,0 +1,394 @@
#include "object_parser.h"
#include <algorithm>
#include <cstring>
#include "absl/strings/str_format.h"
#include "zelda3/dungeon/room_object.h"
#include "util/log.h"
// ROM addresses for object data (PC addresses, not SNES)
static constexpr int kRoomObjectSubtype1 = 0x0A8000;
static constexpr int kRoomObjectSubtype2 = 0x0A9000;
static constexpr int kRoomObjectSubtype3 = 0x0AA000;
static constexpr int kRoomObjectTileAddress = 0x0AB000;
namespace yaze {
namespace zelda3 {
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);
case 2:
return ParseSubtype2(object_id);
case 3:
return ParseSubtype3(object_id);
default:
return absl::InvalidArgumentError(
absl::StrFormat("Invalid object subtype for ID: %#04x", object_id));
}
}
absl::StatusOr<ObjectRoutineInfo> ObjectParser::ParseObjectRoutine(int16_t object_id) {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null");
}
auto subtype_info = GetObjectSubtype(object_id);
if (!subtype_info.ok()) {
return subtype_info.status();
}
ObjectRoutineInfo routine_info;
routine_info.routine_ptr = subtype_info->routine_ptr;
routine_info.tile_ptr = subtype_info->subtype_ptr;
routine_info.tile_count = subtype_info->max_tile_count;
routine_info.is_repeatable = true;
routine_info.is_orientation_dependent = true;
return routine_info;
}
absl::StatusOr<ObjectSubtypeInfo> ObjectParser::GetObjectSubtype(int16_t object_id) {
ObjectSubtypeInfo info;
info.subtype = DetermineSubtype(object_id);
switch (info.subtype) {
case 1: {
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
break;
}
case 2: {
int index = object_id & 0x7F;
info.subtype_ptr = kRoomObjectSubtype2 + (index * 2);
info.routine_ptr = kRoomObjectSubtype2 + 0x80 + (index * 2);
info.max_tile_count = 8;
break;
}
case 3: {
int index = object_id & 0xFF;
info.subtype_ptr = kRoomObjectSubtype3 + (index * 2);
info.routine_ptr = kRoomObjectSubtype3 + 0x100 + (index * 2);
info.max_tile_count = 8;
break;
}
default:
return absl::InvalidArgumentError(
absl::StrFormat("Invalid object subtype for ID: %#04x", object_id));
}
return info;
}
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) {
// Objects 0x80-0xFF are typically vertical
info.is_horizontal = false;
} else {
// 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) {
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) {
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) {
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) {
// 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]));
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);
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_);
}
}
if (should_log) {
printf("[ObjectParser] ReadTileData: addr=0x%06X count=%d → loaded %zu tiles\n",
address, tile_count, tiles.size());
debug_read_count++;
}
return tiles;
}
int ObjectParser::DetermineSubtype(int16_t object_id) const {
if (object_id >= 0x200) {
return 3;
} else if (object_id >= 0x100) {
return 2;
} else {
return 1;
}
}
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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
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
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
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
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
info.routine_name = "CustomDraw";
info.tile_count = 1;
}
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
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
info.routine_name = "DoorSwitcherer";
info.tile_count = 1;
}
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
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
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
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
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
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)
info.routine_name = "RightwardsHasEdge1x1_1to16_plus2_variant";
info.tile_count = 1;
info.is_horizontal = true;
}
else {
// Default to simple 1x1 solid for unmapped objects
info.draw_routine_id = 16; // Use solid block routine
info.routine_name = "DefaultSolid";
info.tile_count = 1;
info.is_horizontal = true;
}
return info;
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,172 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_OBJECT_PARSER_H
#define YAZE_APP_ZELDA3_DUNGEON_OBJECT_PARSER_H
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace zelda3 {
/**
* @brief Object routine information
*/
struct ObjectRoutineInfo {
uint32_t routine_ptr;
uint32_t tile_ptr;
int tile_count;
bool is_repeatable;
bool is_orientation_dependent;
ObjectRoutineInfo()
: routine_ptr(0),
tile_ptr(0),
tile_count(0),
is_repeatable(false),
is_orientation_dependent(false) {}
};
/**
* @brief Object subtype information
*/
struct ObjectSubtypeInfo {
int subtype;
uint32_t subtype_ptr;
uint32_t routine_ptr;
int max_tile_count;
ObjectSubtypeInfo()
: subtype(0), subtype_ptr(0), routine_ptr(0), max_tile_count(0) {}
};
/**
* @brief Object size and orientation information
*/
struct ObjectSizeInfo {
int width_tiles;
int height_tiles;
bool is_horizontal;
bool is_repeatable;
int repeat_count;
ObjectSizeInfo()
: width_tiles(0),
height_tiles(0),
is_horizontal(true),
is_repeatable(false),
repeat_count(1) {}
};
/**
* @brief Draw routine information for object rendering
*/
struct ObjectDrawInfo {
int draw_routine_id; // Which drawing pattern to use (0-24)
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
ObjectDrawInfo()
: draw_routine_id(0),
tile_count(1),
is_horizontal(true),
is_vertical(false),
both_layers(false),
routine_name("Unknown") {}
};
/**
* @brief Direct ROM parser for dungeon objects
*
* This class replaces the SNES emulation approach with direct ROM parsing,
* providing better performance and reliability for object rendering.
*/
class ObjectParser {
public:
explicit ObjectParser(Rom* rom) : rom_(rom) {}
/**
* @brief Parse object data directly from ROM
*
* @param object_id The object ID to parse
* @return StatusOr containing the parsed tile data
*/
absl::StatusOr<std::vector<gfx::TileInfo>> ParseObject(int16_t object_id);
/**
* @brief Parse object routine data
*
* @param object_id The object ID
* @return StatusOr containing routine information
*/
absl::StatusOr<ObjectRoutineInfo> ParseObjectRoutine(int16_t object_id);
/**
* @brief Get object subtype information
*
* @param object_id The object ID
* @return StatusOr containing subtype information
*/
absl::StatusOr<ObjectSubtypeInfo> GetObjectSubtype(int16_t object_id);
/**
* @brief Parse object size and orientation
*
* @param object_id The object ID
* @param size_byte The size byte from object data
* @return StatusOr containing size and orientation info
*/
absl::StatusOr<ObjectSizeInfo> ParseObjectSize(int16_t object_id,
uint8_t size_byte);
/**
* @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
* @return ObjectDrawInfo containing draw routine details
*/
ObjectDrawInfo GetObjectDrawInfo(int16_t object_id) const;
private:
/**
* @brief Parse subtype 1 objects (0x00-0xFF)
*/
absl::StatusOr<std::vector<gfx::TileInfo>> ParseSubtype1(int16_t object_id);
/**
* @brief Parse subtype 2 objects (0x100-0x1FF)
*/
absl::StatusOr<std::vector<gfx::TileInfo>> ParseSubtype2(int16_t object_id);
/**
* @brief Parse subtype 3 objects (0x200+)
*/
absl::StatusOr<std::vector<gfx::TileInfo>> ParseSubtype3(int16_t object_id);
/**
* @brief Read tile data from ROM
*
* @param address The address to read from
* @param tile_count Number of tiles to read
* @return StatusOr containing tile data
*/
absl::StatusOr<std::vector<gfx::TileInfo>> ReadTileData(int address,
int tile_count);
Rom* rom_;
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_OBJECT_PARSER_H

1181
src/zelda3/dungeon/room.cc Normal file

File diff suppressed because it is too large Load Diff

856
src/zelda3/dungeon/room.h Normal file
View File

@@ -0,0 +1,856 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_H
#define YAZE_APP_ZELDA3_DUNGEON_ROOM_H
#include <yaze.h>
#include <cstdint>
#include <string_view>
#include <vector>
#include "app/rom.h"
#include "app/gfx/render/background_buffer.h"
#include "zelda3/dungeon/dungeon_rom_addresses.h"
#include "zelda3/dungeon/room_object.h"
#include "zelda3/dungeon/room_layout.h"
#include "zelda3/sprite/sprite.h"
namespace yaze {
namespace zelda3 {
// ROM addresses moved to dungeon_rom_addresses.h for better organization
// Use kPrefixedNames for new code (clean naming convention)
// 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_palettes = kDungeonsPalettes;
constexpr int room_items_pointers = kRoomItemsPointers;
constexpr int rooms_sprite_pointer = kRoomsSpritePointer;
constexpr int gfx_groups_pointer = kGfxGroupsPointer;
constexpr int chests_length_pointer = kChestsLengthPointer;
constexpr int chests_data_pointer1 = kChestsDataPointer1;
constexpr int messages_id_dungeon = kMessagesIdDungeon;
constexpr int blocks_length = kBlocksLength;
constexpr int blocks_pointer1 = kBlocksPointer1;
constexpr int blocks_pointer2 = kBlocksPointer2;
constexpr int blocks_pointer3 = kBlocksPointer3;
constexpr int blocks_pointer4 = kBlocksPointer4;
constexpr int torch_data = kTorchData;
constexpr int torches_length_pointer = kTorchesLengthPointer;
constexpr int sprite_blockset_pointer = kSpriteBlocksetPointer;
constexpr int sprites_data = kSpritesData;
constexpr int sprites_data_empty_room = kSpritesDataEmptyRoom;
constexpr int sprites_end_data = kSpritesEndData;
constexpr int pit_pointer = kPitPointer;
constexpr int pit_count = kPitCount;
constexpr int doorPointers = kDoorPointers;
constexpr int door_gfx_up = kDoorGfxUp;
constexpr int door_gfx_down = kDoorGfxDown;
constexpr int door_gfx_cavexit_down = kDoorGfxCaveExitDown;
constexpr int door_gfx_left = kDoorGfxLeft;
constexpr int door_gfx_right = kDoorGfxRight;
constexpr int door_pos_up = kDoorPosUp;
constexpr int door_pos_down = kDoorPosDown;
constexpr int door_pos_left = kDoorPosLeft;
constexpr int door_pos_right = kDoorPosRight;
constexpr int dungeon_spr_ptrs = kDungeonSpritePointers;
constexpr int tile_address = kTileAddress;
constexpr int tile_address_floor = kTileAddressFloor;
constexpr int NumberOfRooms = kNumberOfRooms;
constexpr uint16_t stairsObjects[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
// TODO: Gradually migrate all code to use kPrefixedNames directly
// Then remove these legacy aliases
struct LayerMergeType {
uint8_t ID;
std::string Name;
bool Layer2OnTop;
bool Layer2Translucent;
bool Layer2Visible;
LayerMergeType() = default;
LayerMergeType(uint8_t id, std::string name, bool see, bool top, bool trans) {
ID = id;
Name = name;
Layer2OnTop = top;
Layer2Translucent = trans;
Layer2Visible = see;
}
};
const static LayerMergeType LayerMerge00{0x00, "Off", true, false, false};
const static LayerMergeType LayerMerge01{0x01, "Parallax", true, false, false};
const static LayerMergeType LayerMerge02{0x02, "Dark", true, true, true};
const static LayerMergeType LayerMerge03{0x03, "On top", true, true, false};
const static LayerMergeType LayerMerge04{0x04, "Translucent", true, true, true};
const static LayerMergeType LayerMerge05{0x05, "Addition", true, true, true};
const static LayerMergeType LayerMerge06{0x06, "Normal", true, false, false};
const static LayerMergeType LayerMerge07{0x07, "Transparent", true, true, true};
const static LayerMergeType LayerMerge08{0x08, "Dark room", true, true, true};
const static LayerMergeType kLayerMergeTypeList[] = {
LayerMerge00, LayerMerge01, LayerMerge02, LayerMerge03, LayerMerge04,
LayerMerge05, LayerMerge06, LayerMerge07, LayerMerge08};
enum CollisionKey {
One_Collision,
Both,
Both_With_Scroll,
Moving_Floor_Collision,
Moving_Water_Collision,
};
enum EffectKey {
Effect_Nothing,
One,
Moving_Floor,
Moving_Water,
Four,
Red_Flashes,
Torch_Show_Floor,
Ganon_Room,
};
enum TagKey {
Nothing,
NW_Kill_Enemy_to_Open,
NE_Kill_Enemy_to_Open,
SW_Kill_Enemy_to_Open,
SE_Kill_Enemy_to_Open,
W_Kill_Enemy_to_Open,
E_Kill_Enemy_to_Open,
N_Kill_Enemy_to_Open,
S_Kill_Enemy_to_Open,
Clear_Quadrant_to_Open,
Clear_Room_to_Open,
NW_Push_Block_to_Open,
NE_Push_Block_to_Open,
SW_Push_Block_to_Open,
SE_Push_Block_to_Open,
W_Push_Block_to_Open,
E_Push_Block_to_Open,
N_Push_Block_to_Open,
S_Push_Block_to_Open,
Push_Block_to_Open,
Pull_Lever_to_Open,
Clear_Level_to_Open,
Switch_Open_Door_Hold,
Switch_Open_Door_Toggle,
Turn_off_Water,
Turn_on_Water,
Water_Gate,
Water_Twin,
Secret_Wall_Right,
Secret_Wall_Left,
Crash1,
Crash2,
Pull_Switch_to_bomb_Wall,
Holes_0,
Open_Chest_Activate_Holes_0,
Holes_1,
Holes_2,
Kill_Enemy_to_clear_level,
SE_Kill_Enemy_to_Move_Block,
Trigger_activated_Chest,
Pull_lever_to_Bomb_Wall,
NW_Kill_Enemy_for_Chest,
NE_Kill_Enemy_for_Chest,
SW_Kill_Enemy_for_Chest,
SE_Kill_Enemy_for_Chest,
W_Kill_Enemy_for_Chest,
E_Kill_Enemy_for_Chest,
N_Kill_Enemy_for_Chest,
S_Kill_Enemy_for_Chest,
Clear_Quadrant_for_Chest,
Clear_Room_for_Chest,
Light_Torches_to_Open,
Holes_3,
Holes_4,
Holes_5,
Holes_6,
Agahnim_Room,
Holes_7,
Holes_8,
Open_Chest_for_Holes_8,
Push_Block_for_Chest,
Kill_to_open_Ganon_Door,
Light_Torches_to_get_Chest,
Kill_boss_Again
};
class Room {
public:
Room() = default;
Room(int room_id, Rom* rom) : room_id_(room_id), rom_(rom) {}
void LoadRoomGraphics(uint8_t entrance_blockset = 0xFF);
void CopyRoomGraphicsToBuffer();
// LoadGraphicsSheetsIntoArena() removed - per-room graphics instead
void RenderRoomGraphics();
void RenderObjectsToBackground();
void LoadAnimatedGraphics();
void LoadObjects();
void LoadSprites();
void LoadChests();
void LoadDoors();
void LoadTorches();
void LoadBlocks();
void LoadPits();
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_; }
// Public getters and manipulators for chests
const std::vector<chest_data>& GetChests() const { return chests_in_room_; }
std::vector<chest_data>& GetChests() { return chests_in_room_; }
// Public getters and manipulators for stairs
const std::vector<staircase>& GetStairs() const { return z3_staircases_; }
std::vector<staircase>& GetStairs() { return z3_staircases_; }
const RoomLayout& GetLayout() const { return layout_; }
// Public getters and manipulators for tile objects
const std::vector<RoomObject>& GetTileObjects() const {
return tile_objects_;
}
std::vector<RoomObject>& GetTileObjects() { return tile_objects_; }
// Methods for modifying tile objects
void ClearTileObjects() { tile_objects_.clear(); }
void AddTileObject(const RoomObject& object) {
tile_objects_.push_back(object);
MarkObjectsDirty();
}
// Enhanced object manipulation (Phase 3)
absl::Status AddObject(const RoomObject& object);
absl::Status RemoveObject(size_t index);
absl::Status UpdateObject(size_t index, const RoomObject& object);
absl::StatusOr<size_t> FindObjectAt(int x, int y, int layer) const;
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 RemoveTileObject(size_t index) {
if (index < tile_objects_.size()) {
tile_objects_.erase(tile_objects_.begin() + index);
MarkObjectsDirty();
}
}
size_t GetTileObjectCount() const { return tile_objects_.size(); }
RoomObject& GetTileObject(size_t index) { return tile_objects_[index]; }
const RoomObject& GetTileObject(size_t index) const {
return tile_objects_[index];
}
// For undo/redo functionality
void SetTileObjects(const std::vector<RoomObject>& objects) {
tile_objects_ = objects;
MarkObjectsDirty();
}
// Public setters for LoadRoomFromRom function
void SetBg2(background2 bg2) { bg2_ = bg2; }
void SetCollision(CollisionKey collision) { collision_ = collision; }
void SetIsLight(bool is_light) { is_light_ = is_light; }
void SetPalette(uint8_t palette) {
if (this->palette != palette) {
this->palette = palette;
MarkGraphicsDirty();
}
}
void SetBlockset(uint8_t blockset) {
if (this->blockset != blockset) {
this->blockset = blockset;
MarkGraphicsDirty();
}
}
void SetSpriteset(uint8_t spriteset) {
if (this->spriteset != spriteset) {
this->spriteset = spriteset;
MarkGraphicsDirty();
}
}
void SetEffect(EffectKey effect) {
if (effect_ != effect) {
effect_ = effect;
MarkObjectsDirty();
}
}
void SetTag1(TagKey tag1) {
if (tag1_ != tag1) {
tag1_ = tag1;
MarkObjectsDirty();
}
}
void SetTag2(TagKey tag2) {
if (tag2_ != tag2) {
tag2_ = tag2;
MarkObjectsDirty();
}
}
void SetStaircasePlane(int index, uint8_t 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;
}
// 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
bool IsLight() const { return is_light_; }
// Additional setters for LoadRoomFromRom function
void SetMessageIdDirect(uint16_t message_id) { message_id_ = message_id; }
void SetLayer2Mode(uint8_t mode) { layer2_mode_ = mode; }
void SetLayerMerging(LayerMergeType merging) { layer_merging_ = merging; }
void SetIsDark(bool is_dark) { is_dark_ = is_dark; }
void SetPaletteDirect(uint8_t palette) { palette_ = palette; }
void SetBackgroundTileset(uint8_t tileset) { background_tileset_ = tileset; }
void SetSpriteTileset(uint8_t tileset) { sprite_tileset_ = tileset; }
void SetLayer2Behavior(uint8_t behavior) { layer2_behavior_ = behavior; }
void SetTag1Direct(TagKey tag1) { tag1_ = tag1; }
void SetTag2Direct(TagKey tag2) { tag2_ = tag2; }
void SetPitsTargetLayer(uint8_t layer) { pits_.target_layer = layer; }
void SetStair1TargetLayer(uint8_t layer) { stair1_.target_layer = layer; }
void SetStair2TargetLayer(uint8_t layer) { stair2_.target_layer = layer; }
void SetStair3TargetLayer(uint8_t layer) { stair3_.target_layer = layer; }
void SetStair4TargetLayer(uint8_t layer) { stair4_.target_layer = layer; }
void SetPitsTarget(uint8_t target) { pits_.target = target; }
void SetStair1Target(uint8_t target) { stair1_.target = target; }
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; }
// Read-only accessors for metadata
EffectKey effect() const { return effect_; }
TagKey tag1() const { return tag1_; }
TagKey tag2() const { return tag2_; }
CollisionKey collision() const { return collision_; }
const LayerMergeType& layer_merging() const { return layer_merging_; }
int id() const { return room_id_; }
uint8_t blockset = 0;
uint8_t spriteset = 0;
uint8_t palette = 0;
uint8_t layout = 0;
uint8_t holewarp = 0;
// 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_; }
void set_floor1(uint8_t value) {
if (floor1_graphics_ != value) {
floor1_graphics_ = value;
MarkGraphicsDirty();
}
}
void set_floor2(uint8_t value) {
if (floor2_graphics_ != value) {
floor2_graphics_ = value;
MarkGraphicsDirty();
}
}
// Enhanced object parsing methods
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;
auto blocks() const { return blocks_; }
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_; }
// Per-room background buffers (not shared via arena!)
auto& bg1_buffer() { return bg1_buffer_; }
auto& bg2_buffer() { return bg2_buffer_; }
const auto& bg1_buffer() const { return bg1_buffer_; }
const auto& bg2_buffer() const { return bg2_buffer_; }
private:
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};
bool is_light_;
bool is_loaded_ = false;
bool is_dark_;
bool is_floor_ = true;
// 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;
uint8_t cached_layout_ = 0xFF;
uint8_t cached_floor1_graphics_ = 0xFF;
uint8_t cached_floor2_graphics_ = 0xFF;
uint8_t cached_effect_ = 0xFF;
TagKey cached_tag1_ = TagKey::Nothing;
TagKey cached_tag2_ = TagKey::Nothing;
// Dirty flags for selective rendering
bool graphics_dirty_ = true;
bool objects_dirty_ = true;
bool layout_dirty_ = true;
bool textures_dirty_ = true;
int room_id_;
int animated_frame_;
uint8_t staircase_plane_[4];
uint8_t staircase_rooms_[4];
uint8_t background_tileset_;
uint8_t sprite_tileset_;
uint8_t layer2_behavior_;
uint8_t palette_;
uint8_t floor1_graphics_;
uint8_t floor2_graphics_;
uint8_t layer2_mode_;
std::array<uint8_t, 16> blocks_;
std::array<chest, 16> chest_list_;
std::vector<RoomObject> tile_objects_;
// TODO: add separate door objects list when door section (F0 FF) is parsed
std::vector<zelda3::Sprite> sprites_;
std::vector<staircase> z3_staircases_;
std::vector<chest_data> chests_in_room_;
RoomLayout layout_;
LayerMergeType layer_merging_;
CollisionKey collision_;
EffectKey effect_;
TagKey tag1_;
TagKey tag2_;
background2 bg2_;
destination pits_;
destination stair1_;
destination stair2_;
destination stair3_;
destination stair4_;
};
// Loads a room from the ROM.
Room LoadRoomFromRom(Rom* rom, int room_id);
struct RoomSize {
int64_t room_size_pointer;
int64_t room_size;
};
// Calculates the size of a room in the ROM.
RoomSize CalculateRoomSize(Rom* rom, int room_id);
static const std::string RoomEffect[] = {"Nothing",
"Nothing",
"Moving Floor",
"Moving Water",
"Trinexx Shell",
"Red Flashes",
"Light Torch to See Floor",
"Ganon's Darkness"};
constexpr std::string_view kRoomNames[] = {
"Ganon",
"Hyrule Castle (North Corridor)",
"Behind Sanctuary (Switch)",
"Houlihan",
"Turtle Rock (Crysta-Roller)",
"Empty",
"Swamp Palace (Arrghus[Boss])",
"Tower of Hera (Moldorm[Boss])",
"Cave (Healing Fairy)",
"Palace of Darkness",
"Palace of Darkness (Stalfos Trap)",
"Palace of Darkness (Turtle)",
"Ganon's Tower (Entrance)",
"Ganon's Tower (Agahnim2[Boss])",
"Ice Palace (Entrance )",
"Empty Clone ",
"Ganon Evacuation Route",
"Hyrule Castle (Bombable Stock )",
"Sanctuary",
"Turtle Rock (Hokku-Bokku Key 2)",
"Turtle Rock (Big Key )",
"Turtle Rock",
"Swamp Palace (Swimming Treadmill)",
"Tower of Hera (Moldorm Fall )",
"Cave",
"Palace of Darkness (Dark Maze)",
"Palace of Darkness (Big Chest )",
"Palace of Darkness (Mimics / Moving Wall )",
"Ganon's Tower (Ice Armos)",
"Ganon's Tower (Final Hallway)",
"Ice Palace (Bomb Floor / Bari )",
"Ice Palace (Pengator / Big Key )",
"Agahnim's Tower (Agahnim[Boss])",
"Hyrule Castle (Key-rat )",
"Hyrule Castle (Sewer Text Trigger )",
"Turtle Rock (West Exit to Balcony)",
"Turtle Rock (Double Hokku-Bokku / Big chest )",
"Empty Clone ",
"Swamp Palace (Statue )",
"Tower of Hera (Big Chest)",
"Swamp Palace (Entrance )",
"Skull Woods (Mothula[Boss])",
"Palace of Darkness (Big Hub )",
"Palace of Darkness (Map Chest / Fairy )",
"Cave",
"Empty Clone ",
"Ice Palace (Compass )",
"Cave (Kakariko Well HP)",
"Agahnim's Tower (Maiden Sacrifice Chamber)",
"Tower of Hera (Hardhat Beetles )",
"Hyrule Castle (Sewer Key Chest )",
"Desert Palace (Lanmolas[Boss])",
"Swamp Palace (Push Block Puzzle / Pre-Big Key )",
"Swamp Palace (Big Key / BS )",
"Swamp Palace (Big Chest )",
"Swamp Palace (Map Chest / Water Fill )",
"Swamp Palace (Key Pot )",
"Skull Woods (Gibdo Key / Mothula Hole )",
"Palace of Darkness (Bombable Floor )",
"Palace of Darkness (Spike Block / Conveyor )",
"Cave",
"Ganon's Tower (Torch 2)",
"Ice Palace (Stalfos Knights / Conveyor Hellway)",
"Ice Palace (Map Chest )",
"Agahnim's Tower (Final Bridge )",
"Hyrule Castle (First Dark )",
"Hyrule Castle (6 Ropes )",
"Desert Palace (Torch Puzzle / Moving Wall )",
"Thieves Town (Big Chest )",
"Thieves Town (Jail Cells )",
"Swamp Palace (Compass Chest )",
"Empty Clone ",
"Empty Clone ",
"Skull Woods (Gibdo Torch Puzzle )",
"Palace of Darkness (Entrance )",
"Palace of Darkness (Warps / South Mimics )",
"Ganon's Tower (Mini-Helmasaur Conveyor )",
"Ganon's Tower (Moldorm )",
"Ice Palace (Bomb-Jump )",
"Ice Palace Clone (Fairy )",
"Hyrule Castle (West Corridor)",
"Hyrule Castle (Throne )",
"Hyrule Castle (East Corridor)",
"Desert Palace (Popos 2 / Beamos Hellway )",
"Swamp Palace (Upstairs Pits )",
"Castle Secret Entrance / Uncle Death ",
"Skull Woods (Key Pot / Trap )",
"Skull Woods (Big Key )",
"Skull Woods (Big Chest )",
"Skull Woods (Final Section Entrance )",
"Palace of Darkness (Helmasaur King[Boss])",
"Ganon's Tower (Spike Pit )",
"Ganon's Tower (Ganon-Ball Z)",
"Ganon's Tower (Gauntlet 1/2/3)",
"Ice Palace (Lonely Firebar)",
"Ice Palace (Hidden Chest / Spike Floor )",
"Hyrule Castle (West Entrance )",
"Hyrule Castle (Main Entrance )",
"Hyrule Castle (East Entrance )",
"Desert Palace (Final Section Entrance )",
"Thieves Town (West Attic )",
"Thieves Town (East Attic )",
"Swamp Palace (Hidden Chest / Hidden Door )",
"Skull Woods (Compass Chest )",
"Skull Woods (Key Chest / Trap )",
"Empty Clone ",
"Palace of Darkness (Rupee )",
"Ganon's Tower (Mimics s)",
"Ganon's Tower (Lanmolas )",
"Ganon's Tower (Gauntlet 4/5)",
"Ice Palace (Pengators )",
"Empty Clone ",
"Hyrule Castle (Small Corridor to Jail Cells)",
"Hyrule Castle (Boomerang Chest )",
"Hyrule Castle (Map Chest )",
"Desert Palace (Big Chest )",
"Desert Palace (Map Chest )",
"Desert Palace (Big Key Chest )",
"Swamp Palace (Water Drain )",
"Tower of Hera (Entrance )",
"Empty Clone ",
"Empty Clone ",
"Empty Clone ",
"Ganon's Tower",
"Ganon's Tower (East Side Collapsing Bridge / Exploding Wall )",
"Ganon's Tower (Winder / Warp Maze )",
"Ice Palace (Hidden Chest / Bombable Floor )",
"Ice Palace ( Big Spike Traps )",
"Hyrule Castle (Jail Cell )",
"Hyrule Castle",
"Hyrule Castle (Basement Chasm )",
"Desert Palace (West Entrance )",
"Desert Palace (Main Entrance )",
"Desert Palace (East Entrance )",
"Empty Clone ",
"Tower of Hera (Tile )",
"Empty Clone ",
"Eastern Palace (Fairy )",
"Empty Clone ",
"Ganon's Tower (Block Puzzle / Spike Skip / Map Chest )",
"Ganon's Tower (East and West Downstairs / Big Chest )",
"Ganon's Tower (Tile / Torch Puzzle )",
"Ice Palace",
"Empty Clone ",
"Misery Mire (Vitreous[Boss])",
"Misery Mire (Final Switch )",
"Misery Mire (Dark Bomb Wall / Switches )",
"Misery Mire (Dark Cane Floor Switch Puzzle )",
"Empty Clone ",
"Ganon's Tower (Final Collapsing Bridge )",
"Ganon's Tower (Torches 1 )",
"Misery Mire (Torch Puzzle / Moving Wall )",
"Misery Mire (Entrance )",
"Eastern Palace (Eyegore Key )",
"Empty Clone ",
"Ganon's Tower (Many Spikes / Warp Maze )",
"Ganon's Tower (Invisible Floor Maze )",
"Ganon's Tower (Compass Chest / Invisible Floor )",
"Ice Palace (Big Chest )",
"Ice Palace",
"Misery Mire (Pre-Vitreous )",
"Misery Mire (Fish )",
"Misery Mire (Bridge Key Chest )",
"Misery Mire",
"Turtle Rock (Trinexx[Boss])",
"Ganon's Tower (Wizzrobes s)",
"Ganon's Tower (Moldorm Fall )",
"Tower of Hera (Fairy )",
"Eastern Palace (Stalfos Spawn )",
"Eastern Palace (Big Chest )",
"Eastern Palace (Map Chest )",
"Thieves Town (Moving Spikes / Key Pot )",
"Thieves Town (Blind The Thief[Boss])",
"Empty Clone ",
"Ice Palace",
"Ice Palace (Ice Bridge )",
"Agahnim's Tower (Circle of Pots)",
"Misery Mire (Hourglass )",
"Misery Mire (Slug )",
"Misery Mire (Spike Key Chest )",
"Turtle Rock (Pre-Trinexx )",
"Turtle Rock (Dark Maze)",
"Turtle Rock (Chain Chomps )",
"Turtle Rock (Map Chest / Key Chest / Roller )",
"Eastern Palace (Big Key )",
"Eastern Palace (Lobby Cannonballs )",
"Eastern Palace (Dark Antifairy / Key Pot )",
"Thieves Town (Hellway)",
"Thieves Town (Conveyor Toilet)",
"Empty Clone ",
"Ice Palace (Block Puzzle )",
"Ice Palace Clone (Switch )",
"Agahnim's Tower (Dark Bridge )",
"Misery Mire (Compass Chest / Tile )",
"Misery Mire (Big Hub )",
"Misery Mire (Big Chest )",
"Turtle Rock (Final Crystal Switch Puzzle )",
"Turtle Rock (Laser Bridge)",
"Turtle Rock",
"Turtle Rock (Torch Puzzle)",
"Eastern Palace (Armos Knights[Boss])",
"Eastern Palace (Entrance )",
"??",
"Thieves Town (North West Entrance )",
"Thieves Town (North East Entrance )",
"Empty Clone ",
"Ice Palace (Hole to Kholdstare )",
"Empty Clone ",
"Agahnim's Tower (Dark Maze)",
"Misery Mire (Conveyor Slug / Big Key )",
"Misery Mire (Mire02 / Wizzrobes )",
"Empty Clone ",
"Empty Clone ",
"Turtle Rock (Laser Key )",
"Turtle Rock (Entrance )",
"Empty Clone ",
"Eastern Palace (Zeldagamer / Pre-Armos Knights )",
"Eastern Palace (Canonball ",
"Eastern Palace",
"Thieves Town (Main (South West) Entrance )",
"Thieves Town (South East Entrance )",
"Empty Clone ",
"Ice Palace (Kholdstare[Boss])",
"Cave",
"Agahnim's Tower (Entrance )",
"Cave (Lost Woods HP)",
"Cave (Lumberjack's Tree HP)",
"Cave (1/2 Magic)",
"Cave (Lost Old Man Final Cave)",
"Cave (Lost Old Man Final Cave)",
"Cave",
"Cave",
"Cave",
"Empty Clone ",
"Cave (Spectacle Rock HP)",
"Cave",
"Empty Clone ",
"Cave",
"Cave (Spiral Cave)",
"Cave (Crystal Switch / 5 Chests )",
"Cave (Lost Old Man Starting Cave)",
"Cave (Lost Old Man Starting Cave)",
"House",
"House (Old Woman (Sahasrahla's Wife?))",
"House (Angry Brothers)",
"House (Angry Brothers)",
"Empty Clone ",
"Empty Clone ",
"Cave",
"Cave",
"Cave",
"Cave",
"Empty Clone ",
"Cave",
"Cave",
"Cave",
"Chest Minigame",
"Houses",
"Sick Boy house",
"Tavern",
"Link's House",
"Sarashrala Hut",
"Chest Minigame",
"Library",
"Chicken House",
"Witch Shop",
"A Aginah's Cave",
"Dam",
"Mimic Cave",
"Mire Shed",
"Cave",
"Shop",
"Shop",
"Archery Minigame",
"DW Church/Shop",
"Grave Cave",
"Fairy Fountain",
"Fairy Upgrade",
"Pyramid Fairy",
"Spike Cave",
"Chest Minigame",
"Blind Hut",
"Bonzai Cave",
"Circle of bush Cave",
"Big Bomb Shop, C-House",
"Blind Hut 2",
"Hype Cave",
"Shop",
"Ice Cave",
"Smith",
"Fortune Teller",
"MiniMoldorm Cave",
"Under Rock Caves",
"Smith",
"Cave",
"Mazeblock Cave",
"Smith Peg Cave"};
static const std::string RoomTag[] = {"Nothing",
"NW Kill Enemy to Open",
"NE Kill Enemy to Open",
"SW Kill Enemy to Open",
"SE Kill Enemy to Open",
"W Kill Enemy to Open",
"E Kill Enemy to Open",
"N Kill Enemy to Open",
"S Kill Enemy to Open",
"Clear Quadrant to Open",
"Clear Full Tile to Open",
"NW Push Block to Open",
"NE Push Block to Open",
"SW Push Block to Open",
"SE Push Block to Open",
"W Push Block to Open",
"E Push Block to Open",
"N Push Block to Open",
"S Push Block to Open",
"Push Block to Open",
"Pull Lever to Open",
"Collect Prize to Open",
"Hold Switch Open Door",
"Toggle Switch to Open Door",
"Turn off Water",
"Turn on Water",
"Water Gate",
"Water Twin",
"Moving Wall Right",
"Moving Wall Left",
"Crash",
"Crash",
"Push Switch Exploding Wall",
"Holes 0",
"Open Chest (Holes 0)",
"Holes 1",
"Holes 2",
"Defeat Boss for Dungeon Prize",
"SE Kill Enemy to Push Block",
"Trigger Switch Chest",
"Pull Lever Exploding Wall",
"NW Kill Enemy for Chest",
"NE Kill Enemy for Chest",
"SW Kill Enemy for Chest",
"SE Kill Enemy for Chest",
"W Kill Enemy for Chest",
"E Kill Enemy for Chest",
"N Kill Enemy for Chest",
"S Kill Enemy for Chest",
"Clear Quadrant for Chest",
"Clear Full Tile for Chest",
"Light Torches to Open",
"Holes 3",
"Holes 4",
"Holes 5",
"Holes 6",
"Agahnim Room",
"Holes 7",
"Holes 8",
"Open Chest for Holes 8",
"Push Block for Chest",
"Clear Room for Triforce Door",
"Light Torches for Chest",
"Kill Boss Again"};
} // namespace zelda3
} // namespace yaze
#endif

View File

@@ -0,0 +1,366 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_ENTRANCE_H
#define YAZE_APP_ZELDA3_DUNGEON_ROOM_ENTRANCE_H
#include <cstdint>
#include "app/rom.h"
namespace yaze {
namespace zelda3 {
// 0x14577 word value for each room
constexpr int kEntranceRoom = 0x14813;
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
constexpr int kEntranceScrollEdge = 0x1491D; // 0x14681
constexpr int kEntranceYScroll = 0x14D45; // 0x14AA9 2 bytes each room
constexpr int kEntranceXScroll = 0x14E4F; // 0x14BB3 2 bytes
constexpr int kEntranceYPosition = 0x14F59; // 0x14CBD 2bytes
constexpr int kEntranceXPosition = 0x15063; // 0x14DC7 2bytes
constexpr int kEntranceCameraYTrigger = 0x1516D; // 0x14ED1 2bytes
constexpr int kEntranceCameraXTrigger = 0x15277; // 0x14FDB 2bytes
constexpr int kEntranceBlockset = 0x15381; // 0x150E5 1byte
constexpr int kEntranceFloor = 0x15406; // 0x1516A 1byte
constexpr int kEntranceDungeon = 0x1548B; // 0x151EF 1byte (dungeon id)
constexpr int kEntranceDoor = 0x15510; // 0x15274 1byte
// 1 byte, ---b ---a b = bg2, a = need to check
constexpr int kEntranceLadderBG = 0x15595; // 0x152F9
constexpr int kEntrancescrolling = 0x1561A; // 0x1537E 1byte --h- --v-
constexpr int kEntranceScrollQuadrant = 0x1569F; // 0x15403 1byte
constexpr int kEntranceExit = 0x15724; // 0x15488 2byte word
constexpr int kEntranceMusic = 0x1582E; // 0x15592
// word value for each room
constexpr int kStartingEntranceroom = 0x15B6E; // 0x158D2
// 8 bytes per room, HU, FU, HD, FD, HL, FL, HR, FR
constexpr int kStartingEntranceScrollEdge = 0x15B7C; // 0x158E0
constexpr int kStartingEntranceYScroll = 0x15BB4; // 0x14AA9 //2bytes each room
constexpr int kStartingEntranceXScroll = 0x15BC2; // 0x14BB3 //2bytes
constexpr int kStartingEntranceYPosition = 0x15BD0; // 0x14CBD 2bytes
constexpr int kStartingEntranceXPosition = 0x15BDE; // 0x14DC7 2bytes
constexpr int kStartingEntranceCameraYTrigger = 0x15BEC; // 0x14ED1 2bytes
constexpr int kStartingEntranceCameraXTrigger = 0x15BFA; // 0x14FDB 2bytes
constexpr int kStartingEntranceBlockset = 0x15C08; // 0x150E5 1byte
constexpr int kStartingEntranceFloor = 0x15C0F; // 0x1516A 1byte
constexpr int kStartingEntranceDungeon = 0x15C16; // 0x151EF 1byte (dungeon id)
constexpr int kStartingEntranceDoor = 0x15C2B; // 0x15274 1byte
// 1 byte, ---b ---a b = bg2, a = need to check
constexpr int kStartingEntranceLadderBG = 0x15C1D; // 0x152F9
// 1byte --h- --v-
constexpr int kStartingEntrancescrolling = 0x15C24; // 0x1537E
constexpr int kStartingEntranceScrollQuadrant = 0x15C2B; // 0x15403 1byte
constexpr int kStartingEntranceexit = 0x15C32; // 0x15488 //2byte word
constexpr int kStartingEntrancemusic = 0x15C4E; // 0x15592
constexpr int kStartingEntranceentrance = 0x15C40;
constexpr int items_data_start = 0xDDE9; // save purpose
constexpr int items_data_end = 0xE6B2; // save purpose
constexpr int initial_equipement = 0x271A6;
// item id you get instead if you already have that item
constexpr int chests_backupitems = 0x3B528;
constexpr int chests_yoffset = 0x4836C;
constexpr int chests_xoffset = 0x4836C + (76 * 1);
constexpr int chests_itemsgfx = 0x4836C + (76 * 2);
constexpr int chests_itemswide = 0x4836C + (76 * 3);
constexpr int chests_itemsproperties = 0x4836C + (76 * 4);
constexpr int chests_sramaddress = 0x4836C + (76 * 5);
constexpr int chests_sramvalue = 0x4836C + (76 * 7);
constexpr int chests_msgid = 0x442DD;
constexpr int dungeons_startrooms = 0x7939;
constexpr int dungeons_endrooms = 0x792D;
constexpr int dungeons_bossrooms = 0x10954; // short value
// Bed Related Values (Starting location)
constexpr int bedPositionX = 0x039A37; // short value
constexpr int bedPositionY = 0x039A32; // short value
// short value (on 2 different bytes)
constexpr int bedPositionResetXLow = 0x02DE53;
constexpr int bedPositionResetXHigh = 0x02DE58;
// short value (on 2 different bytes)
constexpr int bedPositionResetYLow = 0x02DE5D;
constexpr int bedPositionResetYHigh = 0x02DE62;
constexpr int bedSheetPositionX = 0x0480BD; // short value
constexpr int bedSheetPositionY = 0x0480B8; // short value
/**
* @brief Dungeon Room Entrance or Spawn Point
*/
class RoomEntrance {
public:
RoomEntrance() = default;
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) +
rom->data()[kEntranceRoom + (entrance_id * 2)]);
y_position_ = static_cast<uint16_t>(
(rom->data()[kEntranceYPosition + (entrance_id * 2) + 1] << 8) +
rom->data()[kEntranceYPosition + (entrance_id * 2)]);
x_position_ = static_cast<uint16_t>(
(rom->data()[kEntranceXPosition + (entrance_id * 2) + 1] << 8) +
rom->data()[kEntranceXPosition + (entrance_id * 2)]);
camera_x_ = static_cast<uint16_t>(
(rom->data()[kEntranceXScroll + (entrance_id * 2) + 1] << 8) +
rom->data()[kEntranceXScroll + (entrance_id * 2)]);
camera_y_ = static_cast<uint16_t>(
(rom->data()[kEntranceYScroll + (entrance_id * 2) + 1] << 8) +
rom->data()[kEntranceYScroll + (entrance_id * 2)]);
camera_trigger_y_ = static_cast<uint16_t>(
(rom->data()[(kEntranceCameraYTrigger + (entrance_id * 2)) + 1] << 8) +
rom->data()[kEntranceCameraYTrigger + (entrance_id * 2)]);
camera_trigger_x_ = static_cast<uint16_t>(
(rom->data()[(kEntranceCameraXTrigger + (entrance_id * 2)) + 1] << 8) +
rom->data()[kEntranceCameraXTrigger + (entrance_id * 2)]);
blockset_ = rom->data()[kEntranceBlockset + entrance_id];
music_ = rom->data()[kEntranceMusic + entrance_id];
dungeon_id_ = rom->data()[kEntranceDungeon + entrance_id];
floor_ = rom->data()[kEntranceFloor + entrance_id];
door_ = rom->data()[kEntranceDoor + entrance_id];
ladder_bg_ = rom->data()[kEntranceLadderBG + entrance_id];
scrolling_ = rom->data()[kEntrancescrolling + entrance_id];
scroll_quadrant_ = rom->data()[kEntranceScrollQuadrant + entrance_id];
exit_ = static_cast<short>(
(rom->data()[kEntranceExit + (entrance_id * 2) + 1] << 8) +
rom->data()[kEntranceExit + (entrance_id * 2)]);
camera_boundary_qn_ =
rom->data()[kEntranceScrollEdge + 0 + (entrance_id * 8)];
camera_boundary_fn_ =
rom->data()[kEntranceScrollEdge + 1 + (entrance_id * 8)];
camera_boundary_qs_ =
rom->data()[kEntranceScrollEdge + 2 + (entrance_id * 8)];
camera_boundary_fs_ =
rom->data()[kEntranceScrollEdge + 3 + (entrance_id * 8)];
camera_boundary_qw_ =
rom->data()[kEntranceScrollEdge + 4 + (entrance_id * 8)];
camera_boundary_fw_ =
rom->data()[kEntranceScrollEdge + 5 + (entrance_id * 8)];
camera_boundary_qe_ =
rom->data()[kEntranceScrollEdge + 6 + (entrance_id * 8)];
camera_boundary_fe_ =
rom->data()[kEntranceScrollEdge + 7 + (entrance_id * 8)];
if (is_spawn_point) {
room_ = static_cast<short>(
(rom->data()[kStartingEntranceroom + (entrance_id * 2) + 1] << 8) +
rom->data()[kStartingEntranceroom + (entrance_id * 2)]);
y_position_ = static_cast<uint16_t>(
(rom->data()[kStartingEntranceYPosition + (entrance_id * 2) + 1]
<< 8) +
rom->data()[kStartingEntranceYPosition + (entrance_id * 2)]);
x_position_ = static_cast<uint16_t>(
(rom->data()[kStartingEntranceXPosition + (entrance_id * 2) + 1]
<< 8) +
rom->data()[kStartingEntranceXPosition + (entrance_id * 2)]);
camera_x_ = static_cast<uint16_t>(
(rom->data()[kStartingEntranceXScroll + (entrance_id * 2) + 1] << 8) +
rom->data()[kStartingEntranceXScroll + (entrance_id * 2)]);
camera_y_ = static_cast<uint16_t>(
(rom->data()[kStartingEntranceYScroll + (entrance_id * 2) + 1] << 8) +
rom->data()[kStartingEntranceYScroll + (entrance_id * 2)]);
camera_trigger_y_ = static_cast<uint16_t>(
(rom->data()[kStartingEntranceCameraYTrigger + (entrance_id * 2) + 1]
<< 8) +
rom->data()[kStartingEntranceCameraYTrigger + (entrance_id * 2)]);
camera_trigger_x_ = static_cast<uint16_t>(
(rom->data()[kStartingEntranceCameraXTrigger + (entrance_id * 2) + 1]
<< 8) +
rom->data()[kStartingEntranceCameraXTrigger + (entrance_id * 2)]);
blockset_ = rom->data()[kStartingEntranceBlockset + entrance_id];
music_ = rom->data()[kStartingEntrancemusic + entrance_id];
dungeon_id_ = rom->data()[kStartingEntranceDungeon + entrance_id];
floor_ = rom->data()[kStartingEntranceFloor + entrance_id];
door_ = rom->data()[kStartingEntranceDoor + entrance_id];
ladder_bg_ = rom->data()[kStartingEntranceLadderBG + entrance_id];
scrolling_ = rom->data()[kStartingEntrancescrolling + entrance_id];
scroll_quadrant_ =
rom->data()[kStartingEntranceScrollQuadrant + entrance_id];
exit_ = static_cast<short>(
((rom->data()[kStartingEntranceexit + (entrance_id * 2) + 1] & 0x01)
<< 8) +
rom->data()[kStartingEntranceexit + (entrance_id * 2)]);
camera_boundary_qn_ =
rom->data()[kStartingEntranceScrollEdge + 0 + (entrance_id * 8)];
camera_boundary_fn_ =
rom->data()[kStartingEntranceScrollEdge + 1 + (entrance_id * 8)];
camera_boundary_qs_ =
rom->data()[kStartingEntranceScrollEdge + 2 + (entrance_id * 8)];
camera_boundary_fs_ =
rom->data()[kStartingEntranceScrollEdge + 3 + (entrance_id * 8)];
camera_boundary_qw_ =
rom->data()[kStartingEntranceScrollEdge + 4 + (entrance_id * 8)];
camera_boundary_fw_ =
rom->data()[kStartingEntranceScrollEdge + 5 + (entrance_id * 8)];
camera_boundary_qe_ =
rom->data()[kStartingEntranceScrollEdge + 6 + (entrance_id * 8)];
camera_boundary_fe_ =
rom->data()[kStartingEntranceScrollEdge + 7 + (entrance_id * 8)];
}
}
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_));
RETURN_IF_ERROR(
rom->WriteShort(kEntranceXPosition + (entrance_id * 2), x_position_));
RETURN_IF_ERROR(
rom->WriteShort(kEntranceYScroll + (entrance_id * 2), camera_y_));
RETURN_IF_ERROR(
rom->WriteShort(kEntranceXScroll + (entrance_id * 2), camera_x_));
RETURN_IF_ERROR(rom->WriteShort(
kEntranceCameraXTrigger + (entrance_id * 2), camera_trigger_x_));
RETURN_IF_ERROR(rom->WriteShort(
kEntranceCameraYTrigger + (entrance_id * 2), camera_trigger_y_));
RETURN_IF_ERROR(
rom->WriteShort(kEntranceExit + (entrance_id * 2), exit_));
RETURN_IF_ERROR(rom->WriteByte(kEntranceBlockset + entrance_id,
(uint8_t)(blockset_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kEntranceMusic + entrance_id,
(uint8_t)(music_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kEntranceDungeon + entrance_id,
(uint8_t)(dungeon_id_ & 0xFF)));
RETURN_IF_ERROR(
rom->WriteByte(kEntranceDoor + entrance_id, (uint8_t)(door_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kEntranceFloor + entrance_id,
(uint8_t)(floor_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kEntranceLadderBG + entrance_id,
(uint8_t)(ladder_bg_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kEntrancescrolling + entrance_id,
(uint8_t)(scrolling_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kEntranceScrollQuadrant + entrance_id,
(uint8_t)(scroll_quadrant_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 0 + (entrance_id * 8), camera_boundary_qn_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 1 + (entrance_id * 8), camera_boundary_fn_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 2 + (entrance_id * 8), camera_boundary_qs_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 3 + (entrance_id * 8), camera_boundary_fs_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 4 + (entrance_id * 8), camera_boundary_qw_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 5 + (entrance_id * 8), camera_boundary_fw_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 6 + (entrance_id * 8), camera_boundary_qe_));
RETURN_IF_ERROR(rom->WriteByte(
kEntranceScrollEdge + 7 + (entrance_id * 8), camera_boundary_fe_));
} else {
RETURN_IF_ERROR(
rom->WriteShort(kStartingEntranceroom + (entrance_id * 2), room_));
RETURN_IF_ERROR(rom->WriteShort(
kStartingEntranceYPosition + (entrance_id * 2), y_position_));
RETURN_IF_ERROR(rom->WriteShort(
kStartingEntranceXPosition + (entrance_id * 2), x_position_));
RETURN_IF_ERROR(rom->WriteShort(
kStartingEntranceYScroll + (entrance_id * 2), camera_y_));
RETURN_IF_ERROR(rom->WriteShort(
kStartingEntranceXScroll + (entrance_id * 2), camera_x_));
RETURN_IF_ERROR(
rom->WriteShort(kStartingEntranceCameraXTrigger + (entrance_id * 2),
camera_trigger_x_));
RETURN_IF_ERROR(
rom->WriteShort(kStartingEntranceCameraYTrigger + (entrance_id * 2),
camera_trigger_y_));
RETURN_IF_ERROR(
rom->WriteShort(kStartingEntranceexit + (entrance_id * 2), exit_));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntranceBlockset + entrance_id,
(uint8_t)(blockset_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntrancemusic + entrance_id,
(uint8_t)(music_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntranceDungeon + entrance_id,
(uint8_t)(dungeon_id_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntranceDoor + entrance_id,
(uint8_t)(door_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntranceFloor + entrance_id,
(uint8_t)(floor_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntranceLadderBG + entrance_id,
(uint8_t)(ladder_bg_ & 0xFF)));
RETURN_IF_ERROR(rom->WriteByte(kStartingEntrancescrolling + entrance_id,
(uint8_t)(scrolling_ & 0xFF)));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollQuadrant + entrance_id,
(uint8_t)(scroll_quadrant_ & 0xFF)));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 0 + (entrance_id * 8),
camera_boundary_qn_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 1 + (entrance_id * 8),
camera_boundary_fn_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 2 + (entrance_id * 8),
camera_boundary_qs_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 3 + (entrance_id * 8),
camera_boundary_fs_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 4 + (entrance_id * 8),
camera_boundary_qw_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 5 + (entrance_id * 8),
camera_boundary_fw_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 6 + (entrance_id * 8),
camera_boundary_qe_));
RETURN_IF_ERROR(
rom->WriteByte(kStartingEntranceScrollEdge + 7 + (entrance_id * 8),
camera_boundary_fe_));
}
return absl::OkStatus();
}
uint16_t entrance_id_;
uint16_t x_position_;
uint16_t y_position_;
uint16_t camera_x_;
uint16_t camera_y_;
uint16_t camera_trigger_x_;
uint16_t camera_trigger_y_;
int16_t room_;
uint8_t blockset_;
uint8_t floor_;
uint8_t dungeon_id_;
uint8_t ladder_bg_;
uint8_t scrolling_;
uint8_t scroll_quadrant_;
int16_t exit_;
uint8_t music_;
uint8_t door_;
uint8_t camera_boundary_qn_;
uint8_t camera_boundary_fn_;
uint8_t camera_boundary_qs_;
uint8_t camera_boundary_fs_;
uint8_t camera_boundary_qw_;
uint8_t camera_boundary_fw_;
uint8_t camera_boundary_qe_;
uint8_t camera_boundary_fe_;
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_ROOM_ENTRANCE_H

View File

@@ -0,0 +1,76 @@
#include "room_layout.h"
#include "absl/strings/str_format.h"
#include "app/snes.h"
#include "zelda3/dungeon/dungeon_rom_addresses.h"
namespace yaze::zelda3 {
namespace {
constexpr uint16_t kLayerTerminator = 0xFFFF;
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));
}
}
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));
}
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 pc_addr;
}
absl::Status RoomLayout::LoadLayout(int layout_id) {
objects_.clear();
auto addr_result = GetLayoutAddress(layout_id);
if (!addr_result.ok()) {
return addr_result.status();
}
int pos = addr_result.value();
const auto& rom_data = rom_->data();
int layer = 0;
while (pos + 2 < static_cast<int>(rom_->size())) {
uint8_t b1 = rom_data[pos];
uint8_t b2 = rom_data[pos + 1];
if (b1 == 0xFF && b2 == 0xFF) {
layer++;
pos += 2;
if (layer >= 3) {
break;
}
continue;
}
if (pos + 2 >= static_cast<int>(rom_->size())) {
break;
}
uint8_t b3 = rom_data[pos + 2];
pos += 3;
RoomObject obj = RoomObject::DecodeObjectFromBytes(b1, b2, b3, static_cast<uint8_t>(layer));
obj.set_rom(rom_);
obj.EnsureTilesLoaded();
objects_.push_back(obj);
}
return absl::OkStatus();
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,33 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_LAYOUT_H
#define YAZE_APP_ZELDA3_DUNGEON_ROOM_LAYOUT_H
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
#include "zelda3/dungeon/room_object.h"
namespace yaze::zelda3 {
class RoomLayout {
public:
RoomLayout() = default;
explicit RoomLayout(Rom* rom) : rom_(rom) {}
void set_rom(Rom* rom) { rom_ = rom; }
absl::Status LoadLayout(int layout_id);
const std::vector<RoomObject>& GetObjects() const { return objects_; }
private:
absl::StatusOr<int> GetLayoutAddress(int layout_id) const;
Rom* rom_ = nullptr;
std::vector<RoomObject> objects_;
};
} // namespace yaze::zelda3
#endif // YAZE_APP_ZELDA3_DUNGEON_ROOM_LAYOUT_H

View File

@@ -0,0 +1,242 @@
#include "room_object.h"
#include "absl/status/status.h"
#include "zelda3/dungeon/object_parser.h"
#include "util/log.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
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
if (object_id >= 0x200) {
return SubtypeTableInfo(kRoomObjectSubtype3, 0xFF);
} else if (object_id >= 0x100) {
return SubtypeTableInfo(kRoomObjectSubtype2, 0x7F);
} else {
return SubtypeTableInfo(kRoomObjectSubtype1, 0xFF);
}
}
} // namespace
ObjectOption operator|(ObjectOption lhs, ObjectOption rhs) {
return static_cast<ObjectOption>(static_cast<int>(lhs) |
static_cast<int>(rhs));
}
ObjectOption operator&(ObjectOption lhs, ObjectOption rhs) {
return static_cast<ObjectOption>(static_cast<int>(lhs) &
static_cast<int>(rhs));
}
ObjectOption operator^(ObjectOption lhs, ObjectOption rhs) {
return static_cast<ObjectOption>(static_cast<int>(lhs) ^
static_cast<int>(rhs));
}
ObjectOption operator~(ObjectOption option) {
return static_cast<ObjectOption>(~static_cast<int>(option));
}
// NOTE: DrawTile was legacy ZScream code that is no longer used.
// Modern rendering uses ObjectDrawer which draws directly to BackgroundBuffer bitmaps.
void RoomObject::EnsureTilesLoaded() {
if (tiles_loaded_) {
return;
}
if (rom_ == nullptr) {
return;
}
// Try the new parser first - this is more efficient and accurate
auto parser_status = LoadTilesWithParser();
if (parser_status.ok()) {
tiles_loaded_ = true;
return;
}
// Fallback to legacy method for compatibility with enhanced validation
auto rom_data = rom_->data();
// Determine which subtype table to use and compute the tile data offset.
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
return;
}
int tile_rel = (int16_t)((rom_data[tile_ptr + 1] << 8) + rom_data[tile_ptr]);
int pos = kRoomObjectTileAddress + tile_rel;
tile_data_ptr_ = pos;
// Enhanced bounds checking for tile data
if (pos < 0 || pos + 7 >= (int)rom_->size()) {
// 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));
uint16_t w2 = (uint16_t)(rom_data[pos + 4] | (rom_data[pos + 5] << 8));
uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
tiles_.clear();
tiles_.push_back(gfx::WordToTileInfo(w0));
tiles_.push_back(gfx::WordToTileInfo(w1));
tiles_.push_back(gfx::WordToTileInfo(w2));
tiles_.push_back(gfx::WordToTileInfo(w3));
tile_count_ = 1;
tiles_loaded_ = true;
}
absl::Status RoomObject::LoadTilesWithParser() {
if (rom_ == nullptr) {
return absl::InvalidArgumentError("ROM is null");
}
ObjectParser parser(rom_);
auto result = parser.ParseObject(id_);
if (!result.ok()) {
return result.status();
}
tiles_ = std::move(result.value());
tile_count_ = tiles_.size();
return absl::OkStatus();
}
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());
}
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 &tiles_[index];
}
int RoomObject::GetTileCount() const {
if (!tiles_loaded_) {
const_cast<RoomObject*>(this)->EnsureTilesLoaded();
}
return tile_count_;
}
// ============================================================================
// Object Encoding/Decoding Implementation (Phase 1, Task 1.1)
// ============================================================================
int RoomObject::DetermineObjectType(uint8_t /* b1 */, uint8_t b3) {
// Type 3: Objects with ID >= 0xF00
// These have b3 >= 0xF8 (top nibble is 0xF)
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;
}
RoomObject RoomObject::DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3,
uint8_t layer) {
uint8_t x = 0;
uint8_t y = 0;
uint8_t size = 0;
uint16_t id = 0;
// 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 |
((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);
} 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",
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",
b1, b2, b3, id, x, y, size);
}
}
return RoomObject(static_cast<int16_t>(id), x, y, size, layer);
}
RoomObject::ObjectBytes RoomObject::EncodeObjectToBytes() const {
ObjectBytes bytes;
// Determine type based on object ID
if (id_ >= 0x100 && id_ < 0x200) {
// Type 2: 111111xx xxxxyyyy yyiiiiii
bytes.b1 = 0xFC | ((x_ & 0x30) >> 4);
bytes.b2 = ((x_ & 0x0F) << 4) | ((y_ & 0x3C) >> 2);
bytes.b3 = ((y_ & 0x03) << 6) | (id_ & 0x3F);
} else if (id_ >= 0xF00) {
// Type 3: xxxxxxii yyyyyyii 11111iii
bytes.b1 = (x_ << 2) | (id_ & 0x03);
bytes.b2 = (y_ << 2) | ((id_ >> 2) & 0x03);
bytes.b3 = (id_ >> 4) & 0xFF;
} else {
// Type 1: xxxxxxss yyyyyyss iiiiiiii
uint8_t clamped_size = size_ > 15 ? 15 : size_;
bytes.b1 = (x_ << 2) | ((clamped_size >> 2) & 0x03);
bytes.b2 = (y_ << 2) | (clamped_size & 0x03);
bytes.b3 = static_cast<uint8_t>(id_);
}
return bytes;
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,631 @@
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_OBJECT_H
#define YAZE_APP_ZELDA3_DUNGEON_ROOM_OBJECT_H
#include <cstdint>
#include <string>
#include <vector>
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
#include "zelda3/dungeon/object_parser.h"
namespace yaze {
namespace zelda3 {
enum class SpecialObjectType { Chest, BigChest, InterroomStairs };
enum Sorting {
All = 0,
Wall = 1,
Horizontal = 2,
Vertical = 4,
NonScalable = 8,
Dungeons = 16,
Floors = 32,
SortStairs = 64
};
enum class ObjectOption {
Nothing = 0,
Door = 1,
Chest = 2,
Block = 4,
Torch = 8,
Bgr = 16,
Stairs = 32
};
ObjectOption operator|(ObjectOption lhs, ObjectOption rhs);
ObjectOption operator&(ObjectOption lhs, ObjectOption rhs);
ObjectOption operator^(ObjectOption lhs, ObjectOption rhs);
ObjectOption operator~(ObjectOption option);
constexpr int kRoomObjectSubtype1 = 0x8000; // JP = Same
constexpr int kRoomObjectSubtype2 = 0x83F0; // JP = Same
constexpr int kRoomObjectSubtype3 = 0x84F0; // JP = Same
constexpr int kRoomObjectTileAddress = 0x1B52; // JP = Same
constexpr int kRoomObjectTileAddressFloor = 0x1B5A; // JP = Same
class RoomObject {
public:
enum LayerType { BG1 = 0, BG2 = 1, BG3 = 2 };
RoomObject(int16_t id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer = 0)
: id_(id),
x_(x),
y_(y),
size_(size),
layer_(static_cast<LayerType>(layer)),
nx_(x),
ny_(y),
ox_(x),
oy_(y),
width_(16),
height_(16),
rom_(nullptr) {}
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; }
void set_size(uint8_t size) { size_ = size; }
uint8_t x() const { return x_; }
uint8_t y() const { return y_; }
uint8_t size() const { return size_; }
// 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();
// Getter for tiles
const std::vector<gfx::TileInfo>& tiles() const { return tiles_; }
std::vector<gfx::TileInfo>& mutable_tiles() { return tiles_; }
// 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:
// - ObjectParser for loading tiles from ROM
// - ObjectDrawer for rendering tiles to BackgroundBuffer
auto options() const { return options_; }
void set_options(ObjectOption options) { options_ = options; }
bool all_bgs_ = false;
bool lit_ = false;
int16_t id_;
uint8_t x_;
uint8_t y_;
uint8_t size_;
uint8_t nx_;
uint8_t ny_;
uint8_t ox_;
uint8_t oy_;
uint8_t z_ = 0;
uint8_t previous_size_ = 0;
// Size nibble bits captured from object encoding (0..3 each) for heuristic
// orientation and sizing decisions.
uint8_t size_x_bits_ = 0;
uint8_t size_y_bits_ = 0;
int width_;
int height_;
int offset_x_ = 0;
int offset_y_ = 0;
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
mutable bool tiles_loaded_ = false;
mutable int tile_count_ = 0;
mutable int tile_data_ptr_ = -1; // Pointer to tile data in ROM
LayerType layer_;
ObjectOption options_ = ObjectOption::Nothing;
Rom* rom_;
};
// NOTE: Legacy Subtype1, Subtype2, Subtype3 classes removed.
// These were ported from ZScream but are no longer used.
// Modern code uses: ObjectParser + ObjectDrawer + ObjectRenderer
constexpr static inline const char* Type1RoomObjectNames[] = {
"Ceiling ↔",
"Wall (top, north) ↔",
"Wall (top, south) ↔",
"Wall (bottom, north) ↔",
"Wall (bottom, south) ↔",
"Wall columns (north) ↔",
"Wall columns (south) ↔",
"Deep wall (north) ↔",
"Deep wall (south) ↔",
"Diagonal wall A ◤ (top) ↔",
"Diagonal wall A ◣ (top) ↔",
"Diagonal wall A ◥ (top) ↔",
"Diagonal wall A ◢ (top) ↔",
"Diagonal wall B ◤ (top) ↔",
"Diagonal wall B ◣ (top) ↔",
"Diagonal wall B ◥ (top) ↔",
"Diagonal wall B ◢ (top) ↔",
"Diagonal wall C ◤ (top) ↔",
"Diagonal wall C ◣ (top) ↔",
"Diagonal wall C ◥ (top) ↔",
"Diagonal wall C ◢ (top) ↔",
"Diagonal wall A ◤ (bottom) ↔",
"Diagonal wall A ◣ (bottom) ↔",
"Diagonal wall A ◥ (bottom) ↔",
"Diagonal wall A ◢ (bottom) ↔",
"Diagonal wall B ◤ (bottom) ↔",
"Diagonal wall B ◣ (bottom) ↔",
"Diagonal wall B ◥ (bottom) ↔",
"Diagonal wall B ◢ (bottom) ↔",
"Diagonal wall C ◤ (bottom) ↔",
"Diagonal wall C ◣ (bottom) ↔",
"Diagonal wall C ◥ (bottom) ↔",
"Diagonal wall C ◢ (bottom) ↔",
"Platform stairs ↔",
"Rail ↔",
"Pit edge ┏━┓ A (north) ↔",
"Pit edge ┏━┓ B (north) ↔",
"Pit edge ┏━┓ C (north) ↔",
"Pit edge ┏━┓ D (north) ↔",
"Pit edge ┏━┓ E (north) ↔",
"Pit edge ┗━┛ (south) ↔",
"Pit edge ━━━ (south) ↔",
"Pit edge ━━━ (north) ↔",
"Pit edge ━━┛ (south) ↔",
"Pit edge ┗━━ (south) ↔",
"Pit edge ━━┓ (north) ↔",
"Pit edge ┏━━ (north) ↔",
"Rail wall (north) ↔",
"Rail wall (south) ↔",
"Nothing",
"Nothing",
"Carpet ↔",
"Carpet trim ↔",
"Weird door", // TODO: WEIRD DOOR OBJECT NEEDS INVESTIGATION
"Drapes (north) ↔",
"Drapes (west, odd) ↔",
"Statues ↔",
"Columns ↔",
"Wall decors (north) ↔",
"Wall decors (south) ↔",
"Chairs in pairs ↔",
"Tall torches ↔",
"Supports (north) ↔",
"Water edge ┏━┓ (concave) ↔",
"Water edge ┗━┛ (concave) ↔",
"Water edge ┏━┓ (convex) ↔",
"Water edge ┗━┛ (convex) ↔",
"Water edge ┏━┛ (concave) ↔",
"Water edge ┗━┓ (concave) ↔",
"Water edge ┗━┓ (convex) ↔",
"Water edge ┏━┛ (convex) ↔",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Supports (south) ↔",
"Bar ↔",
"Shelf A ↔",
"Shelf B ↔",
"Shelf C ↔",
"Somaria path ↔",
"Cannon hole A (north) ↔",
"Cannon hole A (south) ↔",
"Pipe path ↔",
"Nothing",
"Wall torches (north) ↔",
"Wall torches (south) ↔",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Cannon hole B (north) ↔",
"Cannon hole B (south) ↔",
"Thick rail ↔",
"Blocks ↔",
"Long rail ↔",
"Ceiling ↕",
"Wall (top, west) ↕",
"Wall (top, east) ↕",
"Wall (bottom, west) ↕",
"Wall (bottom, east) ↕",
"Wall columns (west) ↕",
"Wall columns (east) ↕",
"Deep wall (west) ↕",
"Deep wall (east) ↕",
"Rail ↕",
"Pit edge (west) ↕",
"Pit edge (east) ↕",
"Rail wall (west) ↕",
"Rail wall (east) ↕",
"Nothing",
"Nothing",
"Carpet ↕",
"Carpet trim ↕",
"Nothing",
"Drapes (west) ↕",
"Drapes (east) ↕",
"Columns ↕",
"Wall decors (west) ↕",
"Wall decors (east) ↕",
"Supports (west) ↕",
"Water edge (west) ↕",
"Water edge (east) ↕",
"Supports (east) ↕",
"Somaria path ↕",
"Pipe path ↕",
"Nothing",
"Wall torches (west) ↕",
"Wall torches (east) ↕",
"Wall decors tight A (west) ↕",
"Wall decors tight A (east) ↕",
"Wall decors tight B (west) ↕",
"Wall decors tight B (east) ↕",
"Cannon hole (west) ↕",
"Cannon hole (east) ↕",
"Tall torches ↕",
"Thick rail ↕",
"Blocks ↕",
"Long rail ↕",
"Jump ledge (west) ↕",
"Jump ledge (east) ↕",
"Rug trim (west) ↕",
"Rug trim (east) ↕",
"Bar ↕",
"Wall flair (west) ↕",
"Wall flair (east) ↕",
"Blue pegs ↕",
"Orange pegs ↕",
"Invisible floor ↕",
"Fake pots ↕",
"Hammer pegs ↕",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Diagonal ceiling A ◤",
"Diagonal ceiling A ◣",
"Diagonal ceiling A ◥",
"Diagonal ceiling A ◢",
"Pit ⇲",
"Diagonal layer 2 mask A ◤",
"Diagonal layer 2 mask A ◣",
"Diagonal layer 2 mask A ◥",
"Diagonal layer 2 mask A ◢",
"Diagonal layer 2 mask B ◤", // TODO: VERIFY
"Diagonal layer 2 mask B ◣", // TODO: VERIFY
"Diagonal layer 2 mask B ◥", // TODO: VERIFY
"Diagonal layer 2 mask B ◢", // TODO: VERIFY
"Nothing",
"Nothing",
"Nothing",
"Jump ledge (north) ↔",
"Jump ledge (south) ↔",
"Rug ↔",
"Rug trim (north) ↔",
"Rug trim (south) ↔",
"Archery game curtains ↔",
"Wall flair (north) ↔",
"Wall flair (south) ↔",
"Blue pegs ↔",
"Orange pegs ↔",
"Invisible floor ↔",
"Fake pressure plates ↔",
"Fake pots ↔",
"Hammer pegs ↔",
"Nothing",
"Nothing",
"Ceiling (large) ⇲",
"Chest platform (tall) ⇲",
"Layer 2 pit mask (large) ⇲",
"Layer 2 pit mask (medium) ⇲",
"Floor 1 ⇲",
"Floor 3 ⇲",
"Layer 2 mask (large) ⇲",
"Floor 4 ⇲",
"Water floor ⇲ ",
"Flood water (medium) ⇲ ",
"Conveyor floor ⇲ ",
"Nothing",
"Nothing",
"Moving wall (west) ⇲",
"Moving wall (east) ⇲",
"Nothing",
"Nothing",
"Icy floor A ⇲",
"Icy floor B ⇲",
"Moving wall flag", // TODO: WTF IS THIS?
"Moving wall flag", // TODO: WTF IS THIS?
"Moving wall flag", // TODO: WTF IS THIS?
"Moving wall flag", // TODO: WTF IS THIS?
"Layer 2 mask (medium) ⇲",
"Flood water (large) ⇲",
"Layer 2 swim mask ⇲",
"Flood water B (large) ⇲",
"Floor 2 ⇲",
"Chest platform (short) ⇲",
"Table / rock ⇲",
"Spike blocks ⇲",
"Spiked floor ⇲",
"Floor 7 ⇲",
"Tiled floor ⇲",
"Rupee floor ⇲",
"Conveyor upwards ⇲",
"Conveyor downwards ⇲",
"Conveyor leftwards ⇲",
"Conveyor rightwards ⇲",
"Heavy current water ⇲",
"Floor 10 ⇲",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
"Nothing",
};
constexpr static inline const char* Type2RoomObjectNames[] = {
"Corner (top, concave) ▛",
"Corner (top, concave) ▙",
"Corner (top, concave) ▜",
"Corner (top, concave) ▟",
"Corner (top, convex) ▟",
"Corner (top, convex) ▜",
"Corner (top, convex) ▙",
"Corner (top, convex) ▛",
"Corner (bottom, concave) ▛",
"Corner (bottom, concave) ▙",
"Corner (bottom, concave) ▜",
"Corner (bottom, concave) ▟",
"Corner (bottom, convex) ▟",
"Corner (bottom, convex) ▜",
"Corner (bottom, convex) ▙",
"Corner (bottom, convex) ▛",
"Kinked corner north (bottom) ▜",
"Kinked corner south (bottom) ▟",
"Kinked corner north (bottom) ▛",
"Kinked corner south (bottom) ▙",
"Kinked corner west (bottom) ▙",
"Kinked corner west (bottom) ▛",
"Kinked corner east (bottom) ▟",
"Kinked corner east (bottom) ▜",
"Deep corner (concave) ▛",
"Deep corner (concave) ▙",
"Deep corner (concave) ▜",
"Deep corner (concave) ▟",
"Large brazier",
"Statue",
"Star tile (disabled)",
"Star tile (enabled)",
"Small torch (lit)",
"Barrel",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Table",
"Fairy statue",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Chair",
"Bed",
"Fireplace",
"Mario portrait",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Interroom stairs (up)",
"Interroom stairs (down)",
"Interroom stairs B (down)",
"Intraroom stairs north B", // TODO: VERIFY LAYER HANDLING
"Intraroom stairs north (separate layers)",
"Intraroom stairs north (merged layers)",
"Intraroom stairs north (swim layer)",
"Block",
"Water ladder (north)",
"Water ladder (south)", // TODO: NEEDS IN GAME VERIFICATION
"Dam floodgate",
"Interroom spiral stairs up (top)",
"Interroom spiral stairs down (top)",
"Interroom spiral stairs up (bottom)",
"Interroom spiral stairs down (bottom)",
"Sanctuary wall (north)",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Pew",
"Magic bat altar",
};
constexpr static inline const char* Type3RoomObjectNames[] = {
"Waterfall face (empty)",
"Waterfall face (short)",
"Waterfall face (long)",
"Somaria path endpoint",
"Somaria path intersection ╋",
"Somaria path corner ┏",
"Somaria path corner ┗",
"Somaria path corner ┓",
"Somaria path corner ┛",
"Somaria path intersection ┳",
"Somaria path intersection ┻",
"Somaria path intersection ┣",
"Somaria path intersection ┫",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Somaria path 2-way endpoint",
"Somaria path crossover",
"Babasu hole (north)",
"Babasu hole (south)",
"9 blue rupees",
"Telepathy tile",
"Warp door", // TODO: NEEDS IN GAME VERIFICATION THAT THIS IS USELESS
"Kholdstare's shell",
"Hammer peg",
"Prison cell",
"Big key lock",
"Chest",
"Chest (open)",
"Intraroom stairs south", // TODO: VERIFY LAYER HANDLING
"Intraroom stairs south (separate layers)",
"Intraroom stairs south (merged layers)",
"Interroom straight stairs up (north, top)",
"Interroom straight stairs down (north, top)",
"Interroom straight stairs up (south, top)",
"Interroom straight stairs down (south, top)",
"Deep corner (convex) ▟",
"Deep corner (convex) ▜",
"Deep corner (convex) ▙",
"Deep corner (convex) ▛",
"Interroom straight stairs up (north, bottom)",
"Interroom straight stairs down (north, bottom)",
"Interroom straight stairs up (south, bottom)",
"Interroom straight stairs down (south, bottom)",
"Lamp cones",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Liftable large block",
"Agahnim's altar",
"Agahnim's boss room",
"Pot",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Big chest",
"Big chest (open)",
"Intraroom stairs south (swim layer)",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Pipe end (south)",
"Pipe end (north)",
"Pipe end (east)",
"Pipe end (west)",
"Pipe corner ▛",
"Pipe corner ▙",
"Pipe corner ▜",
"Pipe corner ▟",
"Pipe-rock intersection ⯊",
"Pipe-rock intersection ⯋",
"Pipe-rock intersection ◖",
"Pipe-rock intersection ◗",
"Pipe crossover",
"Bombable floor",
"Fake bombable floor",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Warp tile",
"Tool rack",
"Furnace",
"Tub (wide)",
"Anvil",
"Warp tile (disabled)",
"Pressure plate",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Blue peg",
"Orange peg",
"Fortune teller room",
"Unknown", // TODO: NEEDS IN GAME CHECKING
"Bar corner ▛",
"Bar corner ▙",
"Bar corner ▜",
"Bar corner ▟",
"Decorative bowl",
"Tub (tall)",
"Bookcase",
"Range",
"Suitcase",
"Bar bottles",
"Arrow game hole (west)",
"Arrow game hole (east)",
"Vitreous goo gfx",
"Fake pressure plate",
"Medusa head",
"4-way shooter block",
"Pit",
"Wall crack (north)",
"Wall crack (south)",
"Wall crack (west)",
"Wall crack (east)",
"Large decor",
"Water grate (north)",
"Water grate (south)",
"Water grate (west)",
"Water grate (east)",
"Window sunlight",
"Floor sunlight",
"Trinexx's shell",
"Layer 2 mask (full)",
"Boss entrance",
"Minigame chest",
"Ganon door",
"Triforce wall ornament",
"Triforce floor tiles",
"Freezor hole",
"Pile of bones",
"Vitreous goo damage",
"Arrow tile ↑",
"Arrow tile ↓",
"Arrow tile →",
"Nothing",
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_DUNGEON_ROOM_OBJECT_H

1414
src/zelda3/music/tracker.cc Normal file

File diff suppressed because it is too large Load Diff

283
src/zelda3/music/tracker.h Normal file
View File

@@ -0,0 +1,283 @@
#ifndef YAZE_APP_ZELDA3_TRACKER_H
#define YAZE_APP_ZELDA3_TRACKER_H
#include <vector>
#include "app/rom.h"
#include "util/macro.h"
namespace yaze {
namespace zelda3 {
/**
* @namespace yaze::zelda3::music
* @brief Contains classes and functions for handling music data in Zelda 3.
*
* Based off of the Hyrule Magic tracker code, this system is designed to parse
* the game's complex, pointer-based music format into an editable in-memory
* representation and then serialize it back into a binary format that the
* SNES Audio Processing Unit (APU) can understand.
*/
namespace music {
// Length of each SPC700 command opcode (0xE0-0xFF). Used for parsing.
constexpr char op_len[32] = {1, 1, 2, 3, 0, 1, 2, 1, 2, 1, 1, 3, 0, 1, 2, 3,
1, 3, 3, 0, 1, 3, 0, 3, 3, 3, 1, 2, 0, 0, 0, 0};
// Default ROM offsets for specific sound banks.
static int sbank_ofs[] = {0xc8000, 0, 0xd8000, 0};
// Filter coefficients for BRR sample decoding.
constexpr char fil1[4] = {0, 15, 61, 115};
constexpr char fil2[4] = {0, 4, 5, 6};
constexpr char fil3[4] = {0, 0, 15, 13};
constexpr int kOverworldMusicBank = 0x0D0000;
constexpr int kDungeonMusicBank = 0x0D8000;
using text_buf_ty = char[512];
/**
* @struct SongSpcBlock
* @brief Represents a block of binary data destined for the APU (SPC700) RAM.
* 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.
};
/**
* @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.
*/
struct SongRange {
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.
unsigned char endtime; // Default time/duration for this block.
unsigned char filler;
int editor; // Window handle for an associated editor, if any.
};
/**
* @struct SongPart
* @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.
};
/**
* @struct Song
* @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.
};
/**
* @struct ZeldaWave
* @brief Represents a decoded instrument sample (a waveform).
*/
struct ZeldaWave {
int lopst; // Loop start point within the sample data.
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.
};
/**
* @struct SampleEdit
* @brief A state structure for a GUI sample editor.
*/
struct SampleEdit {
unsigned short flag;
unsigned short init;
unsigned short editsamp;
int width;
int height;
int pageh;
int pagev;
int zoom;
int scroll;
int page;
/// Left hand sample selection point
int sell;
/// Right hand sample selection point
int selr;
int editinst;
ZeldaWave *zw;
};
/**
* @struct ZeldaInstrument
* @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).
};
/**
* @struct ZeldaSfxInstrument
* @brief Defines an instrument for a sound effect.
*/
struct ZeldaSfxInstrument {
unsigned char voll;
unsigned char volr;
short freq;
unsigned char samp;
unsigned char ad;
unsigned char sr;
unsigned char gain;
unsigned char multhi;
};
/**
* @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.
*/
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.
};
class Tracker {
public:
SongSpcBlock *AllocSpcBlock(int len, int bank);
unsigned char *GetSpcAddr(Rom &rom, unsigned short addr, short bank);
short AllocSpcCommand();
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);
void SaveSongs(Rom &rom);
void LoadSongs(Rom &rom);
int WriteSpcData(Rom &rom, void *buf, int len, int addr, int spc, int limit);
void EditTrack(Rom &rom, short i);
void NewSR(Rom &rom, int bank);
private:
// A "modified" flag
int modf;
int mark_sr;
int mark_start;
int mark_end;
int mark_first;
int mark_last;
int numwave;
int numinst;
int numsndinst;
int sndinit = 0;
int sndlen1;
int sndlen2;
int m_ofs;
int w_modf;
int ss_num;
int ss_size;
char op_len[32];
char *snddat1;
char *snddat2; // more music stuff.
unsigned short ss_next = 0;
unsigned short spclen;
unsigned short numseg;
short spcbank;
short lastsr;
short ss_lasttime;
short srnum;
short srsize;
short numsong[3]; // ditto
short m_size;
short m_free;
short m_modf; // ???
short m_loaded;
short t_loaded;
short t_modf;
short withhead;
size_t t_number;
std::vector<Song> songs;
SongPart *sp_mark;
SongRange *song_range_;
SpcCommand *current_spc_command_;
const SpcCommand& GetSpcCommand(short index) const {
return current_spc_command_[index];
}
SongSpcBlock **ssblt;
ZeldaWave *waves;
ZeldaInstrument *insts;
ZeldaSfxInstrument *sndinsts;
};
} // namespace music
} // namespace zelda3
} // namespace yaze
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,372 @@
#ifndef YAZE_APP_DATA_OVERWORLD_H
#define YAZE_APP_DATA_OVERWORLD_H
#include <array>
#include <cstdint>
#include <vector>
#include <mutex>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
#include "imgui.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/sprite/sprite.h"
namespace yaze::zelda3 {
constexpr int GravesYTilePos = 0x49968; // short (0x0F entries)
constexpr int GravesXTilePos = 0x49986; // short (0x0F entries)
constexpr int GravesTilemapPos = 0x499A4; // short (0x0F entries)
constexpr int GravesGFX = 0x499C2; // short (0x0F entries)
constexpr int GravesXPos = 0x4994A; // short (0x0F entries)
constexpr int GravesYLine = 0x4993A; // short (0x08 entries)
constexpr int GravesCountOnY = 0x499E0; // Byte 0x09 entries
constexpr int GraveLinkSpecialHole = 0x46DD9; // short
constexpr int GraveLinkSpecialStairs = 0x46DE0; // short
constexpr int kOverworldMapPaletteIds = 0x7D1C;
constexpr int kOverworldSpritePaletteIds = 0x7B41;
constexpr int kOverworldSpritePaletteGroup = 0x75580;
constexpr int kOverworldSpriteset = 0x7A41;
constexpr int kOverworldSpecialGfxGroup = 0x16821;
constexpr int kOverworldSpecialPalGroup = 0x16831;
constexpr int kOverworldSpritesBeginning = 0x4C881;
constexpr int kOverworldSpritesAgahnim = 0x4CA21;
constexpr int kOverworldSpritesZelda = 0x4C901;
constexpr int kAreaGfxIdPtr = 0x7C9C;
constexpr int kOverworldMessageIds = 0x3F51D;
constexpr int kOverworldMusicBeginning = 0x14303;
constexpr int kOverworldMusicZelda = 0x14303 + 0x40;
constexpr int kOverworldMusicMasterSword = 0x14303 + 0x80;
constexpr int kOverworldMusicAgahnim = 0x14303 + 0xC0;
constexpr int kOverworldMusicDarkWorld = 0x14403;
constexpr int kOverworldEntranceAllowedTilesLeft = 0xDB8C1;
constexpr int kOverworldEntranceAllowedTilesRight = 0xDB917;
// 0x00 = small maps, 0x20 = large maps
constexpr int kOverworldMapSize = 0x12844;
// 0x01 = small maps, 0x03 = large maps
constexpr int kOverworldMapSizeHighByte = 0x12884;
// relative to the WORLD + 0x200 per map
// large map that are not == parent id = same position as their parent!
// eg for X position small maps :
// 0000, 0200, 0400, 0600, 0800, 0A00, 0C00, 0E00
// all Large map would be :
// 0000, 0000, 0400, 0400, 0800, 0800, 0C00, 0C00
constexpr int kOverworldMapParentId = 0x125EC;
constexpr int kOverworldTransitionPositionY = 0x128C4;
constexpr int kOverworldTransitionPositionX = 0x12944;
constexpr int kOverworldScreenSize = 0x1788D;
constexpr int kOverworldScreenSizeForLoading = 0x4C635;
constexpr int kOverworldScreenTileMapChangeByScreen1 = 0x12634;
constexpr int kOverworldScreenTileMapChangeByScreen2 = 0x126B4;
constexpr int kOverworldScreenTileMapChangeByScreen3 = 0x12734;
constexpr int kOverworldScreenTileMapChangeByScreen4 = 0x127B4;
constexpr int kOverworldMapDataOverflow = 0x130000;
constexpr int kTransitionTargetNorth = 0x13EE2;
constexpr int kTransitionTargetWest = 0x13F62;
constexpr int overworldCustomMosaicASM = 0x1301D0;
constexpr int overworldCustomMosaicArray = 0x1301F0;
// Expanded tile16 and tile32
constexpr int kMap16TilesExpanded = 0x1E8000;
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 overworldSpritesBeginingExpanded = 0x141438;
constexpr int overworldSpritesZeldaExpanded = 0x141578;
constexpr int overworldSpritesAgahnimExpanded = 0x1416B8;
constexpr int overworldSpritesDataStartExpanded = 0x04C881;
constexpr int overworldSpecialSpriteGFXGroupExpandedTemp = 0x0166E1;
constexpr int overworldSpecialSpritePaletteExpandedTemp = 0x016701;
constexpr int ExpandedOverlaySpace = 0x120000;
constexpr int overworldTilesType = 0x071459;
constexpr int overworldMessages = 0x03F51D;
constexpr int overworldMessagesExpanded = 0x1417F8;
constexpr int kOverworldCompressedMapPos = 0x058000;
constexpr int kOverworldCompressedOverflowPos = 0x137FFF;
constexpr int kNumTileTypes = 0x200;
constexpr int kMap16Tiles = 0x78000;
constexpr int kNumTile16Individual = 4096;
constexpr int Map32PerScreen = 256;
constexpr int NumberOfMap16 = 3752; // 4096
constexpr int NumberOfMap16Ex = 4096; // 4096
constexpr int LimitOfMap32 = 8864;
constexpr int NumberOfOWSprites = 352;
constexpr int NumberOfMap32 = Map32PerScreen * kNumOverworldMaps;
constexpr int kNumMapsPerWorld = 0x40;
/**
* @brief Represents the full Overworld data, light and dark world.
*
* This class is responsible for loading and saving the overworld data,
* as well as creating the tilesets and tilemaps for the overworld.
*/
class Overworld {
public:
Overworld(Rom *rom) : 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 SaveOverworldMaps();
absl::Status SaveLargeMaps();
absl::Status SaveLargeMapsExpanded();
absl::Status SaveSmallAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveLargeAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveWideAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveTallAreaTransitions(int i, int parent_x_pos, int parent_y_pos,
int transition_target_north, int transition_target_west,
int transition_pos_x, int transition_pos_y,
int screen_change_1, int screen_change_2,
int screen_change_3, int screen_change_4);
absl::Status SaveEntrances();
absl::Status SaveExits();
absl::Status SaveItems();
absl::Status SaveMapOverlays();
absl::Status SaveOverworldTilesType();
absl::Status SaveCustomOverworldASM(bool enable_bg_color, bool enable_main_palette,
bool enable_mosaic, bool enable_gfx_groups,
bool enable_subscreen_overlay, bool enable_animated);
absl::Status SaveAreaSpecificBGColors();
absl::Status CreateTile32Tilemap();
absl::Status SaveMap16Expanded();
absl::Status SaveMap16Tiles();
absl::Status SaveMap32Expanded();
absl::Status SaveMap32Tiles();
absl::Status SaveMapProperties();
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);
auto rom() const { return rom_; }
auto mutable_rom() { return rom_; }
void Destroy() {
for (auto &map : overworld_maps_) {
map.Destroy();
}
overworld_maps_.clear();
all_entrances_.clear();
all_exits_.clear();
all_items_.clear();
for (auto &sprites : all_sprites_) {
sprites.clear();
}
tiles16_.clear();
tiles32_.clear();
tiles32_unique_.clear();
is_loaded_ = false;
}
int GetTileFromPosition(ImVec2 position) const {
if (current_world_ == 0) {
return map_tiles_.light_world[position.x][position.y];
} else if (current_world_ == 1) {
return map_tiles_.dark_world[position.x][position.y];
} else {
return map_tiles_.special_world[position.x][position.y];
}
}
OverworldBlockset &GetMapTiles(int world_type) {
switch (world_type) {
case 0:
return map_tiles_.light_world;
case 1:
return map_tiles_.dark_world;
case 2:
return map_tiles_.special_world;
default:
return map_tiles_.light_world;
}
}
auto overworld_maps() const { return overworld_maps_; }
auto overworld_map(int i) const { return &overworld_maps_[i]; }
auto mutable_overworld_map(int i) { return &overworld_maps_[i]; }
auto exits() const { return &all_exits_; }
auto mutable_exits() { return &all_exits_; }
std::vector<gfx::Tile16> tiles16() const { return tiles16_; }
auto tiles32_unique() const { return tiles32_unique_; }
auto mutable_tiles16() { return &tiles16_; }
auto sprites(int state) const { return all_sprites_[state]; }
auto mutable_sprites(int state) { return &all_sprites_[state]; }
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_; }
auto mutable_entrances() { return &all_entrances_; }
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_; }
auto current_area_palette() const {
return overworld_maps_[current_map_].current_palette();
}
auto current_map_bitmap_data() const {
return overworld_maps_[current_map_].bitmap_data();
}
auto tile16_blockset_data() const {
return overworld_maps_[current_map_].current_tile16_blockset();
}
auto is_loaded() const { return is_loaded_; }
auto expanded_tile16() const { return expanded_tile16_; }
auto expanded_tile32() const { return expanded_tile32_; }
auto expanded_entrances() const { return expanded_entrances_; }
void set_current_map(int i) { current_map_ = i; }
void set_current_world(int world) { current_world_ = world; }
uint16_t GetTile(int x, int y) const {
if (current_world_ == 0) {
return map_tiles_.light_world[y][x];
} else if (current_world_ == 1) {
return map_tiles_.dark_world[y][x];
} else {
return map_tiles_.special_world[y][x];
}
}
void SetTile(int x, int y, uint16_t tile_id) {
if (current_world_ == 0) {
map_tiles_.light_world[y][x] = tile_id;
} else if (current_world_ == 1) {
map_tiles_.dark_world[y][x] = tile_id;
} else {
map_tiles_.special_world[y][x] = tile_id;
}
}
auto map_tiles() const { return map_tiles_; }
auto mutable_map_tiles() { return &map_tiles_; }
auto all_items() const { return all_items_; }
auto mutable_all_items() { return &all_items_; }
auto all_tiles_types() const { return all_tiles_types_; }
auto mutable_all_tiles_types() { return &all_tiles_types_; }
auto all_sprites() const { return all_sprites_; }
private:
enum Dimension {
map32TilesTL = 0,
map32TilesTR = 1,
map32TilesBL = 2,
map32TilesBR = 3
};
void FetchLargeMaps();
absl::StatusOr<uint16_t> GetTile16ForTile32(int index, int quadrant,
int dimension,
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();
absl::Status DecompressAllMapTilesParallel();
Rom *rom_;
bool is_loaded_ = false;
bool expanded_tile16_ = false;
bool expanded_tile32_ = false;
bool expanded_entrances_ = false;
int game_state_ = 0;
int current_map_ = 0;
int current_world_ = 0;
OverworldMapTiles map_tiles_;
// Thread safety for parallel operations
mutable std::mutex map_tiles_mutex_;
std::vector<OverworldMap> overworld_maps_;
std::vector<OverworldEntrance> all_entrances_;
std::vector<OverworldEntrance> all_holes_;
std::vector<OverworldExit> all_exits_;
std::vector<OverworldItem> all_items_;
std::vector<gfx::Tile16> tiles16_;
std::vector<gfx::Tile32> tiles32_;
std::vector<gfx::Tile32> tiles32_unique_;
std::vector<uint16_t> tiles32_list_;
std::vector<uint64_t> deleted_entrances_;
std::array<uint8_t, kNumOverworldMaps> map_parent_ = {0};
std::array<uint8_t, kNumTileTypes> all_tiles_types_ = {0};
std::array<std::vector<Sprite>, 3> all_sprites_;
std::array<std::vector<uint8_t>, kNumOverworldMaps> map_data_p1;
std::array<std::vector<uint8_t>, kNumOverworldMaps> map_data_p2;
std::array<int, kNumOverworldMaps> map_pointers1_id;
std::array<int, kNumOverworldMaps> map_pointers2_id;
std::array<int, kNumOverworldMaps> map_pointers1;
std::array<int, kNumOverworldMaps> map_pointers2;
};
} // namespace yaze::zelda3
#endif

View File

@@ -0,0 +1,122 @@
#include "zelda3/overworld/overworld_entrance.h"
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
#include "util/macro.h"
namespace yaze::zelda3 {
absl::StatusOr<std::vector<OverworldEntrance>> LoadEntrances(Rom* rom) {
std::vector<OverworldEntrance> entrances;
int ow_entrance_map_ptr = kOverworldEntranceMap;
int ow_entrance_pos_ptr = kOverworldEntrancePos;
int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
int num_entrances = 129;
// Check if expanded entrance data is actually present in ROM
// The flag position should contain 0xB8 for vanilla, something else for expanded
if (rom->data()[kOverworldEntranceExpandedFlagPos] != 0xB8) {
// ROM has expanded entrance data - use expanded addresses
ow_entrance_map_ptr = kOverworldEntranceMapExpanded;
ow_entrance_pos_ptr = kOverworldEntrancePosExpanded;
ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded;
num_entrances = 256; // Expanded entrance count
}
// Otherwise use vanilla addresses (already set above)
for (int i = 0; i < num_entrances; i++) {
ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(ow_entrance_map_ptr + (i * 2)));
ASSIGN_OR_RETURN(auto map_pos,
rom->ReadWord(ow_entrance_pos_ptr + (i * 2)));
ASSIGN_OR_RETURN(auto entrance_id, rom->ReadByte(ow_entrance_id_ptr + i));
int p = map_pos >> 1;
int x = (p % 64);
int y = (p >> 6);
bool deleted = false;
if (map_pos == 0xFFFF) {
deleted = true;
}
entrances.emplace_back(
(x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512),
(y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id, map_pos,
deleted);
}
return entrances;
}
absl::StatusOr<std::vector<OverworldEntrance>> LoadHoles(Rom* rom) {
constexpr int kNumHoles = 0x13;
std::vector<OverworldEntrance> holes;
for (int i = 0; i < kNumHoles; i++) {
ASSIGN_OR_RETURN(auto map_id, rom->ReadWord(kOverworldHoleArea + (i * 2)));
ASSIGN_OR_RETURN(auto map_pos, rom->ReadWord(kOverworldHolePos + (i * 2)));
ASSIGN_OR_RETURN(auto entrance_id,
rom->ReadByte(kOverworldHoleEntrance + i));
int p = (map_pos + 0x400) >> 1;
int x = (p % 64);
int y = (p >> 6);
holes.emplace_back(
(x * 16) + (((map_id % 64) - (((map_id % 64) / 8) * 8)) * 512),
(y * 16) + (((map_id % 64) / 8) * 512), entrance_id, map_id,
(uint16_t)(map_pos + 0x400), true);
}
return holes;
}
absl::Status SaveEntrances(Rom* rom,
const std::vector<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.
RETURN_IF_ERROR(rom->WriteShort(map_addr, entrances[index].map_id_));
RETURN_IF_ERROR(rom->WriteShort(pos_addr, entrances[index].map_pos_));
RETURN_IF_ERROR(rom->WriteByte(id_addr, entrances[index].entrance_id_));
return absl::OkStatus();
};
// Always keep the legacy tables in sync for pure vanilla ROMs so e.g. Hyrule
// Magic expects them. ZScream does the same in SaveOWEntrances.
for (int i = 0; i < kNumOverworldEntrances; ++i) {
RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMap + (i * 2),
kOverworldEntrancePos + (i * 2),
kOverworldEntranceEntranceId + i));
}
if (expanded_entrances) {
// For ZS v3+ ROMs, mirror writes into the expanded tables the way
// ZeldaFullEditor does when the ASM patch is active.
for (int i = 0; i < kNumOverworldEntrances; ++i) {
RETURN_IF_ERROR(write_entrance(i, kOverworldEntranceMapExpanded + (i * 2),
kOverworldEntrancePosExpanded + (i * 2),
kOverworldEntranceEntranceIdExpanded + i));
}
}
return absl::OkStatus();
}
absl::Status SaveHoles(Rom* rom, const std::vector<OverworldEntrance>& holes) {
for (int i = 0; i < kNumOverworldHoles; ++i) {
RETURN_IF_ERROR(
rom->WriteShort(kOverworldHoleArea + (i * 2), holes[i].map_id_));
// ZeldaFullEditor/Data/Overworld.cs::LoadHoles() adds 0x400 when loading
// (see lines ~1006-1014). SaveOWEntrances subtracts it before writing
// (Save.cs lines ~1088-1092). We replicate that here so vanilla ROMs
// receive the expected values.
uint16_t rom_map_pos = static_cast<uint16_t>(holes[i].map_pos_ >= 0x400
? holes[i].map_pos_ - 0x400
: holes[i].map_pos_);
RETURN_IF_ERROR(rom->WriteShort(kOverworldHolePos + (i * 2), rom_map_pos));
RETURN_IF_ERROR(
rom->WriteByte(kOverworldHoleEntrance + i, holes[i].entrance_id_));
}
return absl::OkStatus();
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,158 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_ENTRANCE_H
#define YAZE_APP_ZELDA3_OVERWORLD_ENTRANCE_H
#include <array>
#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"
namespace yaze::zelda3 {
// EXPANDED to 0x78000 to 0x7A000
constexpr int kEntranceRoomEXP = 0x078000;
constexpr int kEntranceScrollEdgeEXP = 0x078200;
constexpr int kEntranceCameraYEXP = 0x078A00;
constexpr int kEntranceCameraXEXP = 0x078C00;
constexpr int kEntranceYPositionEXP = 0x078E00;
constexpr int kEntranceXPositionEXP = 0x079000;
constexpr int kEntranceCameraYTriggerEXP = 0x079200;
constexpr int kEntranceCameraXTriggerEXP = 0x079400;
constexpr int kEntranceBlocksetEXP = 0x079600;
constexpr int kEntranceFloorEXP = 0x079700;
constexpr int kEntranceDungeonEXP = 0x079800;
constexpr int kEntranceDoorEXP = 0x079900;
constexpr int kEntranceLadderBgEXP = 0x079A00;
constexpr int kEntranceScrollingEXP = 0x079B00;
constexpr int kEntranceScrollQuadrantEXP = 0x079C00;
constexpr int kEntranceExitEXP = 0x079D00;
constexpr int kEntranceMusicEXP = 0x079F00;
constexpr int kEntranceExtraEXP = 0x07A000;
constexpr int kEntranceTotalEXP = 0xFF;
constexpr int kEntranceTotal = 0x84;
constexpr int kEntranceLinkSpawn = 0x00;
constexpr int kEntranceNorthTavern = 0x43;
constexpr int kEntranceEXP = 0x07F000;
constexpr int kEntranceCameraY = 0x014D45; // 0x14AA9 // 2bytes each room
constexpr int kEntranceCameraX = 0x014E4F; // 0x14BB3 // 2bytes
constexpr int kNumOverworldEntrances = 129;
constexpr int kNumOverworldHoles = 0x13;
constexpr int kOverworldEntranceMap = 0xDB96F;
constexpr int kOverworldEntrancePos = 0xDBA71;
constexpr int kOverworldEntranceEntranceId = 0xDBB73;
constexpr int kOverworldEntranceMapExpanded = 0x0DB55F;
constexpr int kOverworldEntrancePosExpanded = 0x0DB35F;
constexpr int kOverworldEntranceEntranceIdExpanded = 0x0DB75F;
constexpr int kOverworldEntranceExpandedFlagPos = 0x0DB895; // 0xB8
// (0x13 entries, 2 bytes each) modified(less 0x400)
// map16 coordinates for each hole
constexpr int kOverworldHolePos = 0xDB800;
// (0x13 entries, 2 bytes each) corresponding
// area numbers for each hole
constexpr int kOverworldHoleArea = 0xDB826;
//(0x13 entries, 1 byte each) corresponding entrance numbers
constexpr int kOverworldHoleEntrance = 0xDB84C;
// OWEntrances Expansion
// Instructions for editors
// if byte at (PC) address 0xDB895 == B8 then it is vanilla
// Load normal overworld entrances data
// Otherwise load from the expanded space
// When saving just save in expanded space 256 values for each
// (PC Addresses) - you can find snes address at the orgs below
// 0x0DB35F = (short) Map16 tile address (mapPos in ZS)
// 0x0DB55F = (short) Screen ID (MapID in ZS)
// 0x0DB75F = (byte) Entrance leading to (EntranceID in ZS)
// *Important* the Screen ID now also require bit 0x8000 (15) being set to tell
// entrance is a hole
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;
OverworldEntrance() = default;
OverworldEntrance(int x, int y, uint8_t entrance_id, short map_id,
uint16_t map_pos, bool hole)
: map_pos_(map_pos), entrance_id_(entrance_id), is_hole_(hole) {
x_ = x;
y_ = y;
map_id_ = map_id;
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));
}
void UpdateMapProperties(uint16_t map_id) override {
map_id_ = map_id;
if (map_id_ >= 64) {
map_id_ -= 64;
}
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));
map_pos_ = (uint16_t)((((area_y_) << 6) | (area_x_ & 0x3F)) << 1);
}
};
constexpr int kEntranceTileTypePtrLow = 0xDB8BF;
constexpr int kEntranceTileTypePtrHigh = 0xDB917;
constexpr int kNumEntranceTileTypes = 0x2C;
struct OverworldEntranceTileTypes {
std::array<uint16_t, kNumEntranceTileTypes> low;
std::array<uint16_t, kNumEntranceTileTypes> high;
};
absl::StatusOr<std::vector<OverworldEntrance>> LoadEntrances(Rom* rom);
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);
inline absl::StatusOr<OverworldEntranceTileTypes> LoadEntranceTileTypes(
Rom* rom) {
OverworldEntranceTileTypes tiletypes;
for (int i = 0; i < kNumEntranceTileTypes; i++) {
ASSIGN_OR_RETURN(auto value_low,
rom->ReadWord(kEntranceTileTypePtrLow + i));
tiletypes.low[i] = value_low;
ASSIGN_OR_RETURN(auto value_high,
rom->ReadWord(kEntranceTileTypePtrHigh + i));
tiletypes.high[i] = value_high;
}
return tiletypes;
}
} // namespace yaze::zelda3
#endif

View File

@@ -0,0 +1,113 @@
#include "zelda3/overworld/overworld_exit.h"
#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>
namespace yaze::zelda3 {
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();
uint16_t exit_room_id;
uint16_t exit_map_id;
uint16_t exit_vram;
uint16_t exit_y_scroll;
uint16_t exit_x_scroll;
uint16_t exit_y_player;
uint16_t exit_x_player;
uint16_t exit_y_camera;
uint16_t exit_x_camera;
uint16_t exit_scroll_mod_y;
uint16_t exit_scroll_mod_x;
uint16_t exit_door_type_1;
uint16_t exit_door_type_2;
RETURN_IF_ERROR(rom->ReadTransaction(
exit_room_id, (OWExitRoomId + (i * 2)), exit_map_id, OWExitMapId + i,
exit_vram, OWExitVram + (i * 2), exit_y_scroll, OWExitYScroll + (i * 2),
exit_x_scroll, OWExitXScroll + (i * 2), exit_y_player,
OWExitYPlayer + (i * 2), exit_x_player, OWExitXPlayer + (i * 2),
exit_y_camera, OWExitYCamera + (i * 2), exit_x_camera,
OWExitXCamera + (i * 2), exit_scroll_mod_y, OWExitUnk1 + i,
exit_scroll_mod_x, OWExitUnk2 + i, exit_door_type_1,
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)]);
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);
}
return exits;
}
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.
uint8_t asm_version = (*rom)[OverworldCustomASMHasBeenApplied];
if (asm_version == 0x00) {
// Apply special fix for Zora's Domain exit (index 0x4D)
// TODO: Implement SpecialUpdatePosition for OverworldExit
// if (all_exits_.size() > 0x4D) {
// all_exits_[0x4D].SpecialUpdatePosition();
// }
}
for (int i = 0; i < kNumOverworldExits; i++) {
RETURN_IF_ERROR(
rom->WriteShort(OWExitRoomId + (i * 2), 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(OWExitYScroll + (i * 2), exits[i].y_scroll_));
RETURN_IF_ERROR(
rom->WriteShort(OWExitXScroll + (i * 2), exits[i].x_scroll_));
RETURN_IF_ERROR(
rom->WriteShort(OWExitYPlayer + (i * 2), exits[i].y_player_));
RETURN_IF_ERROR(
rom->WriteShort(OWExitXPlayer + (i * 2), exits[i].x_player_));
RETURN_IF_ERROR(
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->WriteShort(OWExitDoorType1 + (i * 2),
exits[i].door_type_1_));
RETURN_IF_ERROR(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));
} else if (exits[i].room_id_ == 0x0181) {
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 absl::OkStatus();
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,216 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_EXIT_H
#define YAZE_APP_ZELDA3_OVERWORLD_EXIT_H
#include <cstdint>
#include <iostream>
#include "app/rom.h"
#include "zelda3/common.h"
namespace yaze::zelda3 {
constexpr int kNumOverworldExits = 0x4F;
constexpr int OWExitRoomId = 0x15D8A; // 0x15E07 Credits sequences
// 105C2 Ending maps
// 105E2 Sprite Group Table for Ending
constexpr int OWExitMapId = 0x15E28;
constexpr int OWExitVram = 0x15E77;
constexpr int OWExitYScroll = 0x15F15;
constexpr int OWExitXScroll = 0x15FB3;
constexpr int OWExitYPlayer = 0x16051;
constexpr int OWExitXPlayer = 0x160EF;
constexpr int OWExitYCamera = 0x1618D;
constexpr int OWExitXCamera = 0x1622B;
constexpr int OWExitDoorPosition = 0x15724;
constexpr int OWExitUnk1 = 0x162C9;
constexpr int OWExitUnk2 = 0x16318;
constexpr int OWExitDoorType1 = 0x16367;
constexpr int OWExitDoorType2 = 0x16405;
constexpr int OWExitMapIdWhirlpool = 0x16AE5; // JP = ;016849
constexpr int OWExitVramWhirlpool = 0x16B07; // JP = ;01686B
constexpr int OWExitYScrollWhirlpool = 0x16B29; // JP = ;01688D
constexpr int OWExitXScrollWhirlpool = 0x16B4B; // JP = ;016DE7
constexpr int OWExitYPlayerWhirlpool = 0x16B6D; // JP = ;016E09
constexpr int OWExitXPlayerWhirlpool = 0x16B8F; // JP = ;016E2B
constexpr int OWExitYCameraWhirlpool = 0x16BB1; // JP = ;016E4D
constexpr int OWExitXCameraWhirlpool = 0x16BD3; // JP = ;016E6F
constexpr int OWExitUnk1Whirlpool = 0x16BF5; // JP = ;016E91
constexpr int OWExitUnk2Whirlpool = 0x16C17; // JP = ;016EB3
constexpr int OWWhirlpoolPosition = 0x16CF8; // JP = ;016F94
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
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_;
bool is_hole_ = false;
bool deleted_ = false;
bool is_automatic_ = false;
bool large_map_ = false;
OverworldExit() = default;
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),
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
scroll_mod_y_(scroll_mod_y),
scroll_mod_x_(scroll_mod_x),
door_type_1_(door_type_1),
door_type_2_(door_type_2),
is_hole_(false),
deleted_(deleted) {
// Initialize entity variables with full 16-bit coordinates
x_ = player_x;
y_ = player_y;
map_id_ = map_id;
entity_type_ = kExit;
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 (door_type_1 != 0) {
int p = (door_type_1 & 0x7FFF) >> 1;
entrance_id_ = (uint8_t)(p % 64);
area_y_ = (uint8_t)(p >> 6);
}
if (door_type_2 != 0) {
int p = (door_type_2 & 0x7FFF) >> 1;
entrance_id_ = (uint8_t)(p % 64);
area_y_ = (uint8_t)(p >> 6);
}
if (map_id_ >= 64) {
map_id_ -= 64;
}
mapX = (map_id_ - ((map_id_ / 8) * 8));
mapY = (map_id_ / 8);
area_x_ = (uint8_t)((std::abs(x_ - (mapX * 512)) / 16));
area_y_ = (uint8_t)((std::abs(y_ - (mapY * 512)) / 16));
map_pos_ = (uint16_t)((((area_y_) << 6) | (area_x_ & 0x3F)) << 1);
}
// Overworld overworld
void UpdateMapProperties(uint16_t map_id) override {
// CRITICAL FIX: Sync player position from base entity coordinates
// The drag system in overworld_editor.cc updates x_/y_ (base GameEntity fields),
// but exit auto-calculation uses x_player_/y_player_ for scroll/camera computation.
// Without this sync, dragged exits retain old scroll values, causing save corruption.
// Matches ZScream ExitMode.cs:229-244 where PlayerX/PlayerY are updated during drag,
// then UpdateMapStuff recalculates scroll/camera from those values.
x_player_ = static_cast<uint16_t>(x_);
y_player_ = static_cast<uint16_t>(y_);
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));
}
};
absl::StatusOr<std::vector<OverworldExit>> LoadExits(Rom* rom);
absl::Status SaveExits(Rom* rom, const std::vector<OverworldExit>& exits);
} // namespace yaze::zelda3
#endif // YAZE_APP_ZELDA3_OVERWORLD_EXIT_H_

View File

@@ -0,0 +1,208 @@
#include "zelda3/overworld/overworld_item.h"
#include <algorithm>
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
#include "app/snes.h"
#include "util/log.h"
#include "util/macro.h"
#include "zelda3/common.h"
#include "zelda3/overworld/overworld_map.h"
namespace yaze::zelda3 {
absl::StatusOr<std::vector<OverworldItem>> LoadItems(
Rom* rom, std::vector<OverworldMap>& overworld_maps) {
std::vector<OverworldItem> items;
// Version 0x03 of the OW ASM added item support for the SW.
uint8_t asm_version = (*rom)[OverworldCustomASMHasBeenApplied];
// Determine max number of overworld maps based on actual ASM version
// Only use expanded maps (0xA0) if v3+ ASM is actually applied
int max_ow =
(asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80;
ASSIGN_OR_RETURN(uint32_t pointer_snes,
rom->ReadLong(zelda3::overworldItemsAddress));
uint32_t item_pointer_address =
SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9
for (int i = 0; i < max_ow; i++) {
ASSIGN_OR_RETURN(uint8_t bank_byte,
rom->ReadByte(zelda3::overworldItemsAddressBank));
int bank = bank_byte & 0x7F;
ASSIGN_OR_RETURN(uint8_t addr_low,
rom->ReadByte(item_pointer_address + (i * 2)));
ASSIGN_OR_RETURN(uint8_t addr_high,
rom->ReadByte(item_pointer_address + (i * 2) + 1));
uint32_t addr = (bank << 16) + // 1B
(addr_high << 8) + // F9
addr_low; // 3C
addr = SnesToPc(addr);
// Check if this is a large map and skip if not the parent
if (overworld_maps[i].area_size() != zelda3::AreaSizeEnum::SmallArea) {
if (overworld_maps[i].parent() != (uint8_t)i) {
continue;
}
}
while (true) {
ASSIGN_OR_RETURN(uint8_t b1, rom->ReadByte(addr));
ASSIGN_OR_RETURN(uint8_t b2, rom->ReadByte(addr + 1));
ASSIGN_OR_RETURN(uint8_t b3, rom->ReadByte(addr + 2));
if (b1 == 0xFF && b2 == 0xFF) {
break;
}
int p = (((b2 & 0x1F) << 8) + b1) >> 1;
int x = p % 0x40; // Use 0x40 instead of 64 to match ZS
int y = p >> 6;
int fakeID = i % 0x40; // Use modulo 0x40 to match ZS
int sy = fakeID / 8;
int sx = fakeID - (sy * 8);
items.emplace_back(b3, (uint16_t)i, (x * 16) + (sx * 512),
(y * 16) + (sy * 512), false);
auto size = items.size();
items[size - 1].game_x_ = (uint8_t)x;
items[size - 1].game_y_ = (uint8_t)y;
addr += 3;
}
}
return items;
}
absl::Status SaveItems(Rom* rom, const std::vector<OverworldItem>& items) {
const int pointer_count = zelda3::kNumOverworldMaps;
std::vector<std::vector<OverworldItem>> room_items(pointer_count);
// Reset bomb door lookup table used by special item (0x86)
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
RETURN_IF_ERROR(rom->WriteShort(
zelda3::kOverworldBombDoorItemLocationsNew + (i * 2), 0x0000));
}
for (const OverworldItem& item : items) {
if (item.deleted)
continue;
const int map_index = static_cast<int>(item.room_map_id_);
if (map_index < 0 || map_index >= pointer_count) {
LOG_WARN(
"Overworld::SaveItems",
"Skipping item with map index %d outside pointer table (size=%d)",
map_index, pointer_count);
continue;
}
room_items[map_index].push_back(item);
if (item.id_ == 0x86) {
const int lookup_index =
std::min(map_index, zelda3::kNumOverworldMaps - 1);
RETURN_IF_ERROR(rom->WriteShort(
zelda3::kOverworldBombDoorItemLocationsNew + (lookup_index * 2),
static_cast<uint16_t>((item.game_x_ + (item.game_y_ * 64)) * 2)));
}
}
// Prepare pointer reuse cache
std::vector<int> item_pointers(pointer_count, -1);
std::vector<int> item_pointers_reuse(pointer_count, -1);
for (int i = 0; i < pointer_count; ++i) {
item_pointers_reuse[i] = -1;
for (int ci = 0; ci < i; ++ci) {
if (room_items[i].empty()) {
item_pointers_reuse[i] = -2; // reuse empty terminator
break;
}
if (CompareItemsArrays(room_items[i], room_items[ci])) {
item_pointers_reuse[i] = ci;
break;
}
}
}
// Item data always lives in the vanilla data block
int data_pos = zelda3::kOverworldItemsStartDataNew;
int empty_pointer = -1;
for (int i = 0; i < pointer_count; ++i) {
if (item_pointers_reuse[i] == -1) {
item_pointers[i] = data_pos;
for (const OverworldItem& item : room_items[i]) {
const uint16_t map_pos =
static_cast<uint16_t>(((item.game_y_ << 6) + item.game_x_) << 1);
const uint32_t data =
static_cast<uint32_t>(map_pos & 0xFF) |
(static_cast<uint32_t>((map_pos >> 8) & 0xFF) << 8) |
(static_cast<uint32_t>(item.id_) << 16);
RETURN_IF_ERROR(rom->WriteLong(data_pos, data));
data_pos += 3;
}
empty_pointer = data_pos;
RETURN_IF_ERROR(rom->WriteShort(data_pos, 0xFFFF));
data_pos += 2;
} else if (item_pointers_reuse[i] == -2) {
if (empty_pointer < 0) {
item_pointers[i] = data_pos;
empty_pointer = data_pos;
RETURN_IF_ERROR(rom->WriteShort(data_pos, 0xFFFF));
data_pos += 2;
} else {
item_pointers[i] = empty_pointer;
}
} else {
item_pointers[i] = item_pointers[item_pointers_reuse[i]];
}
}
if (data_pos > kOverworldItemsEndData) {
return absl::AbortedError("Too many items");
}
// Update pointer table metadata to the expanded location used by ZScream
RETURN_IF_ERROR(rom->WriteLong(zelda3::overworldItemsAddress,
PcToSnes(zelda3::kOverworldItemsPointersNew)));
RETURN_IF_ERROR(rom->WriteByte(
zelda3::overworldItemsAddressBank,
static_cast<uint8_t>(
(PcToSnes(zelda3::kOverworldItemsStartDataNew) >> 16) & 0xFF)));
// 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));
}
for (int i = 0; i < pointer_count; ++i) {
const uint32_t snes_addr = PcToSnes(item_pointers[i]);
RETURN_IF_ERROR(
rom->WriteShort(zelda3::kOverworldItemsPointersNew + (i * 2),
static_cast<uint16_t>(snes_addr & 0xFFFF)));
}
util::logf("End of Items : %d", data_pos);
return absl::OkStatus();
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,165 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_ITEM_H_
#define YAZE_APP_ZELDA3_OVERWORLD_ITEM_H_
#include <algorithm>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "app/rom.h"
#include "zelda3/common.h"
namespace yaze {
namespace zelda3 {
// Forward declaration of OverworldMap class
class OverworldMap;
constexpr int kNumOverworldMapItemPointers = 0x80;
constexpr int kOverworldItemsPointers = 0xDC2F9;
constexpr int kOverworldItemsAddress = 0xDC8B9; // 1BC2F9
constexpr int kOverworldItemsBank = 0xDC8BF;
constexpr int kOverworldItemsEndData = 0xDC89C; // 0DC89E
constexpr int kOverworldBombDoorItemLocationsNew = 0x012644;
constexpr int kOverworldItemsPointersNew = 0x012784;
constexpr int kOverworldItemsStartDataNew = 0x0DC2F9;
constexpr int overworldItemsPointers = 0x0DC2F9;
constexpr int overworldItemsAddress = 0x0DC8B9; // 1BC2F9
constexpr int overworldItemsAddressBank = 0x0DC8BF;
constexpr int overworldItemsEndData = 0x0DC89C; // 0DC89E
constexpr int overworldBombDoorItemLocationsNew = 0x012644;
constexpr int overworldItemsPointersNew = 0x012784;
constexpr int overworldItemsStartDataNew = 0x0DC2F9;
class OverworldItem : public GameEntity {
public:
OverworldItem() = default;
OverworldItem(uint8_t id, uint16_t room_map_id, int x, int y, bool bg2)
: bg2_(bg2), id_(id), room_map_id_(room_map_id) {
x_ = x;
y_ = y;
map_id_ = room_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;
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 {
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;
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);
}
bool bg2_ = false;
uint8_t id_;
uint8_t game_x_;
uint8_t game_y_;
uint16_t room_map_id_;
int unique_id = 0;
bool deleted = false;
};
inline bool CompareOverworldItems(const std::vector<OverworldItem>& items1,
const std::vector<OverworldItem>& items2) {
if (items1.size() != items2.size()) {
return false;
}
const auto is_same_item = [](const OverworldItem& a, const OverworldItem& b) {
return a.x_ == b.x_ && a.y_ == b.y_ && a.id_ == b.id_;
};
return std::all_of(items1.begin(), items1.end(),
[&](const OverworldItem& it) {
return std::any_of(items2.begin(), items2.end(),
[&](const OverworldItem& other) {
return is_same_item(it, other);
});
});
}
inline bool CompareItemsArrays(std::vector<OverworldItem> item_array1,
std::vector<OverworldItem> item_array2) {
if (item_array1.size() != item_array2.size()) {
return false;
}
bool match;
for (size_t i = 0; i < item_array1.size(); i++) {
match = false;
for (size_t j = 0; j < item_array2.size(); j++) {
// Check all sprite in 2nd array if one match
if (item_array1[i].x_ == item_array2[j].x_ &&
item_array1[i].y_ == item_array2[j].y_ &&
item_array1[i].id_ == item_array2[j].id_) {
match = true;
break;
}
}
if (!match) {
return false;
}
}
return true;
}
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 = {
"Nothing", // 0
"Green Rupee", // 1
"Rock hoarder", // 2
"Bee", // 3
"Health pack", // 4
"Bomb", // 5
"Heart ", // 6
"Blue Rupee", // 7
"Key", // 8
"Arrow", // 9
"Bomb", // 10
"Heart", // 11
"Magic", // 12
"Full Magic", // 13
"Cucco", // 14
"Green Soldier", // 15
"Bush Stal", // 16
"Blue Soldier", // 17
"Landmine", // 18
"Heart", // 19
"Fairy", // 20
"Heart", // 21
"Nothing ", // 22
"Hole", // 23
"Warp", // 24
"Staircase", // 25
"Bombable", // 26
"Switch" // 27
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_OVERWORLD_ITEM_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,313 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_MAP_H
#define YAZE_APP_ZELDA3_OVERWORLD_MAP_H
#include <array>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace zelda3 {
static constexpr int kTileOffsets[] = {0, 8, 4096, 4104};
// 2 bytes for each overworld area (0x140)
constexpr int OverworldCustomAreaSpecificBGPalette = 0x140000;
// 1 byte, not 0 if enabled
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
// Vanilla overlay constants
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 kOverlayCodeStart = 0x77657; // Start of overlay code
// 1 byte for each overworld area (0xA0)
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;
constexpr int kOverworldTransitionPositionYExpanded = 0x140F38;
constexpr int kOverworldTransitionPositionXExpanded = 0x141078;
constexpr int kOverworldScreenTileMapChangeByScreen1Expanded = 0x140A38;
constexpr int kOverworldScreenTileMapChangeByScreen2Expanded = 0x140B78;
constexpr int kOverworldScreenTileMapChangeByScreen3Expanded = 0x140CB8;
constexpr int kOverworldScreenTileMapChangeByScreen4Expanded = 0x140DF8;
constexpr int kOverworldSpecialSpriteGFXGroup = 0x016811;
constexpr int kOverworldSpecialGFXGroup = 0x016821;
constexpr int kOverworldSpecialPALGroup = 0x016831;
constexpr int kOverworldSpecialSpritePalette = 0x016841;
constexpr int kOverworldPalettesScreenToSetNew = 0x4C635;
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp = 0x0166E1;
constexpr int kOverworldSpecialSpritePaletteExpandedTemp = 0x016701;
constexpr int transition_target_northExpanded = 0x1411B8;
constexpr int transition_target_westExpanded = 0x1412F8;
constexpr int kDarkWorldMapIdStart = 0x40;
constexpr int kSpecialWorldMapIdStart = 0x80;
/**
* @brief Represents tile32 data for the overworld.
*/
using OverworldBlockset = std::vector<std::vector<uint16_t>>;
/**
* @brief Overworld map tile32 data.
*/
typedef struct OverworldMapTiles {
OverworldBlockset light_world; // 64 maps
OverworldBlockset dark_world; // 64 maps
OverworldBlockset special_world; // 32 maps
} OverworldMapTiles;
enum class AreaSizeEnum {
SmallArea = 0,
LargeArea = 1,
WideArea = 2,
TallArea = 3,
};
/**
* @brief Represents a single Overworld map screen.
*/
class OverworldMap : public gfx::GfxContext {
public:
OverworldMap() = default;
OverworldMap(int index, Rom* rom);
absl::Status BuildMap(int count, int game_state, int world,
std::vector<gfx::Tile16>& tiles16,
OverworldBlockset& world_blockset);
void LoadAreaGraphics();
absl::Status LoadPalette();
absl::Status LoadOverlay();
absl::Status LoadVanillaOverlayData();
absl::Status BuildTileset();
absl::Status BuildTiles16Gfx(std::vector<gfx::Tile16>& tiles16, int count);
absl::Status BuildBitmap(OverworldBlockset& world_blockset);
void DrawAnimatedTiles();
auto current_tile16_blockset() const { return current_blockset_; }
auto current_graphics() const { return current_gfx_; }
auto current_palette() const { return current_palette_; }
auto bitmap_data() const { return bitmap_data_; }
auto is_large_map() const { return large_map_; }
auto is_initialized() const { return initialized_; }
auto is_built() const { return built_; }
auto parent() const { return parent_; }
auto mutable_mosaic() { return &mosaic_; }
auto mutable_current_palette() { return &current_palette_; }
void SetNotBuilt() { built_ = false; }
auto area_graphics() const { return area_graphics_; }
auto area_palette() const { return area_palette_; }
auto sprite_graphics(int i) const { return sprite_graphics_[i]; }
auto sprite_palette(int i) const { return sprite_palette_[i]; }
auto message_id() const { return message_id_; }
auto area_music(int i) const { return area_music_[i]; }
auto static_graphics(int i) const { return static_graphics_[i]; }
auto large_index() const { return large_index_; }
auto area_size() const { return area_size_; }
auto main_palette() const { return main_palette_; }
void set_main_palette(uint8_t palette) { main_palette_ = palette; }
auto area_specific_bg_color() const { return area_specific_bg_color_; }
void set_area_specific_bg_color(uint16_t color) {
area_specific_bg_color_ = color;
}
auto subscreen_overlay() const { return subscreen_overlay_; }
void set_subscreen_overlay(uint16_t overlay) { subscreen_overlay_ = overlay; }
auto animated_gfx() const { return animated_gfx_; }
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; }
auto mutable_current_graphics() { return &current_gfx_; }
auto mutable_area_graphics() { return &area_graphics_; }
auto mutable_area_palette() { return &area_palette_; }
auto mutable_sprite_graphics(int i) { return &sprite_graphics_[i]; }
auto mutable_sprite_palette(int i) { return &sprite_palette_[i]; }
auto mutable_message_id() { return &message_id_; }
auto mutable_main_palette() { return &main_palette_; }
auto mutable_animated_gfx() { return &animated_gfx_; }
auto mutable_subscreen_overlay() { return &subscreen_overlay_; }
auto mutable_area_music(int i) { return &area_music_[i]; }
auto mutable_static_graphics(int i) { return &static_graphics_[i]; }
auto set_area_graphics(uint8_t value) { area_graphics_ = value; }
auto set_area_palette(uint8_t value) { area_palette_ = value; }
auto set_sprite_graphics(int i, uint8_t value) {
sprite_graphics_[i] = value;
}
auto set_sprite_palette(int i, uint8_t value) { sprite_palette_[i] = value; }
auto set_message_id(uint16_t value) { message_id_ = value; }
uint8_t* mutable_custom_tileset(int index) { return &custom_gfx_ids_[index]; }
void SetAsLargeMap(int parent_index, int quadrant) {
parent_ = parent_index;
large_index_ = quadrant;
large_map_ = true;
area_size_ = AreaSizeEnum::LargeArea;
}
void SetAsSmallMap(int index = -1) {
if (index != -1)
parent_ = index;
else
parent_ = index_;
large_index_ = 0;
large_map_ = false;
area_size_ = AreaSizeEnum::SmallArea;
}
void SetAreaSize(AreaSizeEnum size) {
area_size_ = size;
large_map_ = (size == AreaSizeEnum::LargeArea);
}
void SetParent(int parent_index) {
parent_ = parent_index;
}
void Destroy() {
current_blockset_.clear();
current_gfx_.clear();
bitmap_data_.clear();
map_tiles_.light_world.clear();
map_tiles_.dark_world.clear();
map_tiles_.special_world.clear();
built_ = false;
initialized_ = false;
large_map_ = false;
mosaic_ = false;
index_ = 0;
parent_ = 0;
large_index_ = 0;
world_ = 0;
game_state_ = 0;
main_gfx_id_ = 0;
message_id_ = 0;
area_graphics_ = 0;
area_palette_ = 0;
main_palette_ = 0;
animated_gfx_ = 0;
subscreen_overlay_ = 0;
area_specific_bg_color_ = 0;
custom_gfx_ids_.fill(0);
sprite_graphics_.fill(0);
sprite_palette_.fill(0);
area_music_.fill(0);
static_graphics_.fill(0);
mosaic_expanded_.fill(false);
area_size_ = AreaSizeEnum::SmallArea;
overlay_id_ = 0;
has_overlay_ = false;
overlay_data_.clear();
}
private:
void LoadAreaInfo();
void LoadCustomOverworldData();
void SetupCustomTileset(uint8_t asm_version);
void LoadMainBlocksetId();
void LoadSpritesBlocksets();
void LoadMainBlocksets();
void LoadAreaGraphicsBlocksets();
void LoadDeathMountainGFX();
void ProcessGraphicsBuffer(int index, int static_graphics_offset, int size,
uint8_t* all_gfx);
absl::StatusOr<gfx::SnesPalette> GetPalette(const gfx::PaletteGroup& group,
int index, int previous_index,
int limit);
Rom* rom_;
bool built_ = false;
bool large_map_ = false;
bool initialized_ = false;
bool mosaic_ = false;
int index_ = 0; // Map index
int parent_ = 0; // Parent map index
int large_index_ = 0; // Quadrant ID [0-3]
int world_ = 0; // World ID [0-2]
int game_state_ = 0; // Game state [0-2]
int main_gfx_id_ = 0; // Main Gfx ID
AreaSizeEnum area_size_ = AreaSizeEnum::SmallArea; // Area size for v3
uint16_t message_id_ = 0;
uint8_t area_graphics_ = 0;
uint8_t area_palette_ = 0;
uint8_t main_palette_ = 0; // Custom Overworld Main Palette ID
uint8_t animated_gfx_ = 0; // Custom Overworld Animated ID
uint16_t subscreen_overlay_ = 0; // Custom Overworld Subscreen Overlay ID
uint16_t area_specific_bg_color_ =
0; // Custom Overworld Area-Specific Background Color
std::array<uint8_t, 8> custom_gfx_ids_;
std::array<uint8_t, 3> sprite_graphics_;
std::array<uint8_t, 3> sprite_palette_;
std::array<uint8_t, 4> area_music_;
std::array<uint8_t, 16> static_graphics_;
std::array<bool, 4> mosaic_expanded_;
// Overlay support (interactive overlays that reveal holes/change elements)
uint16_t overlay_id_ = 0;
bool has_overlay_ = false;
std::vector<uint8_t> overlay_data_;
std::vector<uint8_t> current_blockset_;
std::vector<uint8_t> current_gfx_;
std::vector<uint8_t> bitmap_data_;
OverworldMapTiles map_tiles_;
gfx::SnesPalette current_palette_;
};
} // namespace zelda3
} // namespace yaze
#endif

View File

@@ -0,0 +1,82 @@
#include "zelda3/palette_constants.h"
#include <string>
#include <vector>
namespace yaze::zelda3 {
const PaletteGroupMetadata* GetPaletteGroupMetadata(const char* group_id) {
std::string id_str(group_id);
if (id_str == PaletteGroupName::kOverworldMain) {
return &PaletteMetadata::kOverworldMain;
}
if (id_str == PaletteGroupName::kOverworldAux) {
return &PaletteMetadata::kOverworldAux;
}
if (id_str == PaletteGroupName::kOverworldAnimated) {
return &PaletteMetadata::kOverworldAnimated;
}
if (id_str == PaletteGroupName::kDungeonMain) {
return &PaletteMetadata::kDungeonMain;
}
if (id_str == PaletteGroupName::kGlobalSprites) {
return &PaletteMetadata::kGlobalSprites;
}
if (id_str == PaletteGroupName::kSpritesAux1) {
return &PaletteMetadata::kSpritesAux1;
}
if (id_str == PaletteGroupName::kSpritesAux2) {
return &PaletteMetadata::kSpritesAux2;
}
if (id_str == PaletteGroupName::kSpritesAux3) {
return &PaletteMetadata::kSpritesAux3;
}
if (id_str == PaletteGroupName::kArmor) {
return &PaletteMetadata::kArmor;
}
if (id_str == PaletteGroupName::kSwords) {
return &PaletteMetadata::kSwords;
}
if (id_str == PaletteGroupName::kShields) {
return &PaletteMetadata::kShields;
}
if (id_str == PaletteGroupName::kHud) {
return &PaletteMetadata::kHud;
}
if (id_str == PaletteGroupName::kGrass) {
return &PaletteMetadata::kGrass;
}
if (id_str == PaletteGroupName::k3DObject) {
return &PaletteMetadata::k3DObject;
}
if (id_str == PaletteGroupName::kOverworldMiniMap) {
return &PaletteMetadata::kOverworldMiniMap;
}
return nullptr;
}
std::vector<const PaletteGroupMetadata*> GetAllPaletteGroups() {
return {// Overworld
&PaletteMetadata::kOverworldMain, &PaletteMetadata::kOverworldAux,
&PaletteMetadata::kOverworldAnimated,
// Dungeon
&PaletteMetadata::kDungeonMain,
// Sprites
&PaletteMetadata::kGlobalSprites, &PaletteMetadata::kSpritesAux1,
&PaletteMetadata::kSpritesAux2, &PaletteMetadata::kSpritesAux3,
// Equipment
&PaletteMetadata::kArmor, &PaletteMetadata::kSwords,
&PaletteMetadata::kShields,
// Interface
&PaletteMetadata::kHud, &PaletteMetadata::kOverworldMiniMap,
// Special
&PaletteMetadata::kGrass, &PaletteMetadata::k3DObject};
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,308 @@
#ifndef YAZE_ZELDA3_PALETTE_CONSTANTS_H
#define YAZE_ZELDA3_PALETTE_CONSTANTS_H
#include <cstdint>
#include <vector>
namespace yaze::zelda3 {
// ============================================================================
// Palette Group Names
// ============================================================================
// These constants ensure consistent naming across the entire program
namespace PaletteGroupName {
constexpr const char* kOverworldMain = "ow_main";
constexpr const char* kOverworldAux = "ow_aux";
constexpr const char* kOverworldAnimated = "ow_animated";
constexpr const char* kHud = "hud";
constexpr const char* kGlobalSprites = "global_sprites";
constexpr const char* kArmor = "armor";
constexpr const char* kSwords = "swords";
constexpr const char* kShields = "shields";
constexpr const char* kSpritesAux1 = "sprites_aux1";
constexpr const char* kSpritesAux2 = "sprites_aux2";
constexpr const char* kSpritesAux3 = "sprites_aux3";
constexpr const char* kDungeonMain = "dungeon_main";
constexpr const char* kGrass = "grass";
constexpr const char* k3DObject = "3d_object";
constexpr const char* kOverworldMiniMap = "ow_mini_map";
} // namespace PaletteGroupName
// ============================================================================
// ROM Addresses
// ============================================================================
namespace PaletteAddress {
constexpr uint32_t kOverworldMain = 0xDE6C8;
constexpr uint32_t kOverworldAux = 0xDE86C;
constexpr uint32_t kOverworldAnimated = 0xDE604;
constexpr uint32_t kGlobalSpritesLW = 0xDD218;
constexpr uint32_t kGlobalSpritesDW = 0xDD290;
constexpr uint32_t kArmor = 0xDD308;
constexpr uint32_t kSpritesAux1 = 0xDD39E;
constexpr uint32_t kSpritesAux2 = 0xDD446;
constexpr uint32_t kSpritesAux3 = 0xDD4E0;
constexpr uint32_t kSwords = 0xDD630;
constexpr uint32_t kShields = 0xDD648;
constexpr uint32_t kHud = 0xDD660;
constexpr uint32_t kDungeonMap = 0xDD70A;
constexpr uint32_t kDungeonMain = 0xDD734;
constexpr uint32_t kDungeonMapBg = 0xDE544;
constexpr uint32_t kGrassLW = 0x5FEA9;
constexpr uint32_t kGrassDW = 0x05FEB3;
constexpr uint32_t kGrassSpecial = 0x75640;
constexpr uint32_t kOverworldMiniMap = 0x55B27;
constexpr uint32_t kTriforce = 0x64425;
constexpr uint32_t kCrystal = 0xF4CD3;
} // namespace PaletteAddress
// ============================================================================
// Palette Counts
// ============================================================================
namespace PaletteCount {
constexpr int kHud = 2;
constexpr int kOverworldMain = 60; // 20 LW, 20 DW, 20 Special
constexpr int kOverworldAux = 20;
constexpr int kOverworldAnimated = 14;
constexpr int kGlobalSprites = 6;
constexpr int kArmor = 5;
constexpr int kSwords = 4;
constexpr int kSpritesAux1 = 12;
constexpr int kSpritesAux2 = 11;
constexpr int kSpritesAux3 = 24;
constexpr int kShields = 3;
constexpr int kDungeonMain = 20;
constexpr int kGrass = 3;
constexpr int k3DObject = 2;
constexpr int kOverworldMiniMap = 2;
} // namespace PaletteCount
// ============================================================================
// Palette Metadata
// ============================================================================
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
};
// 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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
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
};
} // namespace PaletteMetadata
// Helper to get metadata by group name
const PaletteGroupMetadata* GetPaletteGroupMetadata(const char* group_id);
// Get all available palette groups
std::vector<const PaletteGroupMetadata*> GetAllPaletteGroups();
} // namespace yaze::zelda3
#endif // YAZE_ZELDA3_PALETTE_CONSTANTS_H

View File

@@ -0,0 +1,142 @@
# SNES Palette Structure for ALTTP
## SNES Palette Memory Layout
The SNES has 256 color palette entries organized as:
- **16 palette rows** of **16 colors each**
- Each row starts with color index 0, which is **transparent**
- Palettes must be aligned to 16-color boundaries
### Example Layout
```
Row 0: Colors 0-15 (Color 0 = transparent)
Row 1: Colors 16-31 (Color 16 = transparent)
Row 2: Colors 32-47 (Color 32 = transparent)
...
```
## ALTTP Palette Groups - Corrected Structure
### Background Palettes (BG)
#### Overworld Main (35 colors per set)
- **Structure**: 2 full rows + 3 colors
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-31 (transparent + 15 colors)
- Row 2: Colors 32-34 (3 colors)
- **ROM**: 0xDE6C8
- **Sets**: 60 (20 LW, 20 DW, 20 Special)
#### Overworld Auxiliary (21 colors per set)
- **Structure**: 1 full row + 5 colors
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-20 (5 colors)
- **ROM**: 0xDE86C
- **Sets**: 20
#### Overworld Animated (7 colors per set)
- **Structure**: Half-row without transparent
- Colors 0-6 (7 colors, no transparent marker as these overlay existing)
- **ROM**: 0xDE604
- **Sets**: 14
#### Dungeon Main (90 colors per set)
- **Structure**: 5 full rows + 10 colors
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-31 (transparent + 15 colors)
- Row 2: Colors 32-47 (transparent + 15 colors)
- Row 3: Colors 48-63 (transparent + 15 colors)
- Row 4: Colors 64-79 (transparent + 15 colors)
- Row 5: Colors 80-89 (10 colors)
- **ROM**: 0xDD734
- **Sets**: 20 (one per dungeon)
### Sprite Palettes (OAM)
Sprite palettes use rows 8-15 (colors 128-255).
#### Global Sprites (60 colors total)
- **Structure**: 4 rows (each with 15 actual colors + transparent)
- Row 8: Colors 128-143 (Sprite Palette 0: transparent + 15 colors)
- Row 9: Colors 144-159 (Sprite Palette 1: transparent + 15 colors)
- Row 10: Colors 160-175 (Sprite Palette 2: transparent + 15 colors)
- Row 11: Colors 176-191 (Sprite Palette 3: transparent + 15 colors)
- **ROM LW**: 0xDD218
- **ROM DW**: 0xDD290
- **Total**: 2 sets (LW and DW)
#### Sprites Auxiliary 1 (7 colors per palette)
- **Structure**: 12 palettes, each occupying half a row
- Palette 0: 7 colors (indices 1-7 of first half-row)
- Palette 1: 7 colors (indices 9-15 of second half-row)
- ...and so on
- **ROM**: 0xDD39E
- **Palettes**: 12
#### Sprites Auxiliary 2 (7 colors per palette)
- **Structure**: 11 palettes, each occupying half a row
- **ROM**: 0xDD446
- **Palettes**: 11
#### Sprites Auxiliary 3 (7 colors per palette)
- **Structure**: 24 palettes, each occupying half a row
- **ROM**: 0xDD4E0
- **Palettes**: 24
### Equipment/Link Palettes
#### Armor/Link (15 colors per palette)
- **Structure**: Full row minus transparent
- Each palette: transparent + 15 colors
- **ROM**: 0xDD308
- **Palettes**: 5 (Green Mail, Blue Mail, Red Mail, Bunny, Electrocuted)
#### Swords (3 colors per palette)
- **Structure**: 3 colors within row (no transparent needed as overlay)
- **ROM**: 0xDD630
- **Palettes**: 4 (Fighter, Master, Tempered, Golden)
#### Shields (4 colors per palette)
- **Structure**: 4 colors within row (no transparent needed as overlay)
- **ROM**: 0xDD648
- **Palettes**: 3 (Fighter, Fire, Mirror)
### HUD Palettes
#### HUD (32 colors per set)
- **Structure**: 2 full rows
- Row 0: Colors 0-15 (transparent + 15 colors)
- Row 1: Colors 16-31 (transparent + 15 colors)
- **ROM**: 0xDD660
- **Sets**: 2
### Special Colors
#### Grass (3 individual colors)
- LW: 0x5FEA9
- DW: 0x5FEB3
- Special: 0x75640
#### 3D Objects (8 colors per palette)
- **Triforce**: 0x64425
- **Crystal**: 0xF4CD3
#### Overworld Mini Map (128 colors per set)
- **Structure**: 8 full rows
- **ROM**: 0x55B27
- **Sets**: 2
## Key Principles
1. **Transparent Color**: Always at indices 0, 16, 32, 48, 64, etc. (multiples of 16)
2. **Row Alignment**: Palettes should respect 16-color row boundaries
3. **Overlay Palettes**: Some palettes (like animated, swords, shields) overlay existing colors and don't have their own transparent
4. **Sub-Palettes**: Multiple small palettes can share a row if they're 8 colors or less
## Implementation Notes
When loading palettes, we must:
1. Mark color index 0 of each 16-color row as transparent
2. For palettes < 16 colors, understand if they're standalone (need transparent) or overlays (don't need transparent)
3. Display palettes in UI with proper row alignment for clarity

View File

@@ -0,0 +1,207 @@
#include "dungeon_map.h"
#include <fstream>
#include <vector>
#include "util/file_util.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/gfx/render/tilemap.h"
#include "app/gfx/backend/irenderer.h"
#include "app/snes.h"
#include "util/hex.h"
namespace yaze::zelda3 {
absl::StatusOr<std::vector<DungeonMap>> LoadDungeonMaps(
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;
int total_floors_d;
uint8_t nbr_floor_d;
uint8_t nbr_basement_d;
for (int d = 0; d < kNumDungeons; d++) {
current_floor_rooms_d.clear();
current_floor_gfx_d.clear();
ASSIGN_OR_RETURN(int ptr, rom.ReadWord(kDungeonMapRoomsPtr + (d * 2)));
ASSIGN_OR_RETURN(int ptr_gfx, rom.ReadWord(kDungeonMapGfxPtr + (d * 2)));
ptr |= 0x0A0000; // Add bank to the short ptr
ptr_gfx |= 0x0A0000; // Add bank to the short ptr
int pc_ptr = SnesToPc(ptr); // Contains data for the next 25 rooms
int pc_ptr_gfx = SnesToPc(ptr_gfx); // Contains data for the next 25 rooms
ASSIGN_OR_RETURN(uint16_t boss_room_d,
rom.ReadWord(kDungeonMapBossRooms + (d * 2)));
ASSIGN_OR_RETURN(nbr_basement_d, rom.ReadByte(kDungeonMapFloors + (d * 2)));
nbr_basement_d &= 0x0F;
ASSIGN_OR_RETURN(nbr_floor_d, rom.ReadByte(kDungeonMapFloors + (d * 2)));
nbr_floor_d &= 0xF0;
nbr_floor_d = nbr_floor_d >> 4;
total_floors_d = nbr_basement_d + nbr_floor_d;
// for each floor in the dungeon
for (int i = 0; i < total_floors_d; i++) {
dungeon_map_labels[d].emplace_back();
std::array<uint8_t, kNumRooms> rdata;
std::array<uint8_t, kNumRooms> gdata;
// for each room on the floor
for (int j = 0; j < kNumRooms; j++) {
gdata[j] = 0xFF;
rdata[j] = rom.data()[pc_ptr + j + (i * kNumRooms)]; // Set the rooms
gdata[j] = rdata[j] == 0x0F ? 0xFF : rom.data()[pc_ptr_gfx++];
std::string label = util::HexByte(rdata[j]);
dungeon_map_labels[d][i][j] = label;
}
current_floor_gfx_d.push_back(gdata); // Add new floor gfx data
current_floor_rooms_d.push_back(rdata); // Add new floor data
}
dungeon_maps.emplace_back(boss_room_d, nbr_floor_d, nbr_basement_d,
current_floor_rooms_d, current_floor_gfx_d);
}
return 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);
int pc_ptr = SnesToPc(ptr);
int pc_ptr_gfx = SnesToPc(ptr_gfx);
const int nbr_floors = dungeon_maps[d].nbr_of_floor;
const int nbr_basements = dungeon_maps[d].nbr_of_basement;
for (int i = 0; i < nbr_floors + nbr_basements; i++) {
for (int j = 0; j < kNumRooms; j++) {
RETURN_IF_ERROR(rom.WriteByte(pc_ptr + j + (i * kNumRooms),
dungeon_maps[d].floor_rooms[i][j]));
RETURN_IF_ERROR(rom.WriteByte(pc_ptr_gfx + j + (i * kNumRooms),
dungeon_maps[d].floor_gfx[i][j]));
pc_ptr_gfx++;
}
}
}
return absl::OkStatus();
}
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};
tile16_blockset.atlas.Create(256, 192, 8,
std::vector<uint8_t>(256 * 192, 0x00));
for (int i = 0; i < kNumDungeonMapTile16; i++) {
int addr = kDungeonMapTile16;
if (rom.data()[kDungeonMapExpCheck] != 0xB9) {
addr = kDungeonMapTile16Expanded;
}
ASSIGN_OR_RETURN(auto tl, rom.ReadWord(addr + (i * 8)));
gfx::TileInfo t1 = gfx::WordToTileInfo(tl); // Top left
ASSIGN_OR_RETURN(auto tr, rom.ReadWord(addr + 2 + (i * 8)));
gfx::TileInfo t2 = gfx::WordToTileInfo(tr); // Top right
ASSIGN_OR_RETURN(auto bl, rom.ReadWord(addr + 4 + (i * 8)));
gfx::TileInfo t3 = gfx::WordToTileInfo(bl); // Bottom left
ASSIGN_OR_RETURN(auto br, rom.ReadWord(addr + 6 + (i * 8)));
gfx::TileInfo t4 = gfx::WordToTileInfo(br); // Bottom right
int sheet_offset = 212;
if (bin_mode) {
sheet_offset = 0;
}
ComposeTile16(tile16_blockset, gfx_data, t1, t2, t3, t4, sheet_offset);
}
tile16_blockset.atlas.SetPalette(*rom.mutable_dungeon_palette(3));
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(gfx::Arena::TextureCommandType::CREATE,
&tile16_blockset.atlas);
return absl::OkStatus();
}
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom) {
for (int i = 0; i < kNumDungeonMapTile16; i++) {
int addr = kDungeonMapTile16;
if (rom.data()[kDungeonMapExpCheck] != 0xB9) {
addr = kDungeonMapTile16Expanded;
}
gfx::TileInfo t1 = tile16_blockset.tile_info[i][0];
gfx::TileInfo t2 = tile16_blockset.tile_info[i][1];
gfx::TileInfo t3 = tile16_blockset.tile_info[i][2];
gfx::TileInfo t4 = tile16_blockset.tile_info[i][3];
auto tl = gfx::TileInfoToWord(t1);
RETURN_IF_ERROR(rom.WriteWord(addr + (i * 8), tl));
auto tr = gfx::TileInfoToWord(t2);
RETURN_IF_ERROR(rom.WriteWord(addr + 2 + (i * 8), tr));
auto bl = gfx::TileInfoToWord(t3);
RETURN_IF_ERROR(rom.WriteWord(addr + 4 + (i * 8), bl));
auto br = gfx::TileInfoToWord(t4);
RETURN_IF_ERROR(rom.WriteWord(addr + 6 + (i * 8), br));
}
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) {
std::string bin_file = util::FileDialogWrapper::ShowOpenFileDialog();
if (bin_file.empty()) {
return absl::InternalError("No file selected");
}
std::ifstream file(bin_file, std::ios::binary);
if (!file.is_open()) {
return absl::InternalError("Failed to open file");
}
// Read the gfx data into a buffer
std::vector<uint8_t> bin_data((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
auto converted_bin = gfx::SnesTo8bppSheet(bin_data, 4, 4);
gfx_bin_data = converted_bin;
if (LoadDungeonMapTile16(tile16_blockset, rom, converted_bin, true).ok()) {
std::vector<std::vector<uint8_t>> gfx_sheets;
for (int i = 0; i < 4; i++) {
gfx_sheets.emplace_back(converted_bin.begin() + (i * 0x1000),
converted_bin.begin() + ((i + 1) * 0x1000));
sheets[i] = gfx::Bitmap(128, 32, 8, gfx_sheets[i]);
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]);
}
}
file.close();
return absl::OkStatus();
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,114 @@
#ifndef YAZE_APP_ZELDA3_SCREEN_DUNGEON_MAP_H
#define YAZE_APP_ZELDA3_SCREEN_DUNGEON_MAP_H
#include <array>
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/render/tilemap.h"
#include "app/rom.h"
namespace yaze::zelda3 {
constexpr int kDungeonMapRoomsPtr = 0x57605; // 14 pointers of map data
constexpr int kDungeonMapFloors = 0x575D9; // 14 words values
constexpr int kDungeonMapGfxPtr = 0x57BE4; // 14 pointers of gfx data
// data start for floors/gfx MUST skip 575D9 to 57621 (pointers)
constexpr int kDungeonMapDataStart = 0x57039;
// IF Byte = 0xB9 dungeon maps are not expanded
constexpr int kDungeonMapExpCheck = 0x56652; // $0A:E652
constexpr int kDungeonMapTile16 = 0x57009; // $0A:F009
constexpr int kDungeonMapTile16Expanded = 0x109010; // $21:9010
// 14 words values 0x000F = no boss
constexpr int kDungeonMapBossRooms = 0x56807;
constexpr int kTriforceVertices = 0x04FFD2; // group of 3, X, Y ,Z
constexpr int kTriforceFaces = 0x04FFE4; // group of 5
constexpr int kCrystalVertices = 0x04FF98;
constexpr int kNumDungeons = 14;
constexpr int kNumRooms = 25;
constexpr int kNumDungeonMapTile16 = 186;
/**
* @brief DungeonMap represents the map menu for a dungeon.
*/
struct DungeonMap {
unsigned short boss_room = 0xFFFF;
unsigned char nbr_of_floor = 0;
unsigned char nbr_of_basement = 0;
std::vector<std::array<uint8_t, kNumRooms>> floor_rooms;
std::vector<std::array<uint8_t, kNumRooms>> floor_gfx;
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)
: boss_room(boss_room),
nbr_of_floor(nbr_of_floor),
nbr_of_basement(nbr_of_basement),
floor_rooms(floor_rooms),
floor_gfx(floor_gfx) {}
};
using DungeonMapLabels =
std::array<std::vector<std::array<std::string, kNumRooms>>, kNumDungeons>;
/**
* @brief Load the dungeon maps from the ROM.
*
* @param rom
* @param dungeon_map_labels
* @return absl::StatusOr<std::vector<DungeonMap>>
*/
absl::StatusOr<std::vector<DungeonMap>> LoadDungeonMaps(
Rom &rom, DungeonMapLabels &dungeon_map_labels);
/**
* @brief Save the dungeon maps to the ROM.
*
* @param rom
* @param dungeon_maps
*/
absl::Status SaveDungeonMaps(Rom &rom, std::vector<DungeonMap> &dungeon_maps);
/**
* @brief Load the dungeon map tile16 from the ROM.
*
* @param tile16_blockset
* @param rom
* @param gfx_data
* @param bin_mode
*/
absl::Status LoadDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom,
const std::vector<uint8_t> &gfx_data,
bool bin_mode);
/**
* @brief Save the dungeon map tile16 to the ROM.
*
* @param tile16_blockset
* @param rom
*/
absl::Status SaveDungeonMapTile16(gfx::Tilemap &tile16_blockset, Rom &rom);
/**
* @brief Load the dungeon map gfx from binary.
*
* @param rom
* @param tile16_blockset
* @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);
} // namespace yaze::zelda3
#endif // YAZE_APP_ZELDA3_SCREEN_DUNGEON_MAP_H

View File

@@ -0,0 +1,156 @@
#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/rom.h"
#include "app/snes.h"
namespace yaze {
namespace zelda3 {
absl::Status Inventory::Create(Rom* rom) {
if (!rom || !rom->is_loaded()) {
return absl::InvalidArgumentError("ROM is not loaded");
}
// Build the tileset first (loads 2BPP graphics)
RETURN_IF_ERROR(BuildTileset(rom));
// Load item icons from ROM
RETURN_IF_ERROR(LoadItemIcons(rom));
// TODO(scawful): For now, create a simple display bitmap
// Future: Oracle of Secrets menu editor will handle full menu layout
data_.reserve(256 * 256);
for (int i = 0; i < 256 * 256; i++) {
data_.push_back(0xFF);
}
bitmap_.Create(256, 256, 8, data_);
bitmap_.SetPalette(palette_);
// Queue texture creation via Arena's deferred system
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);
ASSIGN_OR_RETURN(tilesheets_, Load2BppGraphics(*rom));
std::vector<uint8_t> test;
for (int i = 0; i < 0x4000; i++) {
test_.push_back(tilesheets_[i]);
}
for (int i = 0x8000; i < +0x8000 + 0x2000; i++) {
test_.push_back(tilesheets_[i]);
}
tilesheets_bmp_.Create(128, 0x130, 64, test_);
auto hud_pal_group = rom->palette_group().hud;
palette_ = hud_pal_group[0];
tilesheets_bmp_.SetPalette(palette_);
// Queue texture creation via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &tilesheets_bmp_);
return absl::OkStatus();
}
absl::Status Inventory::LoadItemIcons(Rom* rom) {
// Convert SNES address to PC address
int pc_addr = SnesToPc(kItemIconsPtr);
// Define icon categories and their ROM offsets (relative to kItemIconsPtr)
// Based on bank_0D.asm ItemIcons structure
struct IconDef {
int offset;
std::string name;
};
// 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"}
};
// Boomerang icons (.booms section)
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"}
};
// Bomb icons (.bombs section)
std::vector<IconDef> bomb_icons = {
{0x50, "No bombs"},
{0x58, "Bombs"}
};
// Load all icon categories
auto load_icons = [&](const std::vector<IconDef>& icons) -> absl::Status {
for (const auto& icon_def : icons) {
ItemIcon icon;
int icon_addr = pc_addr + icon_def.offset;
ASSIGN_OR_RETURN(icon.tile_tl, rom->ReadWord(icon_addr));
ASSIGN_OR_RETURN(icon.tile_tr, rom->ReadWord(icon_addr + 2));
ASSIGN_OR_RETURN(icon.tile_bl, rom->ReadWord(icon_addr + 4));
ASSIGN_OR_RETURN(icon.tile_br, rom->ReadWord(icon_addr + 6));
icon.name = icon_def.name;
item_icons_.push_back(icon);
}
return absl::OkStatus();
};
RETURN_IF_ERROR(load_icons(bow_icons));
RETURN_IF_ERROR(load_icons(boom_icons));
RETURN_IF_ERROR(load_icons(hook_icons));
RETURN_IF_ERROR(load_icons(bomb_icons));
// TODO(scawful): Load remaining icon categories:
// - Mushroom/Powder (.shroom)
// - Magic powder (.powder)
// - Fire rod (.fires)
// - Ice rod (.ices)
// - Bombos medallion (.bombos)
// - Ether medallion (.ether)
// - Quake medallion (.quake)
// - Lantern (.lamp)
// - Hammer (.hammer)
// - Flute (.flute)
// - Bug net (.net)
// - Book of Mudora (.book)
// - Bottles (.bottles) - Multiple variants (empty, red potion, green potion, etc.)
// - Cane of Somaria (.canes)
// - Cane of Byrna (.byrn)
// - Magic cape (.cape)
// - Magic mirror (.mirror)
// - Gloves (.glove)
// - Boots (.boots)
// - Flippers (.flippers)
// - Moon pearl (.pearl)
// - Swords (.swords)
// - Shields (.shields)
// - Armor (.armor)
return absl::OkStatus();
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,77 @@
#ifndef YAZE_APP_ZELDA3_INVENTORY_H
#define YAZE_APP_ZELDA3_INVENTORY_H
#include "absl/status/status.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "app/gui/canvas/canvas.h"
#include "app/rom.h"
namespace yaze {
namespace zelda3 {
constexpr int kInventoryStart = 0x6564A;
// ItemIcons base address in SNES format (0x0DF629)
constexpr int kItemIconsPtr = 0x0DF629;
/**
* @brief Represents a single item icon (2x2 tiles = 4 tile words)
*/
struct ItemIcon {
uint16_t tile_tl; // Top-left tile word (vhopppcc cccccccc format)
uint16_t tile_tr; // Top-right tile word
uint16_t tile_bl; // Bottom-left tile word
uint16_t tile_br; // Bottom-right tile word
std::string name; // Human-readable name for debugging
};
/**
* @brief Inventory manages the inventory screen graphics and layout.
*
* The inventory screen consists of a 256x256 bitmap displaying equipment,
* items, and UI elements using 2BPP graphics and HUD palette.
*/
class Inventory {
public:
/**
* @brief Initialize and load inventory screen data from ROM
* @param rom ROM instance to read data from
*/
absl::Status Create(Rom* rom);
auto &bitmap() { return bitmap_; }
auto &tilesheet() { return tilesheets_bmp_; }
auto &palette() { return palette_; }
auto &item_icons() { return item_icons_; }
private:
/**
* @brief Build the tileset from 2BPP graphics
* @param rom ROM instance to read graphics from
*/
absl::Status BuildTileset(Rom* rom);
/**
* @brief Load individual item icons from ROM
* @param rom ROM instance to read icon data from
*/
absl::Status LoadItemIcons(Rom* rom);
std::vector<uint8_t> data_;
gfx::Bitmap bitmap_;
std::vector<uint8_t> tilesheets_;
std::vector<uint8_t> test_;
gfx::Bitmap tilesheets_bmp_;
gfx::SnesPalette palette_;
gui::Canvas canvas_;
std::vector<gfx::TileInfo> tiles_;
std::vector<ItemIcon> item_icons_;
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_INVENTORY_H

View File

@@ -0,0 +1,365 @@
#include "zelda3/screen/overworld_map_screen.h"
#include <fstream>
#include "app/gfx/resource/arena.h"
#include "app/gfx/types/snes_color.h"
#include "app/rom.h"
#include "app/snes.h"
namespace yaze {
namespace zelda3 {
absl::Status OverworldMapScreen::Create(Rom* rom) {
if (!rom || !rom->is_loaded()) {
return absl::InvalidArgumentError("ROM is not loaded");
}
// Set metadata for overworld map bitmaps
// Mode 7 graphics use full 128-color palettes
// 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
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
mode7_gfx[x + sx + y + sy] = mode7_gfx_raw[pos];
pos++;
}
}
}
}
// Create tiles8 bitmap: 128×128 pixels (16×16 tiles = 256 tiles)
tiles8_bitmap_.Create(128, 128, 8, mode7_gfx);
tiles8_bitmap_.metadata().source_bpp = 8;
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;
map_bitmap_.metadata().palette_format = 0;
map_bitmap_.metadata().source_type = "mode7_map";
map_bitmap_.metadata().palette_colors = 128;
// Light World palette at 0x055B27
const int lw_pal_addr = 0x055B27;
for (int i = 0; i < 128; i++) {
ASSIGN_OR_RETURN(uint16_t snes_color, rom->ReadWord(lw_pal_addr + (i * 2)));
// Create SnesColor directly from SNES 15-bit format
lw_palette_.AddColor(gfx::SnesColor(snes_color));
}
// 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)));
// Create SnesColor directly from SNES 15-bit format
dw_palette_.AddColor(gfx::SnesColor(snes_color));
}
// Load map tile data
RETURN_IF_ERROR(LoadMapData(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_);
return absl::OkStatus();
}
absl::Status OverworldMapScreen::LoadMapData(Rom* rom) {
// Map data is stored in interleaved format across 4 sections + 1 DW section
// 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 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)
if (!rSide) {
// Read from left side (p1)
ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p1));
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p1++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
count++;
continue;
}
} else {
// Read from right side (p2)
ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p2));
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p2++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
count++;
continue;
}
}
} else { // Bottom half (remaining 2048 tiles)
if (!rSide) {
// Read from left side (p3)
ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p3));
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p3++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
count++;
continue;
}
} else {
// Read from right side (p4)
ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p4));
lw_map_tiles_[count] = tile;
dw_map_tiles_[count] = tile;
p4++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
count++;
continue;
}
}
}
cSide++;
count++;
}
// Load Dark World specific data (bottom-right 32x32 section)
count = 0;
int line = 0;
while (true) {
ASSIGN_OR_RETURN(uint8_t tile, rom->ReadByte(p5));
dw_map_tiles_[1040 + count + (line * 64)] = tile;
p5++;
count++;
if (count >= 32) {
count = 0;
line++;
if (line >= 32) {
break;
}
}
}
return absl::OkStatus();
}
absl::Status OverworldMapScreen::RenderMapLayer(bool use_dark_world) {
auto& map_data = map_bitmap_.mutable_data();
const auto& tiles8_data = tiles8_bitmap_.vector();
const auto& tile_source = use_dark_world ? dw_map_tiles_ : lw_map_tiles_;
// Render 64x64 tiles (each 8x8 pixels) into 512x512 bitmap
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];
}
}
}
}
}
// Copy pixel data to SDL surface
map_bitmap_.UpdateSurfacePixels();
return absl::OkStatus();
}
absl::Status OverworldMapScreen::Save(Rom* rom) {
// Write data back in the same interleaved format
const int base_addr = 0x054727;
int p1 = base_addr + 0x0000;
int p2 = base_addr + 0x0400;
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;
count++;
continue;
}
} else {
RETURN_IF_ERROR(rom->WriteByte(p2, lw_map_tiles_[count]));
p2++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
count++;
continue;
}
}
} else {
if (!rSide) {
RETURN_IF_ERROR(rom->WriteByte(p3, lw_map_tiles_[count]));
p3++;
if (cSide >= 31) {
cSide = 0;
rSide = true;
count++;
continue;
}
} else {
RETURN_IF_ERROR(rom->WriteByte(p4, lw_map_tiles_[count]));
p4++;
if (cSide >= 31) {
cSide = 0;
rSide = false;
count++;
continue;
}
}
}
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)]));
p5++;
count++;
if (count >= 32) {
count = 0;
line++;
if (line >= 32) {
break;
}
}
}
return absl::OkStatus();
}
absl::Status OverworldMapScreen::LoadCustomMap(const std::string& file_path) {
// Load custom map from external binary file
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
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_);
return absl::OkStatus();
}
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);
}
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();
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,94 @@
#ifndef YAZE_APP_ZELDA3_OVERWORLD_MAP_SCREEN_H
#define YAZE_APP_ZELDA3_OVERWORLD_MAP_SCREEN_H
#include <array>
#include "absl/status/status.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/types/snes_palette.h"
#include "app/rom.h"
namespace yaze {
namespace zelda3 {
/**
* @brief OverworldMapScreen manages the overworld map (pause menu) graphics.
*
* The overworld map screen shows the mini-map when the player pauses.
* It consists of:
* - 64x64 tiles (8x8 pixels each) for Light World map
* - 64x64 tiles (8x8 pixels each) for Dark World map
* - Mode 7 graphics stored at 0x0C4000
* - Tile data in interleaved format across 4 sections
*/
class OverworldMapScreen {
public:
/**
* @brief Initialize and load overworld map data from ROM
* @param rom ROM instance to read data from
*/
absl::Status Create(Rom* rom);
/**
* @brief Save changes back to ROM
* @param rom ROM instance to write data to
*/
absl::Status Save(Rom* rom);
// 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_; }
// 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_; }
/**
* @brief Render map tiles into bitmap
* @param use_dark_world If true, render DW tiles, otherwise LW tiles
*/
absl::Status RenderMapLayer(bool use_dark_world);
/**
* @brief Load custom map from external binary file
* @param file_path Path to .bin file containing 64×64 tile indices
*/
absl::Status LoadCustomMap(const std::string& file_path);
/**
* @brief Save map data to external binary file
* @param file_path Path to output .bin file
* @param use_dark_world If true, save DW tiles, otherwise LW tiles
*/
absl::Status SaveCustomMap(const std::string& file_path, bool use_dark_world);
private:
/**
* @brief Load map tile data from ROM
* Reads the interleaved tile format from 4 ROM sections
*/
absl::Status LoadMapData(Rom* rom);
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
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_OVERWORLD_MAP_SCREEN_H

View File

@@ -0,0 +1,718 @@
#include "title_screen.h"
#include <cstdint>
#include "app/gfx/core/bitmap.h"
#include "app/gfx/resource/arena.h"
#include "app/rom.h"
#include "app/snes.h"
namespace yaze {
namespace zelda3 {
absl::Status TitleScreen::Create(Rom* rom) {
if (!rom || !rom->is_loaded()) {
return absl::InvalidArgumentError("ROM is not loaded");
}
// Initialize bitmaps for each layer
tiles8_bitmap_.Create(128, 512, 8, std::vector<uint8_t>(0x20000));
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
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().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)
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;
title_composite_bitmap_.metadata().source_type = "screen_buffer";
title_composite_bitmap_.metadata().palette_colors = 64;
// Initialize tilemap buffers
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]
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];
size_t added = 0;
for (size_t i = 0; i < 8 && i < src.size(); i++) {
palette_.AddColor(src[i]);
added++;
}
// Pad with black if less than 8 colors
while (added < 8) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
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];
size_t added = 0;
for (size_t i = 0; i < 8 && i < src.size(); i++) {
palette_.AddColor(src[i]);
added++;
}
while (added < 8) {
palette_.AddColor(gfx::SnesColor(0, 0, 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
for (int pal = 0; pal < 2; pal++) {
size_t added = 0;
for (size_t i = 0; i < 8 && i < src.size(); i++) {
palette_.AddColor(src[i]);
added++;
}
while (added < 8) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
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
size_t added = 0;
for (size_t i = 0; i < 8 && i < src.size(); i++) {
palette_.AddColor(src[i]);
added++;
}
while (added < 8) {
palette_.AddColor(gfx::SnesColor(0, 0, 0));
added++;
}
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
for (int pal = 0; pal < 2; pal++) {
size_t added = 0;
for (size_t i = 0; i < 8 && i < src.size(); i++) {
palette_.AddColor(src[i]);
added++;
}
while (added < 8) {
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", "Built composite palette: %zu colors (should be 64)", palette_.size());
// Build tile16 blockset from graphics
RETURN_IF_ERROR(BuildTileset(rom));
// Load tilemap data from ROM
RETURN_IF_ERROR(LoadTitleScreen(rom));
return absl::OkStatus();
}
absl::Status TitleScreen::BuildTileset(Rom* rom) {
// Title screen uses specific graphics sheets
// Load sheet configuration from ROM (matches ZScream implementation)
uint8_t staticgfx[16] = {0};
// 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",
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",
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++) {
ASSIGN_OR_RETURN(staticgfx[i], rom->ReadByte(main_gfx_offset + i));
}
// Load sprite graphics sheets (slots 8-12) - matches ZScream logic
// Sprite GFX groups are after the 37 main groups (37 * 8 = 296 bytes)
// 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
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
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
// 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);
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() &&
dest_offset + 0x1000 <= tiles8_data.size()) {
std::copy(gfx_buffer.begin() + source_offset,
gfx_buffer.begin() + source_offset + 0x1000,
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]);
} else {
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 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());
}
// Queue texture creation via Arena's deferred system
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
// For now, we'll use the tile8 data directly
return absl::OkStatus();
}
absl::Status TitleScreen::LoadTitleScreen(Rom* rom) {
// Check if ROM uses ZScream's expanded format (data at 0x108000 PC)
// by reading the title screen pointer at 0x137A+3, 0x1383+3, 0x138C+3
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);
// Initialize buffers with default empty tile
for (int i = 0; i < 1024; i++) {
tiles_bg1_buffer_[i] = 0x492;
tiles_bg2_buffer_[i] = 0x492;
}
// 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);
// 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);
// 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");
}
// 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",
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
int index = (dest_addr - 0x1000) / 2;
if (index < 1024) {
tiles_bg1_buffer_[index] = tiledata;
total_entries++;
}
} else if (dest_addr < 0x0800) {
// BG2 layer
int index = dest_addr / 2;
if (index < 1024) {
tiles_bg2_buffer_[index] = tiledata;
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)",
total_entries, blocks_read);
}
pal_selected_ = 2;
// Render tilemaps into bitmap pixels
RETURN_IF_ERROR(RenderBG1Layer());
RETURN_IF_ERROR(RenderBG2Layer());
// Apply palettes to layer bitmaps AFTER rendering
tiles_bg1_bitmap_.SetPalette(palette_);
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);
oam_bg_bitmap_.set_active(true);
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_);
// Initial composite render (both layers visible)
RETURN_IF_ERROR(RenderCompositeLayer(true, true));
return absl::OkStatus();
}
absl::Status TitleScreen::RenderBG1Layer() {
// BG1 layer is 32x32 tiles (256x256 pixels)
auto& bg1_data = tiles_bg1_bitmap_.mutable_data();
const auto& tile8_bitmap_data = tiles8_bitmap_.vector();
// Render each tile in the 32x32 tilemap
for (int tile_y = 0; tile_y < 32; tile_y++) {
for (int tile_x = 0; tile_x < 32; tile_x++) {
int tilemap_index = tile_y * 32 + tile_x;
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
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);
}
// 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)
int src_tile_x = (tile_in_sheet % 16) * 8;
int src_tile_y = (sheet_index * 32) + ((tile_in_sheet / 16) * 8);
// Copy 8x8 tile pixels from tile8 bitmap to BG1 bitmap
for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) {
// Apply flipping
int src_px = h_flip ? (7 - px) : px;
int src_py = v_flip ? (7 - py) : py;
// 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 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()) {
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)
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_);
return absl::OkStatus();
}
absl::Status TitleScreen::RenderBG2Layer() {
// BG2 layer is 32x32 tiles (256x256 pixels)
auto& bg2_data = tiles_bg2_bitmap_.mutable_data();
const auto& tile8_bitmap_data = tiles8_bitmap_.vector();
// Render each tile in the 32x32 tilemap
for (int tile_y = 0; tile_y < 32; tile_y++) {
for (int tile_x = 0; tile_x < 32; tile_x++) {
int tilemap_index = tile_y * 32 + tile_x;
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
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)
int src_tile_x = (tile_in_sheet % 16) * 8;
int src_tile_y = (sheet_index * 32) + ((tile_in_sheet / 16) * 8);
// Copy 8x8 tile pixels from tile8 bitmap to BG2 bitmap
for (int py = 0; py < 8; py++) {
for (int px = 0; px < 8; px++) {
// Apply flipping
int src_px = h_flip ? (7 - px) : px;
int src_py = v_flip ? (7 - py) : py;
// 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 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()) {
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)
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_);
return absl::OkStatus();
}
absl::Status TitleScreen::Save(Rom* rom) {
if (!rom || !rom->is_loaded()) {
return absl::InvalidArgumentError("ROM is not loaded");
}
// 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) {
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) {
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();
}
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++) {
uint8_t pixel = bg1_data[i];
// Check if color 0 in the sub-palette (transparent)
// Pixel format is (palette<<3) | color, so color is bits 0-2
if ((pixel & 0x07) != 0) {
composite_data[i] = pixel;
}
}
}
// Copy pixel data to SDL surface
title_composite_bitmap_.UpdateSurfacePixels();
// Queue texture update
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &title_composite_bitmap_);
return absl::OkStatus();
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,107 @@
#ifndef YAZE_APP_ZELDA3_SCREEN_H
#define YAZE_APP_ZELDA3_SCREEN_H
#include "absl/status/status.h"
#include "app/gfx/core/bitmap.h"
#include "app/gfx/render/tilemap.h"
#include "app/gfx/types/snes_palette.h"
#include "app/gfx/types/snes_tile.h"
#include "app/rom.h"
namespace yaze {
namespace zelda3 {
/**
* @brief TitleScreen manages the title screen graphics and tilemap data.
*
* The title screen consists of three layers:
* - BG1: Main logo and graphics
* - BG2: Background elements
* - OAM: Sprite layer (sword, etc.)
*
* Each layer is stored as a 32x32 tilemap (0x400 tiles = 0x1000 bytes as words)
*/
class TitleScreen {
public:
/**
* @brief Initialize and load title screen data from ROM
* @param rom ROM instance to read data from
*/
absl::Status Create(Rom* rom);
// Accessors for layer data
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_; }
// Accessors for bitmaps
auto& bg1_bitmap() { return tiles_bg1_bitmap_; }
auto& bg2_bitmap() { return tiles_bg2_bitmap_; }
auto& oam_bitmap() { return oam_bg_bitmap_; }
auto& tiles8_bitmap() { return tiles8_bitmap_; }
auto& composite_bitmap() { return title_composite_bitmap_; }
auto& blockset() { return tile16_blockset_; }
// Palette access
auto& palette() { return palette_; }
// Save changes back to ROM
absl::Status Save(Rom* rom);
/**
* @brief Render BG1 tilemap into bitmap pixels
* Converts tile IDs from tiles_bg1_buffer_ into pixel data
*/
absl::Status RenderBG1Layer();
/**
* @brief Render BG2 tilemap into bitmap pixels
* Converts tile IDs from tiles_bg2_buffer_ into pixel data
*/
absl::Status RenderBG2Layer();
/**
* @brief Render composite layer with BG1 on top of BG2 with transparency
* @param show_bg1 Whether to include BG1 layer
* @param show_bg2 Whether to include BG2 layer
*/
absl::Status RenderCompositeLayer(bool show_bg1, bool show_bg2);
private:
/**
* @brief Build the tile16 blockset from ROM graphics
* @param rom ROM instance to read graphics from
*/
absl::Status BuildTileset(Rom* rom);
/**
* @brief Load title screen tilemap data from ROM
* @param rom ROM instance to read tilemap from
*/
absl::Status LoadTitleScreen(Rom* rom);
int pal_selected_ = 2;
std::array<uint16_t, 0x1000> tiles_bg1_buffer_; // BG1 tilemap (32x32 tiles)
std::array<uint16_t, 0x1000> tiles_bg2_buffer_; // BG2 tilemap (32x32 tiles)
gfx::OamTile oam_data_[10];
gfx::Bitmap tiles_bg1_bitmap_; // Rendered BG1 layer
gfx::Bitmap tiles_bg2_bitmap_; // Rendered BG2 layer
gfx::Bitmap oam_bg_bitmap_; // Rendered OAM layer
gfx::Bitmap tiles8_bitmap_; // 8x8 tile graphics
gfx::Bitmap title_composite_bitmap_; // Composite BG1+BG2 with transparency
gfx::Tilemap tile16_blockset_; // 16x16 tile blockset
gfx::SnesPalette palette_; // Title screen palette
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_SCREEN_H

View File

@@ -0,0 +1,41 @@
#ifndef YAZE_APP_ZELDA3_SPRITE_OVERLORD_H
#define YAZE_APP_ZELDA3_SPRITE_OVERLORD_H
#include <string>
namespace yaze {
namespace zelda3 {
static const std::string kOverlordNames[] = {
"Overlord_SpritePositionTarget",
"Overlord_AllDirectionMetalBallFactory",
"Overlord_CascadeMetalBallFactory",
"Overlord_StalfosFactory",
"Overlord_StalfosTrap",
"Overlord_SnakeTrap",
"Overlord_MovingFloor",
"Overlord_ZolFactory",
"Overlord_WallMasterFactory",
"Overlord_CrumbleTilePath 1",
"Overlord_CrumbleTilePath 2",
"Overlord_CrumbleTilePath 3",
"Overlord_CrumbleTilePath 4",
"Overlord_CrumbleTilePath 5",
"Overlord_CrumbleTilePath 6",
"Overlord_PirogusuFactory 1",
"Overlord_PirogusuFactory 2",
"Overlord_PirogusuFactory 3",
"Overlord_PirogusuFactory 4",
"Overlord_FlyingTileFactory",
"Overlord_WizzrobeFactory",
"Overlord_ZoroFactory",
"Overlord_StalfosTrapTriggerWindow",
"Overlord_RedStalfosTrap",
"Overlord_ArmosCoordinator",
"Overlord_BombTrap",
};
}
} // namespace yaze
#endif // YAZE_APP_ZELDA3_SPRITE_OVERLORD_H

932
src/zelda3/sprite/sprite.cc Normal file
View File

@@ -0,0 +1,932 @@
#include "sprite.h"
#include <cstdint>
#include <iostream>
namespace yaze {
namespace zelda3 {
void Sprite::UpdateMapProperties(uint16_t map_id) {
map_x_ = x_;
map_y_ = y_;
name_ = kSpriteDefaultNames[id_];
}
void Sprite::UpdateCoordinates(int map_x, int map_y) {
map_x_ = map_x;
map_y_ = map_y;
}
void Sprite::Draw() {
uint8_t x = nx_;
uint8_t y = ny_;
if (overlord_ == 0x07) {
if (id_ == 0x1A) {
DrawSpriteTile((x * 16), (y * 16), 14, 6, 11); // bomb
} else if (id_ == 0x05) {
DrawSpriteTile((x * 16), (y * 16) - 12, 12, 16, 12, false, true);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 12, false, true);
} else if (id_ == 0x06) {
DrawSpriteTile((x * 16), (y * 16), 10, 26, 14, true, true, 2,
2); // snek
} else if (id_ == 0x09) {
DrawSpriteTile((x * 16), (y * 16), 6, 26, 14);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 8, 26, 14);
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 27, 14, false, false, 1, 1);
} else if (id_ == 0x14) {
DrawSpriteTile((x * 16), (y * 16) + 8, 12, 06, 12, false, false, 2,
1); // shadow tile
DrawSpriteTile((x * 16), (y * 16) - 8, 3, 29, 8, false, false, 1,
1); // tile
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 3, 29, 8, true, false, 1,
1); // tile
DrawSpriteTile((x * 16), (y * 16), 3, 29, 8, false, true, 1,
1); // tile
DrawSpriteTile((x * 16) + 8, (y * 16), 3, 29, 8, true, true, 1,
1); // tile
} else {
DrawSpriteTile((x * 16), (y * 16), 4, 4, 5);
}
if (nx_ != x || ny_ != y) {
bounding_box_.x = (lower_x_ + (nx_ * 16));
bounding_box_.y = (lower_y_ + (ny_ * 16));
bounding_box_.w = width_;
bounding_box_.h = height_;
} else {
bounding_box_.x = (lower_x_ + (x * 16));
bounding_box_.y = (lower_y_ + (y * 16));
bounding_box_.w = width_;
bounding_box_.h = height_;
}
return;
}
if (id_ == 0x00) {
DrawSpriteTile((x * 16), (y * 16), 4, 28, 10);
} else if (id_ == 0x01) {
DrawSpriteTile((x * 16) - 8, (y * 16), 6, 24, 12, false, false, 2, 2);
DrawSpriteTile((x * 16) + 8, (y * 16), 6, 24, 12, true, false, 2, 2);
} else if (id_ == 0x02) {
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0x04) {
uint8_t p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x05) {
uint8_t p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x06) {
uint8_t p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x07) {
uint8_t p = 3;
DrawSpriteTile((x * 16), (y * 16), 14, 28, p);
DrawSpriteTile((x * 16), (y * 16), 14, 30, p);
} else if (id_ == 0x08) {
DrawSpriteTile((x * 16), (y * 16), 0, 24, 6);
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 6, false, false, 1, 1);
} else if (id_ == 0x09) {
DrawSpriteTile((x * 16) - 22, (y * 16) - 24, 12, 24, 12, false, false, 2,
2); // Moldorm tail
DrawSpriteTile((x * 16) - 16, (y * 16) - 20, 8, 24, 12, false, false, 2,
2); // Moldorm b2
DrawSpriteTile((x * 16) - 12, (y * 16) - 16, 4, 24, 12, false, false, 4,
4); // Moldorm b
DrawSpriteTile((x * 16), (y * 16), 0, 24, 12, false, false, 4,
4); // Moldorm head
DrawSpriteTile((x * 16) + 20, (y * 16) + 12, 8, 26, 14, false, false, 2,
2); // Moldorm eye
DrawSpriteTile((x * 16) + 12, (y * 16) + 20, 8, 26, 14, false, false, 2,
2); // Moldorm eye
} else if (id_ == 0x0A) {
DrawSpriteTile((x * 16), (y * 16), 0, 24, 8);
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 8, false, false, 1, 1);
} else if (id_ == 0x0B) {
DrawSpriteTile((x * 16), (y * 16), 10, 30, 10);
} else if (id_ == 0x0C) {
DrawSpriteTile((x * 16), (y * 16), 0, 24, 8);
DrawSpriteTile((x * 16) + 4, (y * 16) + 6, 0, 24, 8, false, false, 1, 1);
} else if (id_ == 0x0D) {
DrawSpriteTile((x * 16), (y * 16), 14, 28, 12);
} else if (id_ == 0x0E) {
DrawSpriteTile((x * 16), (y * 16), 8, 18, 10, false, false, 3, 2);
} else if (id_ == 0x0F) {
DrawSpriteTile((x * 16), (y * 16), 14, 24, 8, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 30, 8, 8, true, false, 1, 3);
} else if (id_ == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 12, 31, 8, false, false, 1, 1);
} else if (id_ == 0x11) {
DrawSpriteTile((x * 16), (y * 16) + 16, 6, 16, 8, false, false, 2,
2); // Feet
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 4, 18, 8, false, false, 2,
2); // Body1
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 4, 18, 8, true, false, 2,
2); // Body2
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8, false, false, 2,
2); // Head
} else if (id_ == 0x12) {
DrawSpriteTile((x * 16), (y * 16) + 8, 8, 26, 8);
DrawSpriteTile((x * 16), (y * 16), 6, 24, 8);
} else if (id_ == 0x13) {
DrawSpriteTile((x * 16), (y * 16), 4, 22, 2);
} else if (id_ == 0x15) {
// Antifairy
DrawSpriteTile((x * 16) + 2, (y * 16) + 8, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 2, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 14, (y * 16) + 8, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 14, 3, 30, 5, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 1, 30, 5, false, false, 1,
1); // Middle
} else if (id_ == 0x16) {
DrawSpriteTile((x * 16), (y * 16) + 8, 2, 26, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 26, 2);
} else if (id_ == 0x17) // Bush hoarder
{
DrawSpriteTile((x * 16), (y * 16), 8, 30, 10);
} else if (id_ == 0x18) // Mini moldorm
{
DrawSpriteTile((x * 16) + 13, (y * 16) + 17, 13, 21, 8, false, false, 1,
1); // Tail
DrawSpriteTile((x * 16) + 5, (y * 16) + 8, 2, 22, 8); // Body
DrawSpriteTile((x * 16), (y * 16), 0, 22, 8); // Head
DrawSpriteTile((x * 16), (y * 16) - 4, 13, 20, 8, false, false, 1,
1); // Eyes
DrawSpriteTile((x * 16) - 4, (y * 16), 13, 20, 8, false, false, 1,
1); // Eyes
} else if (id_ == 0x19) // Poe - ghost
{
DrawSpriteTile((x * 16), (y * 16), 6, 31, 2); //
} else if (id_ == 0x1A) // Smith
{
// DrawSpriteTile((x*16), (y *16), 2, 4, 10,true); // Smitty
// DrawSpriteTile((x*16)+12, (y *16) - 7, 0, 6, 10); // Hammer
DrawSpriteTile((x * 16), (y * 16), 4, 22, 10);
} else if (id_ == 0x1C) // Statue
{
DrawSpriteTile((x * 16), (y * 16) + 8, 0, 28, 15);
DrawSpriteTile((x * 16), (y * 16), 2, 28, 15, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 28, 15, true, false, 1, 1);
} else if (id_ == 0x1E) // Crystal switch
{
DrawSpriteTile((x * 16), (y * 16), 4, 30, 5);
} else if (id_ == 0x1F) // Sick kid
{
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 10, 16, 14);
DrawSpriteTile((x * 16) + 16 - 8, (y * 16) + 8, 10, 16, 14, true);
DrawSpriteTile((x * 16) - 8, (y * 16) + 16, 10, 16, 14, false, true, 2, 2);
DrawSpriteTile((x * 16) + 16 - 8, (y * 16) + 16, 10, 16, 14, true, true, 2,
2);
DrawSpriteTile((x * 16), (y * 16) - 4, 14, 16, 10);
} else if (id_ == 0x20) {
DrawSpriteTile((x * 16), (y * 16), 2, 24, 7);
} else if (id_ == 0x21) // Push switch
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 20, 13, 29, 3, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) + 28, 12, 29, 3, false, false, 1, 1);
DrawSpriteTile((x * 16), (y * 16) + 8, 10, 28, 3);
} else if (id_ == 0x22) // Rope
{
DrawSpriteTile((x * 16), (y * 16), 8, 26, 5);
} else if (id_ == 0x23) // Red bari
{
DrawSpriteTile((x * 16), (y * 16), 2, 18, 4, false, false, 1, 2);
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 18, 4, true, false, 1, 2);
} else if (id_ == 0x24) // Blue bari
{
DrawSpriteTile((x * 16), (y * 16), 2, 18, 6, false, false, 1, 2);
DrawSpriteTile((x * 16) + 8, (y * 16), 2, 18, 6, true, false, 1, 2);
} else if (id_ == 0x25) // Talking tree?
{
// TODO: Add something here?
} else if (id_ == 0x26) // Hardhat beetle
{
if ((x & 0x01) == 0x00) {
DrawSpriteTile((x * 16), (y * 16), 4, 20, 8);
DrawSpriteTile((x * 16), (y * 16) - 6, 0, 20, 8);
} else {
DrawSpriteTile((x * 16), (y * 16), 4, 20, 10);
DrawSpriteTile((x * 16), (y * 16) - 6, 0, 20, 10);
}
} else if (id_ == 0x27) // Deadrock
{
DrawSpriteTile((x * 16), (y * 16), 2, 30, 10);
} else if (id_ == 0x28) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x29) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2A) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2B) // ???
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2C) // Lumberjack
{
DrawSpriteTile((x * 16) - 24, (y * 16) + 12, 6, 26, 12, true); // Body
DrawSpriteTile((x * 16) - 24, (y * 16), 8, 26, 12, true); // Head
DrawSpriteTile((x * 16) - 14, (y * 16) + 12, 14, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) - 6, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 2, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 10, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 18, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 26, (y * 16) + 12, 15, 27, 10, false, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 34, (y * 16) + 12, 14, 27, 10, true, false, 1,
1); // Saw left edge
DrawSpriteTile((x * 16) + 40, (y * 16) + 12, 4, 26, 12); // Body
DrawSpriteTile((x * 16) + 40, (y * 16), 8, 26, 12); // Head
} else if (id_ == 0x2D) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2E) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x2F) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x30) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x31) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x32) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
}
/*
else if (id_== 0x33) // Pull for rupees
{
}
*/
else if (id_ == 0x34) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x35) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x36) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
}
/*
else if (id_== 0x37) // Waterfall
{
DrawSpriteTile((x*16), (y *16), 14, 6, 10);
}
*/
else if (id_ == 0x38) // Arrowtarget
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x39) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3A) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3B) // Dash item
{
} else if (id_ == 0x3C) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3D) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3E) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x3F) // Npcs
{
DrawSpriteTile((x * 16), (y * 16), 14, 22, 10);
} else if (id_ == 0x40) // Lightning lock (agah tower)
{
DrawSpriteTile((x * 16) - 24, (y * 16), 10, 28, 2, false, false, 1, 2);
DrawSpriteTile((x * 16) - 16, (y * 16), 6, 30, 2);
DrawSpriteTile((x * 16), (y * 16), 8, 30, 2);
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 30, 2);
DrawSpriteTile((x * 16) + 24, (y * 16), 10, 28, 2, false, false, 1, 2);
} else if (id_ == 0x41) // Blue soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 10, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 10, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 14, 22, 10, false, true, 1,
2); // Sword
} else if (id_ == 0x42) // Green soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 12, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 12, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 14, 22, 12, false, true, 1,
2); // Sword
} else if (id_ == 0x43) // Red spear soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 8, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 8);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 8, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 11, 22, 8, false, true, 1,
2); // Spear
} else if (id_ == 0x44) // Sword blue holding up
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 10);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 14, 22, 10, false, true, 1,
2); // Sword
} else if (id_ == 0x45) // Green spear soldier
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 12, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 13, 22, 12, false, false, 1,
2); // Shield
DrawSpriteTile((x * 16) - 4, (y * 16) + 16, 11, 22, 12, false, true, 1,
2); // Spear
} else if (id_ == 0x46) // Blue archer
{
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 10);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 6, 20, 10, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10); // Head
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 16, 10, false, false, 1,
1); // Bow1
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 10, 16, 10, true, false, 1,
1); // Bow2
} else if (id_ == 0x47) // Green archer
{
DrawSpriteTile((x * 16), (y * 16) + 8, 14, 16, 12);
DrawSpriteTile((x * 16), (y * 16), 0, 20, 12);
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 16, 12, false, false, 1,
1); // Bow1
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 10, 16, 12, true, false, 1,
1); // Bow2
} else if (id_ == 0x48) // Javelin soldier red
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 11, 22, 8, false, true, 1,
2); // Sword
} else if (id_ == 0x49) // Javelin soldier red from bush
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 18, 8); // Head
DrawSpriteTile((x * 16), (y * 16) + 24, 0, 20, 2);
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 11, 22, 8, false, true, 1,
2); // Sword
} else if (id_ == 0x4A) // Red bomb soldier
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 8);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 8, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8); // Head
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 14, 22, 11); // Bomb
} else if (id_ == 0x4B) // Green soldier recruit
{
// 0,4
DrawSpriteTile((x * 16), (y * 16), 6, 24, 12);
DrawSpriteTile((x * 16), (y * 16) - 10, 0, 20, 12);
} else if (id_ == 0x4C) // Jazzhand
{
DrawSpriteTile((x * 16), (y * 16), 0, 26, 14, false, false, 6, 2);
} else if (id_ == 0x4D) // Rabit??
{
DrawSpriteTile((x * 16), (y * 16), 0, 26, 12, false, false, 6, 2);
} else if (id_ == 0x4E) // Popo1
{
DrawSpriteTile((x * 16), (y * 16), 0, 20, 10);
} else if (id_ == 0x4F) // Popo2
{
DrawSpriteTile((x * 16), (y * 16), 2, 20, 10);
} else if (id_ == 0x50) // Canon ball
{
DrawSpriteTile((x * 16), (y * 16), 0, 24, 10);
} else if (id_ == 0x51) // Armos
{
DrawSpriteTile((x * 16), (y * 16), 0, 28, 11, false, false, 2, 4);
} else if (id_ == 0x53) // Armos Knight
{
DrawSpriteTile((x * 16), (y * 16), 0, 28, 10, false, false, 4, 4);
} else if (id_ == 0x54) {
DrawSpriteTile((x * 16), (y * 16), 2, 28, 12);
DrawSpriteTile((x * 16) + 8, (y * 16) + 10, 6, 28, 12);
DrawSpriteTile((x * 16) + 16, (y * 16) + 18, 10, 28, 12);
} else if (id_ == 0x55) // Fireball Zora
{
DrawSpriteTile((x * 16), (y * 16), 4, 26, 11);
} else if (id_ == 0x56) // Zora
{
DrawSpriteTile((x * 16), (y * 16), 10, 20, 2);
DrawSpriteTile((x * 16), (y * 16) + 8, 8, 30, 2);
} else if (id_ == 0x57) // Desert Rocks
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 2, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16), 14, 24, 2, true, false, 2, 4);
} else if (id_ == 0x58) // Crab
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 12);
DrawSpriteTile((x * 16) + 16, (y * 16), 14, 24, 12, true);
} else if (id_ == 0x5B) // Spark
{
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4);
} else if (id_ == 0x5C) // Spark
{
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4, true);
} else if (id_ == 0x5D) // Roller vertical1
{
// Subset3
if (((y * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16) + 8 + (i * 16), (y * 16), 9, 24, 11);
}
DrawSpriteTile((x * 16) + (16 * 7), (y * 16), 8, 24, 11, true);
} else {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 32, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 48, (y * 16), 8, 24, 11, true);
}
} else if (id_ == 0x5E) // Roller vertical2
{
// Subset3
if (((y * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16) + 8 + (i * 16), (y * 16), 9, 24, 11);
}
DrawSpriteTile((x * 16) + (16 * 7), (y * 16), 8, 24, 11, true);
} else {
DrawSpriteTile((x * 16), (y * 16), 8, 24, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 32, (y * 16), 9, 24, 11);
DrawSpriteTile((x * 16) + 48, (y * 16), 8, 24, 11, true);
}
} else if (id_ == 0x5F) // Roller horizontal
{
if (((x * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + 16, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 48, 14, 24, 11, false, true);
} else {
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16), (y * 16) + i * 16, 14, 25, 11);
}
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + (7 * 16), 14, 24, 11, false, true);
}
} else if (id_ == 0x60) // Roller horizontal2 (right to left)
{
// Subset3
if (((x * 16) & 0x10) == 0x10) {
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + 16, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 25, 11);
DrawSpriteTile((x * 16), (y * 16) + 48, 14, 24, 11, false, true);
} else {
for (int i = 0; i < 7; i++) {
DrawSpriteTile((x * 16), (y * 16) + i * 16, 14, 25, 11);
}
DrawSpriteTile((x * 16), (y * 16), 14, 24, 11);
DrawSpriteTile((x * 16), (y * 16) + (7 * 16), 14, 24, 11, false, true);
}
} else if (id_ == 0x61) // Beamos
{
DrawSpriteTile((x * 16), (y * 16) - 16, 8, 20, 14, false, false, 2, 4);
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 10, 20, 14, false, false, 1, 1);
} else if (id_ == 0x63) // Devalant non-shooter
{
DrawSpriteTile((x * 16) - 8, (y * 16) - 8, 2, 16, 2);
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 2, 16, 2, true);
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 2, 16, 2, false, true);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 2, 16, 2, true, true);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0x64) // Devalant non-shooter
{
DrawSpriteTile((x * 16) - 8, (y * 16) - 8, 2, 16, 2);
DrawSpriteTile((x * 16) + 8, (y * 16) - 8, 2, 16, 2, true);
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 2, 16, 2, false, true);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 2, 16, 2, true, true);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 8);
} else if (id_ == 0x66) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 14, 16, 14, true);
} else if (id_ == 0x67) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 14, 16, 14);
} else if (id_ == 0x68) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 12, 16, 14);
} else if (id_ == 0x69) // Moving wall canon right
{
DrawSpriteTile((x * 16), (y * 16), 12, 16, 14, false, true);
} else if (id_ == 0x6A) // Chainball soldier
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 14);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 14, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 14); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) - 16, 10, 18, 14); // Ball
} else if (id_ == 0x6B) // Cannon soldier
{
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 6, 16, 14);
DrawSpriteTile((x * 16) - 4, (y * 16) + 8, 6, 20, 14, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 14); // Head
DrawSpriteTile((x * 16) + 12, (y * 16) + 8, 4, 18, 14); // Cannon
} else if (id_ == 0x6C) // Mirror portal
{
// Useless
} else if (id_ == 0x6D) // Rat
{
DrawSpriteTile((x * 16), (y * 16), 14, 24, 5);
} else if (id_ == 0x6E) // Rope
{
DrawSpriteTile((x * 16), (y * 16), 10, 26, 5);
} else if (id_ == 0x6F) {
DrawSpriteTile((x * 16), (y * 16), 4, 24, 10);
} else if (id_ == 0x70) // Helma fireball
{
DrawSpriteTile((x * 16), (y * 16), 10, 28, 4);
} else if (id_ == 0x71) // Leever
{
DrawSpriteTile((x * 16), (y * 16), 6, 16, 4);
} else if (id_ == 0x73) // Uncle priest
{
} else if (id_ == 0x79) // Bee
{
DrawSpriteTile((x * 16), (y * 16), 4, 14, 11, false, false, 1, 1);
} else if (id_ == 0x7C) // Skull head
{
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0x7D) // Big spike
{
DrawSpriteTile((x * 16), (y * 16), 4, 28, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 4, 28, 11, true);
DrawSpriteTile((x * 16), (y * 16) + 16, 4, 28, 11, false, true);
DrawSpriteTile((x * 16) + 16, (y * 16) + 16, 4, 28, 11, true, true);
} else if (id_ == 0x7E) // Guruguru clockwise
{
DrawSpriteTile((x * 16), (y * 16) - 14, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 28, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 42, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 56, 8, 18, 4);
} else if (id_ == 0x7F) // Guruguru Counterclockwise
{
DrawSpriteTile((x * 16), (y * 16) - 14, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 28, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 42, 8, 18, 4);
DrawSpriteTile((x * 16), (y * 16) - 56, 8, 18, 4);
} else if (id_ == 0x80) // Winder (moving firebar)
{
DrawSpriteTile((x * 16), (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 14, (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 28, (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 42, (y * 16), 8, 18, 4);
DrawSpriteTile((x * 16) - 56, (y * 16), 8, 18, 4);
} else if (id_ == 0x81) // Water tektite
{
DrawSpriteTile((x * 16), (y * 16), 0, 24, 11);
} else if (id_ == 0x82) // circle antifairy
{
// Antifairy top
DrawSpriteTile((x * 16 + 2) - 4, (y * 16 + 8) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 2) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) - 4, (y * 16 + 8) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 14) - 16, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 8) - 16, 1, 30, 5, false, false,
1, 1); // Middle
// Left
DrawSpriteTile((x * 16 + 2) - 16, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 2) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) - 16, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 14) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 16, (y * 16 + 8) - 4, 1, 30, 5, false, false,
1, 1); // Middle
DrawSpriteTile((x * 16 + 2) - 4, (y * 16 + 8) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 2) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) - 4, (y * 16 + 8) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 14) + 8, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) - 4, (y * 16 + 8) + 8, 1, 30, 5, false, false,
1, 1); // Middle
// Left
DrawSpriteTile((x * 16 + 2) + 8, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 2) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 14) + 8, (y * 16 + 8) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 14) - 4, 3, 30, 5, false, false,
1, 1);
DrawSpriteTile((x * 16 + 8) + 8, (y * 16 + 8) - 4, 1, 30, 5, false, false,
1, 1); // Middle
} else if (id_ == 0x83) // Green eyegore
{
DrawSpriteTile((x * 16), (y * 16), 12, 24, 14, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 14, true, false, 1, 3);
} else if (id_ == 0x84) // Red eyegore
{
DrawSpriteTile((x * 16), (y * 16), 12, 24, 8, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 8, true, false, 1, 3);
} else if (id_ == 0x85) // Yellow stalfos
{
DrawSpriteTile((x * 16), (y * 16), 10, 16, 11);
DrawSpriteTile((x * 16), (y * 16) - 12, 0, 16, 11); // Head
} else if (id_ == 0x86) // Kodongo
{
DrawSpriteTile((x * 16), (y * 16), 4, 26, 14);
} else if (id_ == 0x88) // Mothula
{
DrawSpriteTile((x * 16), (y * 16), 8, 24, 14, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16), 8, 24, 14, true, false, 2, 4);
} else if (id_ == 0x8A) // Spike
{
DrawSpriteTile((x * 16), (y * 16), 6, 30, 15);
} else if (id_ == 0x8B) // Gibdo
{
DrawSpriteTile((x * 16), (y * 16), 10, 24, 14);
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 24, 14);
} else if (id_ == 0x8C) // Arrghus
{
DrawSpriteTile((x * 16), (y * 16), 0, 24, 14, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16), 0, 24, 14, true, false, 2, 4);
} else if (id_ == 0x8D) // Arrghus spawn
{
DrawSpriteTile((x * 16), (y * 16), 6, 24, 14);
} 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);
} else if (id_ == 0x90) // Wall master
{
DrawSpriteTile((x * 16), (y * 16), 6, 26, 12);
DrawSpriteTile((x * 16) + 16, (y * 16), 15, 26, 12, false, false, 1, 1);
DrawSpriteTile((x * 16) + 16, (y * 16) + 8, 9, 26, 12, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16) + 16, 10, 27, 12, false, false, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 16, 8, 27, 12, false, false, 1, 1);
} else if (id_ == 0x91) // Stalfos knight
{
DrawSpriteTile((x * 16) - 2, (y * 16) + 12, 4, 22, 12, false, false, 1, 2);
DrawSpriteTile((x * 16) + 10, (y * 16) + 12, 4, 22, 12, true, false, 1, 2);
DrawSpriteTile((x * 16) - 4, (y * 16) + 4, 1, 22, 12);
DrawSpriteTile((x * 16) + 12, (y * 16) + 4, 3, 22, 12, false, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16) - 8, 6, 20, 12);
} else if (id_ == 0x92) // Helmaking
{
DrawSpriteTile((x * 16), (y * 16) + 32, 14, 26, 14);
DrawSpriteTile((x * 16) + 16, (y * 16) + 32, 0, 28, 14);
DrawSpriteTile((x * 16) + 32, (y * 16) + 32, 14, 26, 14, true);
DrawSpriteTile((x * 16), (y * 16) + 16 + 32, 2, 28, 14);
DrawSpriteTile((x * 16) + 16, (y * 16) + 16 + 32, 4, 28, 14);
DrawSpriteTile((x * 16) + 32, (y * 16) + 16 + 32, 2, 28, 14, true);
DrawSpriteTile((x * 16) + 8, (y * 16) + 32 + 32, 6, 28, 14);
DrawSpriteTile((x * 16) + 24, (y * 16) + 32 + 32, 6, 28, 14, true);
} else if (id_ == 0x93) // Bumper
{
DrawSpriteTile((x * 16), (y * 16), 12, 30, 7);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 30, 7, true);
DrawSpriteTile((x * 16) + 16, (y * 16) + 16, 12, 30, 7, true, true);
DrawSpriteTile((x * 16), (y * 16) + 16, 12, 30, 7, false, true);
} else if (id_ == 0x95) // Right laser eye
{
DrawSpriteTile((x * 16), (y * 16), 9, 28, 3, true, false, 1, 2);
DrawSpriteTile((x * 16), (y * 16) + 16, 9, 28, 3, true, true, 1, 1);
} else if (id_ == 0x96) // Left laser eye
{
DrawSpriteTile((x * 16) + 8, (y * 16) - 4, 9, 28, 3, false, false, 1, 2);
DrawSpriteTile((x * 16) + 8, (y * 16) + 12, 9, 28, 3, false, true, 1, 1);
} else if (id_ == 0x97) // Right laser eye
{
DrawSpriteTile((x * 16), (y * 16), 6, 28, 3, false, false, 2, 1);
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 28, 3, true, false, 1, 1);
} else if (id_ == 0x98) // Right laser eye
{
DrawSpriteTile((x * 16), (y * 16), 6, 28, 3, false, true, 2, 1);
DrawSpriteTile((x * 16) + 16, (y * 16), 6, 28, 3, true, true, 1, 1);
} else if (id_ == 0x99) {
DrawSpriteTile((x * 16), (y * 16), 6, 24, 12);
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 24, 12);
} else if (id_ == 0x9A) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 10, 24, 6);
} else if (id_ == 0x9B) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 6, 24, 11);
DrawSpriteTile((x * 16), (y * 16) - 8, 2, 27, 11, false, false, 2, 1);
} else if (id_ == 0x9C) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 12, 22, 11);
DrawSpriteTile((x * 16) + 16, (y * 16), 13, 22, 11, false, false, 1, 2);
} else if (id_ == 0x9D) // Water bubble kyameron
{
DrawSpriteTile((x * 16), (y * 16), 14, 21, 11);
DrawSpriteTile((x * 16), (y * 16) - 16, 14, 20, 11, false, false, 2, 1);
} else if (id_ == 0xA1) {
DrawSpriteTile((x * 16) - 8, (y * 16) + 8, 6, 26, 14);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 6, 26, 14, true);
} else if (id_ == 0xA2) {
DrawSpriteTile((x * 16), (y * 16) + 8, 0, 24, 14, false, false, 4, 4);
} else if (id_ == 0xA5) {
DrawSpriteTile((x * 16), (y * 16), 0, 26, 10, false, false, 3, 2);
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 0, 24, 10);
} else if (id_ == 0xA6) {
DrawSpriteTile((x * 16), (y * 16), 0, 26, 8, false, false, 3, 2);
DrawSpriteTile((x * 16) + 4, (y * 16) - 8, 0, 24, 8);
} else if (id_ == 0xA7) {
DrawSpriteTile((x * 16), (y * 16) + 12, 12, 16, 10);
DrawSpriteTile((x * 16), (y * 16), 0, 16, 10);
} else if (id_ == 0xAC) {
DrawSpriteTile((x * 16), (y * 16), 5, 14, 4);
} else if (id_ == 0xAD) {
DrawSpriteTile((x * 16), (y * 16) + 8, 14, 10, 10);
DrawSpriteTile((x * 16), (y * 16), 12, 10, 10);
} else if (id_ == 0xBA) {
DrawSpriteTile((x * 16), (y * 16), 14, 14, 6);
} else if (id_ == 0xC1 || id_ == 0x7A) {
DrawSpriteTile((x * 16), (y * 16) - 16, 2, 24, 12, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 2, 24, 12, true, false, 2, 4);
} else if (id_ == 0xC3) {
DrawSpriteTile((x * 16), (y * 16), 10, 14, 12);
} else if (id_ == 0xC4) {
DrawSpriteTile((x * 16), (y * 16), 0, 18, 14);
DrawSpriteTile((x * 16), (y * 16) - 8, 0, 16, 14);
} else if (id_ == 0xC5) {
} else if (id_ == 0xC6) {
DrawSpriteTile((x * 16) + 4, (y * 16) + 14, 3, 30, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) + 14, (y * 16) + 4, 3, 30, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) + 2, 1, 31, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) - 6, (y * 16) + 4, 3, 30, 14, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) - 6, 3, 30, 14, false, false, 1, 1);
} else if (id_ == 0xC7) {
DrawSpriteTile((x * 16), (y * 16), 0, 26, 4);
DrawSpriteTile((x * 16), (y * 16) - 10, 0, 26, 4);
DrawSpriteTile((x * 16), (y * 16) - 20, 0, 26, 4);
DrawSpriteTile((x * 16), (y * 16) - 30, 2, 26, 4);
} else if (id_ == 0xC8) {
DrawSpriteTile((x * 16), (y * 16), 12, 24, 12, false, false, 2, 3);
DrawSpriteTile((x * 16) + 16, (y * 16), 12, 24, 12, true, false, 1, 3);
} else if (id_ == 0xC9) {
DrawSpriteTile((x * 16), (y * 16), 8, 28, 8, false);
DrawSpriteTile((x * 16) + 16, (y * 16), 8, 28, 8, true);
} else if (id_ == 0xCA) {
DrawSpriteTile((x * 16), (y * 16), 8, 10, 10);
} else if (id_ == 0xD0) {
DrawSpriteTile((x * 16), (y * 16), 7, 14, 11, false, false, 3, 2);
DrawSpriteTile((x * 16), (y * 16) - 10, 8, 12, 11);
} else if (id_ == 0xD1) {
DrawSpriteTile((x * 16) + 2, (y * 16) + 8, 7, 13, 11, true, true, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 2, 7, 13, 11, true, false, 1, 1);
DrawSpriteTile((x * 16) + 14, (y * 16) + 8, 7, 13, 11, true, true, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 14, 7, 13, 11, false, true, 1, 1);
DrawSpriteTile((x * 16) + 8, (y * 16) + 8, 7, 13, 11, false, false, 1,
1); // Middle
// 6,7 / 13
} else if (id_ == 0xD4) {
DrawSpriteTile((x * 16) - 4, (y * 16), 0, 7, 7, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16), 0, 7, 7, true, false, 1, 1);
} else if (id_ == 0xE3) // Fairy
{
DrawSpriteTile((x * 16), (y * 16), 10, 14, 10);
} else if (id_ == 0xE4) // Key
{
DrawSpriteTile((x * 16), (y * 16), 11, 06, 11, false, false, 1, 2);
} else if (id_ == 0xE7) // Mushroom
{
DrawSpriteTile((x * 16), (y * 16), 14, 30, 16);
} else if (id_ == 0xE8) // Fake ms
{
DrawSpriteTile((x * 16) + 4, (y * 16), 4, 31, 10, false, false, 1, 1);
DrawSpriteTile((x * 16) + 4, (y * 16) + 8, 5, 31, 10, false, false, 1, 1);
} else if (id_ == 0xEB) {
DrawSpriteTile((x * 16), (y * 16), 0, 14, 5);
} else if (id_ == 0xF2) {
DrawSpriteTile((x * 16), (y * 16) - 16, 12, 24, 2, false, false, 2, 4);
DrawSpriteTile((x * 16) + 16, (y * 16) - 16, 12, 24, 2, true, false, 2, 4);
} else if (id_ == 0xF4) {
DrawSpriteTile((x * 16), (y * 16), 12, 28, 5, false, false, 4, 4);
} else {
DrawSpriteTile((x * 16), (y * 16), 4, 4, 5);
}
bounding_box_.x = (lower_x_ + (x * 16));
bounding_box_.y = (lower_y_ + (y * 16));
bounding_box_.w = width_;
bounding_box_.h = height_;
}
void Sprite::DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
bool mirror_x, bool mirror_y, int sizex,
int sizey) {
if (current_gfx_.empty()) {
return;
}
// Validate input parameters
if (sizex <= 0 || sizey <= 0) {
return;
}
if (srcx < 0 || srcy < 0 || pal < 0) {
return;
}
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;
int my = yl;
if (mirror_x) {
mx = (((sizex * 8) / 2) - 1) - xl;
}
if (mirror_y) {
my = ((sizey * 8) - 1) - yl;
}
// Formula information to get tile index position in the array
//((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
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
}
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()) &&
index <= 4096) {
preview_gfx_[index] = (uint8_t)((pixel & 0x0F) + 112 + (pal * 8));
}
}
}
}
} // namespace zelda3
} // namespace yaze

392
src/zelda3/sprite/sprite.h Normal file
View File

@@ -0,0 +1,392 @@
#ifndef YAZE_APP_ZELDA3_SPRITE_H
#define YAZE_APP_ZELDA3_SPRITE_H
#include <SDL.h>
#include <cstdint>
#include <string>
#include <vector>
#include "zelda3/common.h"
#include "zelda3/sprite/overlord.h"
namespace yaze {
namespace zelda3 {
static const std::string kSpriteDefaultNames[]{
"00 Raven",
"01 Vulture",
"02 Flying Stalfos Head",
"03 No Pointer (Empty",
"04 Pull Switch (good",
"05 Pull Switch (unused",
"06 Pull Switch (bad",
"07 Pull Switch (unused",
"08 Octorock (one way",
"09 Moldorm (Boss",
"0A Octorock (four way",
"0B Chicken",
"0C Octorock (?",
"0D Buzzblock",
"0E Snapdragon",
"0F Octoballoon",
"10 Octoballon Hatchlings",
"11 Hinox",
"12 Moblin",
"13 Mini Helmasaure",
"14 Gargoyle's Domain Gate",
"15 Antifairy",
"16 Sahasrahla / Aginah",
"17 Bush Hoarder",
"18 Mini Moldorm",
"19 Poe",
"1A Dwarves",
"1B Arrow in wall",
"1C Statue",
"1D Weathervane",
"1E Crystal Switch",
"1F Bug-Catching Kid",
"20 Sluggula",
"21 Push Switch",
"22 Ropa",
"23 Red Bari",
"24 Blue Bari",
"25 Talking Tree",
"26 Hardhat Beetle",
"27 Deadrock",
"28 Storytellers",
"29 Blind Hideout attendant",
"2A Sweeping Lady",
"2B Storytellers",
"2C Lumberjacks",
"2D Telepathic Stones",
"2E Multipurpose Sprite",
"2F Race Npc",
"30 Person?",
"31 Fortune Teller",
"32 Angry Brothers",
"33 Pull for items",
"34 Scared Girl",
"35 Innkeeper",
"36 Witch",
"37 Waterfall",
"38 Arrow Target",
"39 Average Middle",
"3A Half Magic Bat",
"3B Dash Item",
"3C Village Kid",
"3D Signs? Chicken lady also showed up / Scared ladies outside houses.",
"3E Rock Hoarder",
"3F Tutorial Soldier",
"40 Lightning Lock",
"41 Blue Sword Soldier / Used by guards to detect player",
"42 Green Sword Soldier",
"43 Red Spear Soldier",
"44 Assault Sword Soldier",
"45 Green Spear Soldier",
"46 Blue Archer",
"47 Green Archer",
"48 Red Javelin Soldier",
"49 Red Javelin Soldier 2",
"4A Red Bomb Soldiers",
"4B Green Soldier Recruits",
"4C Geldman",
"4D Rabbit",
"4E Popo",
"4F Popo 2",
"50 Cannon Balls",
"51 Armos",
"52 Giant Zora",
"53 Armos Knights (Boss",
"54 Lanmolas (Boss",
"55 Fireball Zora",
"56 Walking Zora",
"57 Desert Palace Barriers",
"58 Crab",
"59 Bird",
"5A Squirrel",
"5B Spark (Left to Right",
"5C Spark (Right to Left",
"5D Roller (vertical moving",
"5E Roller (vertical moving",
"5F Roller",
"60 Roller (horizontal moving",
"61 Beamos",
"62 Master Sword",
"63 Devalant (Non",
"64 Devalant (Shooter",
"65 Shooting Gallery Proprietor",
"66 Moving Cannon Ball Shooters (Right",
"67 Moving Cannon Ball Shooters (Left",
"68 Moving Cannon Ball Shooters (Down",
"69 Moving Cannon Ball Shooters (Up",
"6A Ball N' Chain Trooper",
"6B Cannon Soldier",
"6C Mirror Portal",
"6D Rat",
"6E Rope",
"6F Keese",
"70 Helmasaur King Fireball",
"71 Leever",
"72 Activator for the ponds (where you throw in items",
"73 Uncle / Priest",
"74 Running Man",
"75 Bottle Salesman",
"76 Princess Zelda",
"77 Antifairy (Alternate",
"78 Village Elder",
"79 Bee",
"7A Agahnim",
"7B Agahnim Energy Ball",
"7C Hyu",
"7D Big Spike Trap",
"7E Guruguru Bar (Clockwise",
"7F Guruguru Bar (Counter Clockwise",
"80 Winder",
"81 Water Tektite",
"82 Antifairy Circle",
"83 Green Eyegore",
"84 Red Eyegore",
"85 Yellow Stalfos",
"86 Kodongos",
"87 Flames",
"88 Mothula (Boss",
"89 Mothula's Beam",
"8A Spike Trap",
"8B Gibdo",
"8C Arrghus (Boss",
"8D Arrghus spawn",
"8E Terrorpin",
"8F Slime",
"90 Wallmaster",
"91 Stalfos Knight",
"92 Helmasaur King",
"93 Bumper",
"94 Swimmers",
"95 Eye Laser (Right",
"96 Eye Laser (Left",
"97 Eye Laser (Down",
"98 Eye Laser (Up",
"99 Pengator",
"9A Kyameron",
"9B Wizzrobe",
"9C Tadpoles",
"9D Tadpoles",
"9E Ostrich (Haunted Grove",
"9F Flute",
"A0 Birds (Haunted Grove",
"A1 Freezor",
"A2 Kholdstare (Boss",
"A3 Kholdstare's Shell",
"A4 Falling Ice",
"A5 Zazak Fireball",
"A6 Red Zazak",
"A7 Stalfos",
"A8 Bomber Flying Creatures from Darkworld",
"A9 Bomber Flying Creatures from Darkworld",
"AA Pikit",
"AB Maiden",
"AC Apple",
"AD Lost Old Man",
"AE Down Pipe",
"AF Up Pipe",
"B0 Right Pip",
"B1 Left Pipe",
"B2 Good bee again?",
"B3 Hylian Inscription",
"B4 Thief?s chest (not the one that follows you",
"B5 Bomb Salesman",
"B6 Kiki",
"B7 Maiden following you in Blind Dungeon",
"B8 Monologue Testing Sprite",
"B9 Feuding Friends on Death Mountain",
"BA Whirlpool",
"BB Salesman / chestgame guy / 300 rupee giver guy / Chest game thief",
"BC Drunk in the inn",
"BD Vitreous (Large Eyeball",
"BE Vitreous (Small Eyeball",
"BF Vitreous' Lightning",
"C0 Monster in Lake of Ill Omen / Quake Medallion",
"C1 Agahnim teleporting Zelda to dark world",
"C2 Boulders",
"C3 Gibo",
"C4 Thief",
"C5 Medusa",
"C6 Four Way Fireball Spitters (spit when you use your sword",
"C7 Hokku",
"C8 Big Fairy who heals you",
"C9 Tektite",
"CA Chain Chomp",
"CB Trinexx",
"CC Another part of trinexx",
"CD Yet another part of trinexx",
"CE Blind The Thief (Boss)",
"CF Swamola",
"D0 Lynel",
"D1 Bunny Beam",
"D2 Flopping fish",
"D3 Stal",
"D4 Landmine",
"D5 Digging Game Proprietor",
"D6 Ganon",
"D7 Copy of Ganon",
"D8 Heart",
"D9 Green Rupee",
"DA Blue Rupee",
"DB Red Rupee",
"DC Bomb Refill (1)",
"DD Bomb Refill (4)",
"DE Bomb Refill (8)",
"DF Small Magic Refill",
"E0 Full Magic Refill",
"E1 Arrow Refill (5)",
"E2 Arrow Refill (10)",
"E3 Fairy",
"E4 Key",
"E5 Big Key",
"E6 Shield",
"E7 Mushroom",
"E8 Fake Master Sword",
"E9 Magic Shop dude / His items",
"EA Heart Container",
"EB Heart Piece",
"EC Bushes",
"ED Cane Of Somaria Platform",
"EE Mantle",
"EF Cane of Somaria Platform (Unused)",
"F0 Cane of Somaria Platform (Unused)",
"F1 Cane of Somaria Platform (Unused)",
"F2 Medallion Tablet",
"F3",
"F4 Falling Rocks",
"F5",
"F6",
"F7",
"F8",
"F9",
"FA",
"FB",
"FC",
"FD",
"FE",
"FF",
};
/**
* @class Sprite
* @brief A class for managing sprites in the overworld and underworld.
*/
class Sprite : public GameEntity {
public:
Sprite() = default;
Sprite(const std::vector<uint8_t>& src, uint8_t overworld_map_id, uint8_t id,
uint8_t x, uint8_t y, int map_x, int map_y)
: map_id_(static_cast<int>(overworld_map_id)),
id_(id),
nx_(x),
ny_(y),
map_x_(map_x),
map_y_(map_y),
current_gfx_(src) {
entity_type_ = zelda3::GameEntity::EntityType::kSprite;
entity_id_ = id;
x_ = map_x_;
y_ = map_y_;
overworld_ = true;
name_ = kSpriteDefaultNames[id];
preview_gfx_.resize(64 * 64, 0xFF);
}
Sprite(uint8_t id, uint8_t x, uint8_t y, uint8_t subtype, uint8_t layer)
: id_(id), nx_(x), ny_(y), subtype_(subtype), layer_(layer) {
x_ = x;
y_ = y;
name_ = kSpriteDefaultNames[id];
if (((subtype & 0x07) == 0x07) && id > 0 && id <= 0x1A) {
name_ = kOverlordNames[id - 1];
overlord_ = 1;
}
}
void InitSprite(const std::vector<uint8_t>& src, uint8_t overworld_map_id,
uint8_t id, uint8_t x, uint8_t y, int map_x, int map_y) {
current_gfx_ = src;
overworld_ = true;
map_id_ = static_cast<int>(overworld_map_id);
id_ = id;
entity_type_ = zelda3::GameEntity::EntityType::kSprite;
entity_id_ = id;
x_ = map_x_;
y_ = map_y_;
nx_ = x;
ny_ = y;
name_ = kSpriteDefaultNames[id];
map_x_ = map_x;
map_y_ = map_y;
preview_gfx_.resize(64 * 64, 0xFF);
}
void Draw();
void DrawSpriteTile(int x, int y, int srcx, int srcy, int pal,
bool mirror_x = false, bool mirror_y = false,
int sizex = 2, int sizey = 2);
void UpdateMapProperties(uint16_t map_id) override;
void UpdateCoordinates(int map_x, int map_y);
auto preview_graphics() const { return &preview_gfx_; }
auto id() const { return id_; }
auto set_id(uint8_t id) { id_ = id; }
auto x() const { return x_; }
auto y() const { return y_; }
auto nx() const { return nx_; }
auto ny() const { return ny_; }
auto map_id() const { return map_id_; }
auto map_x() const { return map_x_; }
auto map_y() const { return map_y_; }
auto game_state() const { return game_state_; }
auto layer() const { return layer_; }
auto subtype() const { return subtype_; }
auto width() const { return width_; }
auto height() const { return height_; }
auto name() { return name_; }
auto deleted() const { return deleted_; }
auto set_deleted(bool deleted) { deleted_ = deleted; }
auto set_key_drop(int key) { key_drop_ = key; }
private:
uint8_t map_id_;
uint8_t game_state_;
uint8_t id_;
uint8_t nx_;
uint8_t ny_;
uint8_t overlord_ = 0;
uint8_t lower_x_ = 32;
uint8_t lower_y_ = 32;
uint8_t higher_x_ = 0;
uint8_t higher_y_ = 0;
int width_ = 16;
int height_ = 16;
int map_x_ = 0;
int map_y_ = 0;
int layer_ = 0;
int subtype_ = 0;
int key_drop_ = 0;
bool deleted_ = false;
bool overworld_;
std::string name_;
std::vector<uint8_t> preview_gfx_;
std::vector<uint8_t> current_gfx_;
SDL_Rect bounding_box_;
};
} // namespace zelda3
} // namespace yaze
#endif

View File

@@ -0,0 +1,151 @@
#include "sprite_builder.h"
#include <sstream>
#include <string>
namespace yaze {
namespace zelda3 {
SpriteBuilder SpriteBuilder::Create(const std::string& spriteName) {
SpriteBuilder builder;
return builder;
}
SpriteBuilder& SpriteBuilder::SetProperty(const std::string& propertyName,
const std::string& value) {
return *this;
}
SpriteBuilder& SpriteBuilder::SetProperty(const std::string& propertyName,
int value) {
return *this;
}
SpriteBuilder& SpriteBuilder::SetProperty(const std::string& propertyName,
bool value) {
return *this;
}
SpriteBuilder& SpriteBuilder::AddAction(const SpriteAction& action) {
return *this;
}
SpriteBuilder& SpriteBuilder::SetGlobalAction(const SpriteAction& action) {
return *this;
}
SpriteBuilder& SpriteBuilder::AddFunction(const SpriteAction& function) {
return *this;
}
SpriteBuilder& SpriteBuilder::AddFunction(const std::string& asmCode) {
return *this;
}
std::string SpriteBuilder::BuildProperties() const {
std::stringstream ss;
// Build the properties
for (int i = 0; i < 27; ++i) {
std::string property = "00";
if (!properties[i].empty()) property = properties[i];
ss << kSpriteProperties[i] << " = $" << property << std::endl;
}
return ss.str();
}
std::string SpriteBuilder::Build() const {
std::stringstream ss;
ss << BuildProperties();
return ss.str();
}
// ============================================================================
SpriteAction SpriteAction::Create(const std::string& actionName) {
SpriteAction action;
return action;
}
SpriteAction SpriteAction::Create() {
SpriteAction action;
return action;
}
SpriteAction& SpriteAction::AddInstruction(
const SpriteInstruction& instruction) {
return *this;
}
SpriteAction& SpriteAction::AddCustomInstruction(const std::string& asmCode) {
return *this;
}
SpriteAction& SpriteAction::SetNextAction(const std::string& nextActionName) {
return *this;
}
std::string SpriteAction::GetConfiguration() const { return ""; }
// ============================================================================
SpriteInstruction SpriteInstruction::PlayAnimation(int startFrame, int endFrame,
int speed) {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::ApplySpeedTowardsPlayer(int speed) {
SpriteInstruction instruction;
instruction.SetConfiguration("JSL Sprite_ApplySpeedTowardsPlayer");
return instruction;
}
SpriteInstruction SpriteInstruction::CheckDamageFromPlayer() {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::MoveXyz() {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::BounceFromTileCollision() {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::SetTimer(int timerId, int value) {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::Custom(const std::string& asmCode) {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::BehaveAsBarrier() {
SpriteInstruction instruction;
return instruction;
}
SpriteInstruction SpriteInstruction::JumpToFunction(
const std::string& functionName) {
SpriteInstruction instruction;
return instruction;
}
} // namespace zelda3
} // namespace yaze

View File

@@ -0,0 +1,129 @@
#ifndef YAZE_APP_ZELDA3_SPRITE_SPRITE_BUILDER_H_
#define YAZE_APP_ZELDA3_SPRITE_SPRITE_BUILDER_H_
#include <array>
#include <string>
#include <vector>
namespace yaze {
namespace zelda3 {
class SpriteInstruction {
public:
// Predefined instructions
static SpriteInstruction PlayAnimation(int startFrame, int endFrame,
int speed);
static SpriteInstruction ApplySpeedTowardsPlayer(int speed);
static SpriteInstruction CheckDamageFromPlayer();
static SpriteInstruction MoveXyz();
static SpriteInstruction BounceFromTileCollision();
static SpriteInstruction SetTimer(int timerId, int value);
static SpriteInstruction BehaveAsBarrier();
static SpriteInstruction JumpToFunction(const std::string& functionName);
// Custom instruction
static SpriteInstruction Custom(const std::string& asmCode);
// Get the instruction configuration
std::string GetConfiguration() const { return instruction_; }
void SetConfiguration(const std::string& instruction) {
instruction_ = instruction;
}
private:
std::string instruction_;
};
class SpriteAction {
public:
// Factory method to create a new action
static SpriteAction Create(const std::string& actionName);
// Anonymously create an action
static SpriteAction Create();
// Add a predefined instruction to the action
SpriteAction& AddInstruction(const SpriteInstruction& instruction);
// Add custom raw ASM instruction to the action
SpriteAction& AddCustomInstruction(const std::string& asmCode);
// Set the next action to jump to
SpriteAction& SetNextAction(const std::string& nextActionName);
// Get the action configuration
std::string GetConfiguration() const;
private:
std::string name;
std::vector<std::string> instructions;
std::string nextAction;
};
// Array of sprite property names
constexpr const char* kSpriteProperties[] = {"!SPRID",
"!NbrTiles",
"!Harmless",
"!HVelocity",
"!Health",
"!Damage",
"!DeathAnimation",
"!ImperviousAll",
"!SmallShadow",
"!Shadow",
"!Palette",
"!Hitbox",
"!Persist",
"!Statis",
"!CollisionLayer",
"!CanFall",
"!DeflectArrow",
"!WaterSprite",
"!Blockable",
"!Prize",
"!Sound",
"!Interaction",
"!Statue",
"!DeflectProjectiles",
"!ImperviousArrow",
"!ImpervSwordHammer",
"!Boss"};
class SpriteBuilder {
public:
// Factory method to create a new sprite
static SpriteBuilder Create(const std::string& spriteName);
// Set sprite properties
SpriteBuilder& SetProperty(const std::string& propertyName,
const std::string& value);
SpriteBuilder& SetProperty(const std::string& propertyName, int value);
SpriteBuilder& SetProperty(const std::string& propertyName, bool value);
// Add an action to the sprite
SpriteBuilder& AddAction(const SpriteAction& action);
// Set global action to the sprite (always runs)
SpriteBuilder& SetGlobalAction(const SpriteAction& action);
// Add a function to be called from anywhere in the sprite code
SpriteBuilder& AddFunction(const std::string& asmCode);
SpriteBuilder& AddFunction(const SpriteAction& action);
std::string BuildProperties() const;
// Build and get the sprite configuration
std::string Build() const;
private:
std::string name;
std::array<std::string, 27> properties;
std::vector<SpriteAction> actions;
SpriteAction globalAction;
std::vector<SpriteAction> functions;
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_SPRITE_SPRITE_BUILDER_H_

554
src/zelda3/zelda3_labels.cc Normal file
View File

@@ -0,0 +1,554 @@
#include "zelda3/zelda3_labels.h"
#include <cstddef>
#include <string>
#include <unordered_map>
#include <vector>
#include "zelda3/common.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/sprite/overlord.h"
#include "zelda3/sprite/sprite.h"
namespace yaze::zelda3 {
// Room names - reuse existing kRoomNames array
const std::vector<std::string>& Zelda3Labels::GetRoomNames() {
static std::vector<std::string> room_names;
if (room_names.empty()) {
for (const auto& name : kRoomNames) {
room_names.emplace_back(name);
}
}
return room_names;
}
// Entrance names - reuse existing kEntranceNames array
const std::vector<std::string>& Zelda3Labels::GetEntranceNames() {
static std::vector<std::string> entrance_names;
if (entrance_names.empty()) {
for (const auto& name : kEntranceNames) {
entrance_names.emplace_back(name);
}
}
return entrance_names;
}
// Sprite names - reuse existing kSpriteDefaultNames array
const std::vector<std::string>& Zelda3Labels::GetSpriteNames() {
static std::vector<std::string> sprite_names;
if (sprite_names.empty()) {
for (const auto& name : kSpriteDefaultNames) {
sprite_names.push_back(name);
}
}
return sprite_names;
}
// Overlord names - reuse existing kOverlordNames array
const std::vector<std::string>& Zelda3Labels::GetOverlordNames() {
static std::vector<std::string> overlord_names;
if (overlord_names.empty()) {
for (const auto& name : kOverlordNames) {
overlord_names.push_back(name);
}
}
return overlord_names;
}
// Overworld map names (64 Light World + 64 Dark World + 32 Special Areas)
const std::vector<std::string>& Zelda3Labels::GetOverworldMapNames() {
static const std::vector<std::string> map_names = {
// Light World (0x00-0x3F)
"Lost Woods", "Master Sword Pedestal", "Castle Courtyard", "Link's House",
"Eastern Palace", "Desert Palace", "Hyrule Castle", "Witch's Hut",
"Kakariko Village", "Death Mountain", "Tower of Hera", "Spectacle Rock",
"Graveyard", "Sanctuary", "Lake Hylia", "Desert of Mystery",
"Eastern Ruins", "Zora's Domain", "Catfish", "Dam", "Potion Shop",
"Kakariko Well", "Blacksmith", "Sick Kid", "Library", "Mushroom",
"Magic Bat", "Fairy Fountain", "Fortune Teller", "Lake Shop", "Bomb Shop",
"Cave 45", "Checkerboard Cave", "Mini Moldorm Cave", "Ice Rod Cave",
"Bonk Rocks", "Bottle Merchant", "Sahasrahla's Hut", "Chicken House",
"Aginah's Cave", "Dam Exterior", "Mimic Cave Exterior", "Waterfall Fairy",
"Pyramid", "Fat Fairy", "Spike Cave", "Hookshot Cave", "Graveyard Ledge",
"Dark Lumberjacks", "Bumper Cave", "Skull Woods 1", "Skull Woods 2",
"Skull Woods 3", "Skull Woods 4", "Skull Woods 5", "Skull Woods 6",
"Skull Woods 7", "Skull Woods 8", "Ice Palace Exterior",
"Misery Mire Exterior", "Palace of Darkness Exterior",
"Swamp Palace Exterior", "Turtle Rock Exterior", "Thieves' Town Exterior",
// Dark World (0x40-0x7F)
"Dark Woods", "Dark Chapel", "Dark Castle", "Dark Shields", "Dark Palace",
"Dark Desert", "Dark Castle Gate", "Dark Witch", "Dark Village",
"Dark Mountain", "Dark Tower", "Dark Rocks", "Dark Graveyard",
"Dark Sanctuary", "Dark Lake", "Dark Desert South", "Dark Eastern",
"Dark Zora", "Dark Catfish", "Dark Dam", "Dark Shop", "Dark Well",
"Dark Blacksmith", "Dark Sick Kid", "Dark Library", "Dark Mushroom",
"Dark Bat", "Dark Fountain", "Dark Fortune", "Dark Lake Shop",
"Dark Bomb Shop", "Dark Cave 45", "Dark Checker", "Dark Mini Moldorm",
"Dark Ice Rod", "Dark Bonk", "Dark Bottle", "Dark Sahasrahla",
"Dark Chicken", "Dark Aginah", "Dark Dam Exit", "Dark Mimic Exit",
"Dark Waterfall", "Pyramid Top", "Dark Fat Fairy", "Dark Spike Cave",
"Dark Hookshot", "Dark Graveyard Ledge", "Lumberjack House",
"Dark Bumper", "Skull Woods A", "Skull Woods B", "Skull Woods C",
"Skull Woods D", "Skull Woods E", "Skull Woods F", "Skull Woods G",
"Skull Woods H", "Ice Palace Entry", "Misery Mire Entry",
"Palace of Darkness Entry", "Swamp Palace Entry", "Turtle Rock Entry",
"Thieves' Town Entry",
// Special Areas (0x80-0x9F)
"Special Area 1", "Special Area 2", "Special Area 3", "Special Area 4",
"Special Area 5", "Special Area 6", "Special Area 7", "Special Area 8",
"Special Area 9", "Special Area 10", "Special Area 11", "Special Area 12",
"Special Area 13", "Special Area 14", "Special Area 15",
"Special Area 16", "Special Area 17", "Special Area 18",
"Special Area 19", "Special Area 20", "Special Area 21",
"Special Area 22", "Special Area 23", "Special Area 24",
"Special Area 25", "Special Area 26", "Special Area 27",
"Special Area 28", "Special Area 29", "Special Area 30",
"Special Area 31", "Special Area 32"};
return map_names;
}
// Item names (complete item list)
const std::vector<std::string>& Zelda3Labels::GetItemNames() {
static const std::vector<std::string> item_names = {
"None",
"Fighter Sword",
"Master Sword",
"Tempered Sword",
"Golden Sword",
"Fighter Shield",
"Fire Shield",
"Mirror Shield",
"Fire Rod",
"Ice Rod",
"Hammer",
"Hookshot",
"Bow",
"Boomerang",
"Powder",
"Bee Badge",
"Bombos Medallion",
"Ether Medallion",
"Quake Medallion",
"Lamp",
"Shovel",
"Flute",
"Somaria Cane",
"Bottle",
"Heart Piece",
"Byrna Cane",
"Cape",
"Mirror",
"Power Glove",
"Titan Mitt",
"Book of Mudora",
"Zora Flippers",
"Moon Pearl",
"Crystal",
"Bug Net",
"Blue Mail",
"Red Mail",
"Key",
"Compass",
"Heart Container",
"Bomb",
"3 Bombs",
"Mushroom",
"Red Boomerang",
"Red Potion",
"Green Potion",
"Blue Potion",
"Red Potion (Refill)",
"Green Potion (Refill)",
"Blue Potion (Refill)",
"10 Bombs",
"Big Key",
"Map",
"1 Rupee",
"5 Rupees",
"20 Rupees",
"Pendant of Courage",
"Pendant of Wisdom",
"Pendant of Power",
"Bow and Arrows",
"Silver Arrows Upgrade",
"Bee",
"Fairy",
"Heart Container (Boss)",
"Heart",
"1 Arrow",
"10 Arrows",
"Magic",
"Small Magic",
"300 Rupees",
"20 Rupees (Green)",
"100 Rupees",
"50 Rupees",
"Heart Container (Sanctuary)",
"Arrow Refill (5)",
"Arrow Refill (10)",
"Bomb Refill (1)",
"Bomb Refill (4)",
"Bomb Refill (8)",
"Blue Shield (Refill)",
"Magic Upgrade (1/2)",
"Magic Upgrade (1/4)",
"Programmable Item 1",
"Programmable Item 2",
"Programmable Item 3",
"Silvers",
"Rupoor",
"Null Item",
"Red Clock",
"Blue Clock",
"Green Clock",
"Progressive Sword",
"Progressive Shield",
"Progressive Armor",
"Progressive Lifting Glove",
"RNG Item (Single)",
"RNG Item (Multi)",
"Progressive Bow",
"Progressive Bow (Alt)",
"Aga Pendant",
"Pendant (Green)",
"Blue Pendant",
"Good Bee",
"Tossed Key"};
return item_names;
}
// Music track names
const std::vector<std::string>& Zelda3Labels::GetMusicTrackNames() {
static const std::vector<std::string> music_names = {
"Nothing",
"Light World",
"Beginning",
"Rabbit",
"Forest",
"Intro",
"Town",
"Warp",
"Dark World",
"Master Sword",
"File Select",
"Soldier",
"Boss",
"Dark World Death Mountain",
"Minigame",
"Skull Woods",
"Indoor",
"Cave 1",
"Zelda's Rescue",
"Crystal",
"Shop",
"Cave 2",
"Game Over",
"Boss Victory",
"Sanctuary",
"Boss Victory (Short)",
"Dark World Woods",
"Pendant",
"Ganon's Message",
"Hyrule Castle",
"Light World Death Mountain",
"Eastern Palace",
"Desert Palace",
"Agahnim's Theme",
"Damp Dungeon",
"Ganon Reveals",
"Confrontation",
"Ganon's Theme",
"Triforce",
"Credits",
"Unused",
"Unused",
"Unused",
"Unused",
"Unused",
"Unused",
"Unused",
"Unused"};
return music_names;
}
// Graphics sheet names
const std::vector<std::string>& Zelda3Labels::GetGraphicsSheetNames() {
static const std::vector<std::string> gfx_names = {"Sprite Sheet 0",
"Sprite Sheet 1",
"Sprite Sheet 2",
"Sprite Sheet 3",
"Sprite Sheet 4",
"Sprite Sheet 5",
"Sprite Sheet 6",
"Sprite Sheet 7",
"Sprite Sheet 8",
"Sprite Sheet 9",
"Sprite Sheet A",
"Sprite Sheet B",
"Sprite Sheet C",
"Sprite Sheet D",
"Sprite Sheet E",
"Sprite Sheet F",
"Link's Sprites",
"Sword Sprites",
"Shield Sprites",
"Common Sprites",
"Boss Sprites",
"NPC Sprites",
"Enemy Sprites 1",
"Enemy Sprites 2",
"Item Sprites",
"Dungeon Objects",
"Overworld Objects",
"Interface",
"Font",
"Credits",
"Unused",
"Unused"};
return gfx_names;
}
// Room object names - these are large, so we'll delegate to a helper
namespace {
std::vector<std::string> ConvertArrayToVector(const char** array, size_t size) {
std::vector<std::string> result;
result.reserve(size);
for (size_t i = 0; i < size; ++i) {
result.emplace_back(array[i]);
}
return result;
}
} // namespace
const std::vector<std::string>& Zelda3Labels::GetType1RoomObjectNames() {
static const std::vector<std::string> names = []() {
std::vector<std::string> result;
// Note: Type1RoomObjectNames is constexpr, we need to count its size
// For now, we'll add known objects. In full implementation,
// we'd import from room_object.h
result = {
"Ceiling ↔",
"Wall (top, north) ↔",
"Wall (top, south) ↔",
"Wall (bottom, north) ↔",
"Wall (bottom, south) ↔",
"Wall columns (north) ↔",
"Wall columns (south) ↔",
"Deep wall (north) ↔",
"Deep wall (south) ↔",
"Diagonal wall A ◤ (top) ↔",
"Diagonal wall A ◣ (top) ↔",
"Diagonal wall A ◥ (top) ↔",
"Diagonal wall A ◢ (top) ↔",
// ... Add all Type1 objects here
};
return result;
}();
return names;
}
const std::vector<std::string>& Zelda3Labels::GetType2RoomObjectNames() {
static const std::vector<std::string> names = []() {
std::vector<std::string> result;
// Add Type2 room objects
result = {"Type2 Object 1", "Type2 Object 2" /* ... */};
return result;
}();
return names;
}
const std::vector<std::string>& Zelda3Labels::GetType3RoomObjectNames() {
static const std::vector<std::string> names = []() {
std::vector<std::string> result;
// Add Type3 room objects
result = {"Type3 Object 1", "Type3 Object 2" /* ... */};
return result;
}();
return names;
}
// Room effect names - reuse existing RoomEffect array
const std::vector<std::string>& Zelda3Labels::GetRoomEffectNames() {
static std::vector<std::string> effect_names;
if (effect_names.empty()) {
for (const auto& name : RoomEffect) {
effect_names.push_back(name);
}
}
return effect_names;
}
// Room tag names
const std::vector<std::string>& Zelda3Labels::GetRoomTagNames() {
static const std::vector<std::string> tag_names = {
"No Tag", "NW", "NE", "SW", "SE", "West", "East",
"North", "South", "Entrance", "Treasure", "Boss", "Dark"};
return tag_names;
}
// Tile type names
const std::vector<std::string>& Zelda3Labels::GetTileTypeNames() {
static const std::vector<std::string> tile_names = {
"Nothing (standard floor)",
"Nothing (unused?)",
"Collision",
"Collision (unknown types)",
"Collision",
"Collision (unused?)",
"Collision",
"Collision",
"Deep water",
"Shallow water",
"Unknown (near water/pit edges)",
"Collision (water/pit edges)",
"Overlay mask",
"Spike floor",
"GT ice",
"Ice palace ice",
"Slope ◤",
"Slope ◥",
"Slope ◣",
"Slope ◢",
"Nothing (unused?)",
"Nothing (unused?)",
"Nothing (unused?)",
"Slope ◤",
"Slope ◥",
"Slope ◣",
"Slope ◢",
"Layer swap",
"Pit",
"Manual stairs",
"Pot switch",
"Pressure switch",
"Blocks switch (chest, PoD, walls)",
"Layer toggle",
"Layer 2 overlay",
"North single-layer auto stairs",
"North layer swap auto stairs",
"South single-layer auto stairs",
"South layer swap auto stairs",
"North/south layer swap auto stairs",
"North/south single-layer auto stairs",
"West single-layer auto stairs",
"West layer swap auto stairs",
"East single-layer auto stairs",
"East layer swap auto stairs",
"East/west layer swap auto stairs",
"East/west single-layer auto stairs",
"Nothing (stairs edge)",
"Straight inter-room stairs south/up",
"Straight inter-room stairs north/down",
"Straight inter-room stairs south/down 2",
"Straight inter-room stairs north/up 2",
"Star tile (inactive on GBA)",
"Collision (near stairs)",
"Warp tile",
"Square corners ⌜⌝⌞⌟",
"Thick corner ⌜",
"Thick corner ⌝",
"Thick corner ⌞",
"Thick corner ⌟",
"Roof/grass tiles?",
"Spike floor"};
return tile_names;
}
// Convert all labels to structured map for project embedding
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
Zelda3Labels::ToResourceLabels() {
std::unordered_map<std::string, std::unordered_map<std::string, std::string>>
labels;
// Rooms
const auto& rooms = GetRoomNames();
for (size_t i = 0; i < rooms.size(); ++i) {
labels["room"][std::to_string(i)] = rooms[i];
}
// Entrances
const auto& entrances = GetEntranceNames();
for (size_t i = 0; i < entrances.size(); ++i) {
labels["entrance"][std::to_string(i)] = entrances[i];
}
// Sprites
const auto& sprites = GetSpriteNames();
for (size_t i = 0; i < sprites.size(); ++i) {
labels["sprite"][std::to_string(i)] = sprites[i];
}
// Overlords
const auto& overlords = GetOverlordNames();
for (size_t i = 0; i < overlords.size(); ++i) {
labels["overlord"][std::to_string(i)] = overlords[i];
}
// Overworld maps
const auto& maps = GetOverworldMapNames();
for (size_t i = 0; i < maps.size(); ++i) {
labels["overworld_map"][std::to_string(i)] = maps[i];
}
// Items
const auto& items = GetItemNames();
for (size_t i = 0; i < items.size(); ++i) {
labels["item"][std::to_string(i)] = items[i];
}
// Music tracks
const auto& music = GetMusicTrackNames();
for (size_t i = 0; i < music.size(); ++i) {
labels["music"][std::to_string(i)] = music[i];
}
// Graphics sheets
const auto& gfx = GetGraphicsSheetNames();
for (size_t i = 0; i < gfx.size(); ++i) {
labels["graphics"][std::to_string(i)] = gfx[i];
}
// Room effects
const auto& effects = GetRoomEffectNames();
for (size_t i = 0; i < effects.size(); ++i) {
labels["room_effect"][std::to_string(i)] = effects[i];
}
// Room tags
const auto& tags = GetRoomTagNames();
for (size_t i = 0; i < tags.size(); ++i) {
labels["room_tag"][std::to_string(i)] = tags[i];
}
// Tile types
const auto& tiles = GetTileTypeNames();
for (size_t i = 0; i < tiles.size(); ++i) {
labels["tile_type"][std::to_string(i)] = tiles[i];
}
return labels;
}
// Get a label by resource type and ID
std::string Zelda3Labels::GetLabel(const std::string& resource_type, int id,
const std::string& default_value) {
static auto labels = ToResourceLabels();
auto type_it = labels.find(resource_type);
if (type_it == labels.end()) {
return default_value.empty() ? resource_type + "_" + std::to_string(id)
: default_value;
}
auto label_it = type_it->second.find(std::to_string(id));
if (label_it == type_it->second.end()) {
return default_value.empty() ? resource_type + "_" + std::to_string(id)
: default_value;
}
return label_it->second;
}
} // namespace yaze::zelda3

View File

@@ -0,0 +1,79 @@
#ifndef YAZE_APP_ZELDA3_ZELDA3_LABELS_H
#define YAZE_APP_ZELDA3_ZELDA3_LABELS_H
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
namespace yaze {
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.
*/
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();
/**
* @brief Get a label by resource type and ID
* @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 = "");
};
} // namespace zelda3
} // namespace yaze
#endif // YAZE_APP_ZELDA3_ZELDA3_LABELS_H

View File

@@ -0,0 +1,74 @@
set(
YAZE_APP_ZELDA3_SRC
zelda3/dungeon/dungeon_editor_system.cc
zelda3/dungeon/dungeon_object_editor.cc
zelda3/dungeon/object_drawer.cc
zelda3/dungeon/object_parser.cc
zelda3/dungeon/room.cc
zelda3/dungeon/room_layout.cc
zelda3/dungeon/room_object.cc
zelda3/music/tracker.cc
zelda3/overworld/overworld.cc
zelda3/overworld/overworld_map.cc
zelda3/overworld/overworld_entrance.cc
zelda3/overworld/overworld_exit.cc
zelda3/overworld/overworld_item.cc
zelda3/palette_constants.cc
zelda3/screen/dungeon_map.cc
zelda3/screen/inventory.cc
zelda3/screen/title_screen.cc
zelda3/screen/overworld_map_screen.cc
zelda3/sprite/sprite.cc
zelda3/sprite/sprite_builder.cc
zelda3/zelda3_labels.cc
)
# ==============================================================================
# Yaze Zelda3 Library
# ==============================================================================
# This library contains all Zelda3-specific game logic:
# - Overworld system (maps, tiles, sprites)
# - Dungeon system (rooms, objects, sprites)
# - Screen modules (title, inventory, dungeon map)
# - Sprite management
# - Music/tracker system
#
# Dependencies: yaze_gfx, yaze_util
# ==============================================================================
add_library(yaze_zelda3 STATIC ${YAZE_APP_ZELDA3_SRC})
target_precompile_headers(yaze_zelda3 PRIVATE
"$<$<COMPILE_LANGUAGE:CXX>:${CMAKE_SOURCE_DIR}/src/yaze_pch.h>"
)
target_include_directories(yaze_zelda3 PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/lib
${CMAKE_SOURCE_DIR}/incl
${PROJECT_BINARY_DIR}
)
target_link_libraries(yaze_zelda3 PUBLIC
yaze_gfx
yaze_util
yaze_common
${ABSL_TARGETS}
)
set_target_properties(yaze_zelda3 PROPERTIES
POSITION_INDEPENDENT_CODE ON
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
)
# Platform-specific compile definitions
if(UNIX AND NOT APPLE)
target_compile_definitions(yaze_zelda3 PRIVATE linux stricmp=strcasecmp)
elseif(APPLE)
target_compile_definitions(yaze_zelda3 PRIVATE MACOS)
elseif(WIN32)
target_compile_definitions(yaze_zelda3 PRIVATE WINDOWS)
endif()
message(STATUS "✓ yaze_zelda3 library configured")