yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_object.cc
Go to the documentation of this file.
1#include "room_object.h"
2
3#include "absl/status/status.h"
4#include "absl/strings/str_format.h"
5#include "util/log.h"
7
8namespace yaze {
9namespace zelda3 {
10
11namespace {
13 int base_ptr; // base address of subtype table in ROM (PC)
14 int index_mask; // mask to apply to object id for index
15 int id_offset; // offset to subtract from object_id before masking
16
17 SubtypeTableInfo(int base, int mask, int offset = 0)
18 : base_ptr(base), index_mask(mask), id_offset(offset) {}
19};
20
22 // Heuristic: 0x00-0xFF => subtype1, 0x100-0x1FF => subtype2, >=0xF80 =>
23 // subtype3. Type 3 IDs from decoding are 0xF80-0xFFF (b3 0xF8-0xFF shifted).
24 if (object_id >= 0xF80) {
25 // Type 3: IDs 0xF80-0xFFF map to table indices 0-127
26 // Subtract 0xF80 first, then mask with 0x7F
27 return SubtypeTableInfo(kRoomObjectSubtype3, 0x7F, 0xF80);
28 } else if (object_id >= 0x100) {
29 // Type 2: IDs 0x100-0x1FF map to table indices 0-255
30 return SubtypeTableInfo(kRoomObjectSubtype2, 0xFF, 0x100);
31 } else {
32 // Type 1: IDs 0x00-0xFF map directly to table indices
34 }
35}
36} // namespace
37
39 return static_cast<ObjectOption>(static_cast<int>(lhs) |
40 static_cast<int>(rhs));
41}
42
44 return static_cast<ObjectOption>(static_cast<int>(lhs) &
45 static_cast<int>(rhs));
46}
47
49 return static_cast<ObjectOption>(static_cast<int>(lhs) ^
50 static_cast<int>(rhs));
51}
52
54 return static_cast<ObjectOption>(~static_cast<int>(option));
55}
56
57// NOTE: DrawTile was legacy ZScream code that is no longer used.
58// Modern rendering uses ObjectDrawer which draws directly to BackgroundBuffer
59// bitmaps.
60
62 if (tiles_loaded_) {
63 return;
64 }
65
66 if (rom_ == nullptr) {
67 // DEBUG: Log wall/corner objects
68 if (id_ == 0x001 || id_ == 0x002 || id_ == 0x061 || id_ == 0x062 ||
69 (id_ >= 0x100 && id_ <= 0x103)) {
70 LOG_DEBUG("RoomObject", "EnsureTilesLoaded: obj=0x%03X ROM is NULL!",
71 id_);
72 }
73 return;
74 }
75
76 // Try the new parser first - this is more efficient and accurate
77 auto parser_status = LoadTilesWithParser();
78 if (parser_status.ok()) {
79 tiles_loaded_ = true;
80 // DEBUG: Log wall/corner objects
81 if (id_ == 0x001 || id_ == 0x002 || id_ == 0x061 || id_ == 0x062 ||
82 (id_ >= 0x100 && id_ <= 0x103)) {
83 LOG_DEBUG("RoomObject",
84 "EnsureTilesLoaded: obj=0x%03X loaded %zu tiles via parser",
85 id_, tiles_.size());
86 }
87 return;
88 }
89
90 // DEBUG: Log parser failure for wall/corner objects
91 if (id_ == 0x001 || id_ == 0x002 || id_ == 0x061 || id_ == 0x062 ||
92 (id_ >= 0x100 && id_ <= 0x103)) {
93 LOG_DEBUG("RoomObject",
94 "EnsureTilesLoaded: obj=0x%03X parser failed: %s, trying legacy",
95 id_, std::string(parser_status.message()).c_str());
96 }
97
98 // Fallback to legacy method for compatibility with enhanced validation
99 auto rom_data = rom_->data();
100
101 // Determine which subtype table to use and compute the tile data offset.
102 SubtypeTableInfo sti = GetSubtypeTable(id_);
103 // Apply offset first (for Type 2/3 objects), then mask
104 int index = ((id_ - sti.id_offset) & sti.index_mask);
105 int tile_ptr = sti.base_ptr + (index * 2);
106
107 // Enhanced bounds checking
108 if (tile_ptr < 0 || tile_ptr + 1 >= (int)rom_->size()) {
109 // Log error but don't crash
110 LOG_DEBUG("RoomObject", "Tile pointer out of bounds for object %04X", id_);
111 tiles_.clear();
112 tiles_loaded_ = true; // Mark as loaded (empty) to prevent retry
113 return;
114 }
115
116 int tile_rel = (int16_t)((rom_data[tile_ptr + 1] << 8) + rom_data[tile_ptr]);
117 int pos = kRoomObjectTileAddress + tile_rel;
118 tile_data_ptr_ = pos;
119
120 // Enhanced bounds checking for tile data
121 if (pos < 0 || pos + 7 >= (int)rom_->size()) {
122 // Log error but don't crash
123 LOG_DEBUG("RoomObject", "Tile data position out of bounds for object %04X",
124 id_);
125 tiles_.clear();
126 tiles_loaded_ = true; // Mark as loaded (empty) to prevent retry
127 return;
128 }
129
130 // Read tile data with validation
131 uint16_t w0 = (uint16_t)(rom_data[pos] | (rom_data[pos + 1] << 8));
132 uint16_t w1 = (uint16_t)(rom_data[pos + 2] | (rom_data[pos + 3] << 8));
133 uint16_t w2 = (uint16_t)(rom_data[pos + 4] | (rom_data[pos + 5] << 8));
134 uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
135
136 tiles_.clear();
137 tiles_.push_back(gfx::WordToTileInfo(w0));
138 tiles_.push_back(gfx::WordToTileInfo(w1));
139 tiles_.push_back(gfx::WordToTileInfo(w2));
140 tiles_.push_back(gfx::WordToTileInfo(w3));
141 tile_count_ = 1;
142 tiles_loaded_ = true;
143}
144
146 if (rom_ == nullptr) {
147 return absl::InvalidArgumentError("ROM is null");
148 }
149
150 ObjectParser parser(rom_);
151 auto result = parser.ParseObject(id_);
152 if (!result.ok()) {
153 return result.status();
154 }
155
156 tiles_ = std::move(result.value());
157 tile_count_ = tiles_.size();
158 return absl::OkStatus();
159}
160
161absl::StatusOr<std::span<const gfx::TileInfo>> RoomObject::GetTiles() const {
162 if (!tiles_loaded_) {
163 const_cast<RoomObject*>(this)->EnsureTilesLoaded();
164 }
165
166 if (tiles_.empty()) {
167 return absl::FailedPreconditionError("No tiles loaded for object");
168 }
169
170 return std::span<const gfx::TileInfo>(tiles_.data(), tiles_.size());
171}
172
173absl::StatusOr<const gfx::TileInfo*> RoomObject::GetTile(int index) const {
174 if (!tiles_loaded_) {
175 const_cast<RoomObject*>(this)->EnsureTilesLoaded();
176 }
177
178 if (index < 0 || index >= static_cast<int>(tiles_.size())) {
179 return absl::OutOfRangeError(absl::StrFormat(
180 "Tile index %d out of range (0-%d)", index, tiles_.size() - 1));
181 }
182
183 return &tiles_[index];
184}
185
187 if (!tiles_loaded_) {
188 const_cast<RoomObject*>(this)->EnsureTilesLoaded();
189 }
190
191 return tile_count_;
192}
193
194// ============================================================================
195// Object Encoding/Decoding Implementation (Phase 1, Task 1.1)
196// ============================================================================
197
198int RoomObject::DetermineObjectType(uint8_t b1, uint8_t b3) {
199 // IMPORTANT: Check Type 2 FIRST to avoid boundary collision with Type 3.
200 // Type 2 objects with certain Y positions can produce b3 >= 0xF8, which
201 // would incorrectly trigger Type 3 decoding if we checked b3 first.
202 //
203 // Type 2: 111111xx xxxxyyyy yyiiiiii
204 // Discriminator: b1 >= 0xFC (top 6 bits all 1)
205 if (b1 >= 0xFC) {
206 return 2;
207 }
208
209 // Type 3: Objects with ID >= 0xF00
210 // These have b3 >= 0xF8 (top nibble is 0xF)
211 if (b3 >= 0xF8) {
212 return 3;
213 }
214
215 // Type 1: Standard objects (ID 0x00-0xFF)
216 return 1;
217}
218
219RoomObject RoomObject::DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3,
220 uint8_t layer) {
221 uint8_t x = 0;
222 uint8_t y = 0;
223 uint8_t size = 0;
224 uint16_t id = 0;
225
226 // IMPORTANT: Check Type 2 FIRST to avoid boundary collision with Type 3.
227 // Type 2 objects with certain Y positions can produce b3 >= 0xF8, which
228 // would incorrectly trigger Type 3 decoding if we checked b3 first.
229
230 // Type 2: 111111xx xxxxyyyy yyiiiiii
231 // Discriminator: b1 >= 0xFC (top 6 bits all 1)
232 if (b1 >= 0xFC) {
233 id = (b3 & 0x3F) | 0x100;
234 x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
235 y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
236 size = 0;
237 LOG_DEBUG("ObjectParser",
238 "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
239 b2, b3, id, x, y, size);
240 }
241 // Type 3: xxxxxxii yyyyyyii 11111iii
242 // Discriminator: b3 >= 0xF8 (top 5 bits all 1)
243 else if (b3 >= 0xF8) {
244 id = (static_cast<uint16_t>(b3) << 4) | 0x80 |
245 ((static_cast<uint16_t>(b2 & 0x03) << 2) + (b1 & 0x03));
246 x = (b1 & 0xFC) >> 2;
247 y = (b2 & 0xFC) >> 2;
248 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
249 LOG_DEBUG("ObjectParser",
250 "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
251 b2, b3, id, x, y, size);
252 }
253 // Type 1: xxxxxxss yyyyyyss iiiiiiii
254 else {
255 id = b3;
256 x = (b1 & 0xFC) >> 2;
257 y = (b2 & 0xFC) >> 2;
258 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
259 LOG_DEBUG("ObjectParser",
260 "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
261 b2, b3, id, x, y, size);
262 }
263
264 auto obj = RoomObject(static_cast<int16_t>(id), x, y, size, layer);
265
266 // Set all_bgs flag for objects that draw to both BG1 and BG2
267 // Based on ZScream/ObjectDrawer logic
268 if ((id >= 0x03 && id <= 0x04) || // Routine 3
269 (id >= 0x63 && id <= 0x64) || // Routine 9
270 // Routine 17 (Acute Diagonals)
271 id == 0x0C || id == 0x0D || id == 0x10 || id == 0x11 || id == 0x14 ||
272 id == 0x15 || id == 0x18 || id == 0x19 || id == 0x1C || id == 0x1D ||
273 id == 0x20 ||
274 // Routine 18 (Grave Diagonals)
275 id == 0x0E || id == 0x0F || id == 0x12 || id == 0x13 || id == 0x16 ||
276 id == 0x17 || id == 0x1A || id == 0x1B || id == 0x1E || id == 0x1F) {
277 obj.all_bgs_ = true;
278 }
279
280 return obj;
281}
282
284 ObjectBytes bytes;
285
286 // Determine type based on object ID
287 if (id_ >= 0x100 && id_ < 0x200) {
288 // Type 2: 111111xx xxxxyyyy yyiiiiii
289 bytes.b1 = 0xFC | ((x_ & 0x30) >> 4);
290 bytes.b2 = ((x_ & 0x0F) << 4) | ((y_ & 0x3C) >> 2);
291 bytes.b3 = ((y_ & 0x03) << 6) | (id_ & 0x3F);
292 } else if (id_ >= 0xF00) {
293 // Type 3: xxxxxxii yyyyyyii 11111iii
294 bytes.b1 = (x_ << 2) | (id_ & 0x03);
295 bytes.b2 = (y_ << 2) | ((id_ >> 2) & 0x03);
296 bytes.b3 = (id_ >> 4) & 0xFF;
297 } else {
298 // Type 1: xxxxxxss yyyyyyss iiiiiiii
299 uint8_t clamped_size = size_ > 15 ? 15 : size_;
300 bytes.b1 = (x_ << 2) | ((clamped_size >> 2) & 0x03);
301 bytes.b2 = (y_ << 2) | (clamped_size & 0x03);
302 bytes.b3 = static_cast<uint8_t>(id_);
303 }
304
305 return bytes;
306}
307
308} // namespace zelda3
309} // namespace yaze
auto data() const
Definition rom.h:135
auto size() const
Definition rom.h:134
Direct ROM parser for dungeon objects.
absl::StatusOr< std::vector< gfx::TileInfo > > ParseObject(int16_t object_id)
Parse object data directly from ROM.
absl::StatusOr< const gfx::TileInfo * > GetTile(int index) const
static RoomObject DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t layer)
std::vector< gfx::TileInfo > tiles_
ObjectBytes EncodeObjectToBytes() const
static int DetermineObjectType(uint8_t b1, uint8_t b3)
absl::Status LoadTilesWithParser()
uint8_t size() const
Definition room_object.h:78
absl::StatusOr< std::span< const gfx::TileInfo > > GetTiles() const
RoomObject(int16_t id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer=0)
Definition room_object.h:54
#define LOG_DEBUG(category, format,...)
Definition log.h:103
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378
SubtypeTableInfo GetSubtypeTable(int object_id)
constexpr int kRoomObjectSubtype3
Definition room_object.h:46
ObjectOption operator|(ObjectOption lhs, ObjectOption rhs)
ObjectOption operator^(ObjectOption lhs, ObjectOption rhs)
constexpr int kRoomObjectSubtype1
Definition room_object.h:44
constexpr int kRoomObjectSubtype2
Definition room_object.h:45
constexpr int kRoomObjectTileAddress
Definition room_object.h:47
ObjectOption operator~(ObjectOption option)
ObjectOption operator&(ObjectOption lhs, ObjectOption rhs)