yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sprite_oam_tables.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cctype>
5#include <filesystem>
6#include <fstream>
7#include <string>
8#include <unordered_map>
9
10namespace yaze {
11namespace zelda3 {
12
13namespace {
14
15// Oracle Sprites/Enemies/puffstool.asm, Sprite_Puffstool_Draw frame 0:
16// CHR C0/D0, properties 33 (page 1, OBJ palette 1), 16x16 at (0,-8)/(0,0).
17// Core/sprite_macros.asm DrawSprite emits the source entries in reverse order.
18// This is a source-backed static preview, not an emulator-verified animation.
20 .sprite_id = 0xB1,
21 .name = "Puffstool",
22 .tiles = {{0, 0, 0x1D0, 1, true, false, false},
23 {0, -8, 0x1C0, 1, true, false, false}},
24 .required_sheets = {0, 0, 0, 0}};
25
26// Oracle Sprites/Bosses/manhandla.asm, Sprite_Manhandla_Draw frame 0. Its
27// descending loop emits CHR20 at (0,8) before CHR00 at (0,-8); properties $33
28// select OBJ page 1 and palette 1. This is the static front head, not the spawned
29// heads, BG body, or Big Chuchu phase. ApplyManhandlaGraphics loads an external
30// $2000-byte page at VRAM $5000; ordinary base-ROM room sheets are not this art.
32 .sprite_id = 0x88,
33 .name = "Manhandla (static head)",
34 .tiles = {{0, 8, 0x120, 1, true, false, false},
35 {0, -8, 0x100, 1, true, false, false}},
36 .required_sheets = {0, 0, 0, 0},
37 .graphics_resource = "Bosses/manhandla.bin"};
38
39// usdasm bank_0D SpriteDraw_Stalfos .oam_groups frame 0 ($0D:C0F3): CHR $00
40// at (0,-10) and CHR $06 at (0,0), both 16x16. SpriteData_OAMProp[$A7] = $19
41// selects OBJ page 1 and palette 1, so the tiles are OAM 0x100/0x106: the
42// first spriteset slot. required_sheets stays empty; the preview draws from
43// the current spriteset slots (Sprite Editor > Spriteset preview).
45 .sprite_id = 0xA7,
46 .name = "Stalfos",
47 .tiles = {{0, 0, 0x106, 1, true, false, false}, // Body
48 {0, -10, 0x100, 1, true, false, false}}, // Head
49 .required_sheets = {0, 0, 0, 0}};
50
51// Static registry of all sprite layouts
52const std::unordered_map<uint8_t, const SpriteOamLayout*>& GetLayoutMap() {
53 static const std::unordered_map<uint8_t, const SpriteOamLayout*> layouts = {
54 {0x01, &kVultureLayout}, {0x08, &kOctorokLayout},
55 {0x0B, &kChickenLayout}, {0x0E, &kGreenSoldierLayout},
56 {0x0F, &kBlueSoldierLayout}, {0x10, &kRedSoldierLayout},
57 {0x29, &kBlueGuardLayout}, {0x41, &kGreenPatrolLayout},
58 {0x44, &kArmosKnightLayout}, {0x4B, &kOctoballoonLayout},
59 {0x53, &kRedEyegoreLayout}, {0x54, &kGreenEyegoreLayout},
60 {0x64, &kMoblinLayout}, {0x81, &kHinoxLayout},
61 {0x88, &kUncleLayout}, {0xA7, &kStalfosLayout},
63 {0xDB, &kBlueRupeeLayout}, {0xDC, &kRedRupeeLayout},
64 {0xDE, &kSmallHeartLayout}, {0xDF, &kKeyLayout},
65 {0xE1, &kSmallMagicLayout}, {0xE2, &kLargeMagicLayout},
66 };
67 return layouts;
68}
69
70} // namespace
71
73 const auto& layouts = GetLayoutMap();
74 auto it = layouts.find(sprite_id);
75 if (it != layouts.end()) {
76 return it->second;
77 }
78 return nullptr;
79}
80
82 uint8_t sprite_id, std::string_view hack_name) {
83 if ((sprite_id != 0xB1 && sprite_id != 0x88) || hack_name.empty()) {
84 return nullptr;
85 }
86 // Match the existing Oracle hack-workflow name fallback. This is per-call,
87 // never global state, so switching projects cannot retain an old override.
88 std::string normalized(hack_name);
89 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
90 [](unsigned char c) { return std::tolower(c); });
91 if (normalized.find("oracle") == std::string::npos) {
92 return nullptr;
93 }
94 return sprite_id == 0x88 ? &kOracleManhandlaPreview
95 : &kOraclePuffstoolPreview;
96}
97
98bool SpritePreviewResourceCache::SetContext(std::string_view project_path,
99 std::string_view assets_path,
100 std::string_view hack_name) {
101 if (project_path_ == project_path && assets_path_ == assets_path &&
102 hack_name_ == hack_name) {
103 return false;
104 }
105 project_path_ = project_path;
106 assets_path_ = assets_path;
107 hack_name_ = hack_name;
108 resource_.clear();
109 graphics_.clear();
110 return true;
111}
112
114 const SpriteOamLayout* layout) {
115 if (layout == nullptr || layout->graphics_resource == nullptr ||
116 project_path_.empty() || assets_path_.empty() ||
118 nullptr) {
119 return {};
120 }
121 if (resource_ == layout->graphics_resource) {
122 return graphics_;
123 }
125 graphics_.clear();
126 // Match ApplyManhandlaGraphics' whole OBJ page transfer, not a partial file.
127 // Read at most this small fixed payload; reject truncated or oversized files.
128 constexpr size_t kObjPageBytes = 0x2000;
129 const auto path = std::filesystem::path(assets_path_) / resource_;
130 std::ifstream file(path, std::ios::binary | std::ios::ate);
131 if (!file || file.tellg() != static_cast<std::streamoff>(kObjPageBytes)) {
132 return {};
133 }
134 file.seekg(0);
135 graphics_.resize(kObjPageBytes);
136 if (!file.read(reinterpret_cast<char*>(graphics_.data()), graphics_.size()) ||
137 file.peek() != std::char_traits<char>::eof()) {
138 graphics_.clear();
139 }
140 return graphics_;
141}
142
143std::optional<std::array<uint8_t, 4>> SpriteOamRegistry::GetRequiredSheets(
144 uint8_t sprite_id) {
145 const auto* layout = GetLayout(sprite_id);
146 if (layout) {
147 return layout->required_sheets;
148 }
149 return std::nullopt;
150}
151
152bool SpriteOamRegistry::HasLayout(uint8_t sprite_id) {
153 const auto& layouts = GetLayoutMap();
154 return layouts.find(sprite_id) != layouts.end();
155}
156
157std::vector<const SpriteOamLayout*> SpriteOamRegistry::GetAllLayouts() {
158 std::vector<const SpriteOamLayout*> result;
159 const auto& layouts = GetLayoutMap();
160 result.reserve(layouts.size());
161 for (const auto& [id, layout] : layouts) {
162 result.push_back(layout);
163 }
164 return result;
165}
166
167} // namespace zelda3
168} // namespace yaze
static const SpriteOamLayout * GetLayout(uint8_t sprite_id)
Get the OAM layout for a sprite ID.
static std::vector< const SpriteOamLayout * > GetAllLayouts()
Get all defined layouts.
static const SpriteOamLayout * GetPreviewOverride(uint8_t sprite_id, std::string_view hack_name)
static std::optional< std::array< uint8_t, 4 > > GetRequiredSheets(uint8_t sprite_id)
Get required graphics sheets for a sprite.
static bool HasLayout(uint8_t sprite_id)
Check if a sprite has a defined layout.
std::span< const uint8_t > GetGraphics(const SpriteOamLayout *layout)
bool SetContext(std::string_view project_path, std::string_view assets_path, std::string_view hack_name)
const std::unordered_map< uint8_t, const SpriteOamLayout * > & GetLayoutMap()
const SpriteOamLayout kRedEyegoreLayout
const SpriteOamLayout kOctorokLayout
const SpriteOamLayout kBlueGuardLayout
const SpriteOamLayout kLargeMagicLayout
const SpriteOamLayout kBlueSoldierLayout
const SpriteOamLayout kBlueRupeeLayout
const SpriteOamLayout kRedSoldierLayout
const SpriteOamLayout kGreenSoldierLayout
const SpriteOamLayout kSmallMagicLayout
const SpriteOamLayout kGreenRupeeLayout
const SpriteOamLayout kArmosKnightLayout
const SpriteOamLayout kGreenPatrolLayout
const SpriteOamLayout kOctoballoonLayout
const SpriteOamLayout kHinoxLayout
const SpriteOamLayout kHeartContainerLayout
const SpriteOamLayout kMoblinLayout
const SpriteOamLayout kChickenLayout
const SpriteOamLayout kUncleLayout
const SpriteOamLayout kKeyLayout
const SpriteOamLayout kGreenEyegoreLayout
const SpriteOamLayout kSmallHeartLayout
const SpriteOamLayout kVultureLayout
const SpriteOamLayout kRedRupeeLayout
Complete OAM layout for a vanilla sprite.