backend-infra-engineer: Release v0.3.2 snapshot
This commit is contained in:
447
src/zelda3/common.h
Normal file
447
src/zelda3/common.h
Normal 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
|
||||
816
src/zelda3/dungeon/dungeon_editor_system.cc
Normal file
816
src/zelda3/dungeon/dungeon_editor_system.cc
Normal 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
|
||||
492
src/zelda3/dungeon/dungeon_editor_system.h
Normal file
492
src/zelda3/dungeon/dungeon_editor_system.h
Normal 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
|
||||
1287
src/zelda3/dungeon/dungeon_object_editor.cc
Normal file
1287
src/zelda3/dungeon/dungeon_object_editor.cc
Normal file
File diff suppressed because it is too large
Load Diff
345
src/zelda3/dungeon/dungeon_object_editor.h
Normal file
345
src/zelda3/dungeon/dungeon_object_editor.h
Normal 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
|
||||
107
src/zelda3/dungeon/dungeon_rom_addresses.h
Normal file
107
src/zelda3/dungeon/dungeon_rom_addresses.h
Normal 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
|
||||
|
||||
833
src/zelda3/dungeon/object_drawer.cc
Normal file
833
src/zelda3/dungeon/object_drawer.cc
Normal 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
|
||||
183
src/zelda3/dungeon/object_drawer.h
Normal file
183
src/zelda3/dungeon/object_drawer.h
Normal 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
|
||||
|
||||
394
src/zelda3/dungeon/object_parser.cc
Normal file
394
src/zelda3/dungeon/object_parser.cc
Normal 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
|
||||
172
src/zelda3/dungeon/object_parser.h
Normal file
172
src/zelda3/dungeon/object_parser.h
Normal 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
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
856
src/zelda3/dungeon/room.h
Normal 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
|
||||
366
src/zelda3/dungeon/room_entrance.h
Normal file
366
src/zelda3/dungeon/room_entrance.h
Normal 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
|
||||
76
src/zelda3/dungeon/room_layout.cc
Normal file
76
src/zelda3/dungeon/room_layout.cc
Normal 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
|
||||
33
src/zelda3/dungeon/room_layout.h
Normal file
33
src/zelda3/dungeon/room_layout.h
Normal 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
|
||||
242
src/zelda3/dungeon/room_object.cc
Normal file
242
src/zelda3/dungeon/room_object.cc
Normal 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
|
||||
631
src/zelda3/dungeon/room_object.h
Normal file
631
src/zelda3/dungeon/room_object.h
Normal 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
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
283
src/zelda3/music/tracker.h
Normal 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
|
||||
2691
src/zelda3/overworld/overworld.cc
Normal file
2691
src/zelda3/overworld/overworld.cc
Normal file
File diff suppressed because it is too large
Load Diff
372
src/zelda3/overworld/overworld.h
Normal file
372
src/zelda3/overworld/overworld.h
Normal 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
|
||||
122
src/zelda3/overworld/overworld_entrance.cc
Normal file
122
src/zelda3/overworld/overworld_entrance.cc
Normal 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
|
||||
158
src/zelda3/overworld/overworld_entrance.h
Normal file
158
src/zelda3/overworld/overworld_entrance.h
Normal 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
|
||||
113
src/zelda3/overworld/overworld_exit.cc
Normal file
113
src/zelda3/overworld/overworld_exit.cc
Normal 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
|
||||
216
src/zelda3/overworld/overworld_exit.h
Normal file
216
src/zelda3/overworld/overworld_exit.h
Normal 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_
|
||||
208
src/zelda3/overworld/overworld_item.cc
Normal file
208
src/zelda3/overworld/overworld_item.cc
Normal 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
|
||||
165
src/zelda3/overworld/overworld_item.h
Normal file
165
src/zelda3/overworld/overworld_item.h
Normal 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_
|
||||
1129
src/zelda3/overworld/overworld_map.cc
Normal file
1129
src/zelda3/overworld/overworld_map.cc
Normal file
File diff suppressed because it is too large
Load Diff
313
src/zelda3/overworld/overworld_map.h
Normal file
313
src/zelda3/overworld/overworld_map.h
Normal 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 ¤t_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 ¤t_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
|
||||
82
src/zelda3/palette_constants.cc
Normal file
82
src/zelda3/palette_constants.cc
Normal 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
|
||||
308
src/zelda3/palette_constants.h
Normal file
308
src/zelda3/palette_constants.h
Normal 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
|
||||
|
||||
142
src/zelda3/palette_structure.md
Normal file
142
src/zelda3/palette_structure.md
Normal 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
|
||||
|
||||
207
src/zelda3/screen/dungeon_map.cc
Normal file
207
src/zelda3/screen/dungeon_map.cc
Normal 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
|
||||
114
src/zelda3/screen/dungeon_map.h
Normal file
114
src/zelda3/screen/dungeon_map.h
Normal 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
|
||||
156
src/zelda3/screen/inventory.cc
Normal file
156
src/zelda3/screen/inventory.cc
Normal 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
|
||||
77
src/zelda3/screen/inventory.h
Normal file
77
src/zelda3/screen/inventory.h
Normal 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
|
||||
365
src/zelda3/screen/overworld_map_screen.cc
Normal file
365
src/zelda3/screen/overworld_map_screen.cc
Normal 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
|
||||
94
src/zelda3/screen/overworld_map_screen.h
Normal file
94
src/zelda3/screen/overworld_map_screen.h
Normal 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
|
||||
|
||||
718
src/zelda3/screen/title_screen.cc
Normal file
718
src/zelda3/screen/title_screen.cc
Normal 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
|
||||
107
src/zelda3/screen/title_screen.h
Normal file
107
src/zelda3/screen/title_screen.h
Normal 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
|
||||
41
src/zelda3/sprite/overlord.h
Normal file
41
src/zelda3/sprite/overlord.h
Normal 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
932
src/zelda3/sprite/sprite.cc
Normal 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
392
src/zelda3/sprite/sprite.h
Normal 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
|
||||
151
src/zelda3/sprite/sprite_builder.cc
Normal file
151
src/zelda3/sprite/sprite_builder.cc
Normal 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
|
||||
129
src/zelda3/sprite/sprite_builder.h
Normal file
129
src/zelda3/sprite/sprite_builder.h
Normal 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
554
src/zelda3/zelda3_labels.cc
Normal 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
|
||||
79
src/zelda3/zelda3_labels.h
Normal file
79
src/zelda3/zelda3_labels.h
Normal 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
|
||||
74
src/zelda3/zelda3_library.cmake
Normal file
74
src/zelda3/zelda3_library.cmake
Normal 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")
|
||||
Reference in New Issue
Block a user