5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
20 : base_ptr(base), index_mask(mask), id_offset(offset) {}
26 if (object_id >= 0xF80) {
30 }
else if (object_id >= 0x100) {
45 const int id = object_id;
47 if ((
id >= 0x03 &&
id <= 0x04) ||
48 (
id >= 0x63 &&
id <= 0x64) ||
50 id == 0x0C ||
id == 0x0D ||
id == 0x10 ||
id == 0x11 ||
id == 0x14 ||
51 id == 0x15 ||
id == 0x18 ||
id == 0x19 ||
id == 0x1C ||
id == 0x1D ||
54 id == 0x0E ||
id == 0x0F ||
id == 0x12 ||
id == 0x13 ||
id == 0x16 ||
55 id == 0x17 ||
id == 0x1A ||
id == 0x1B ||
id == 0x1E ||
id == 0x1F) {
64 static_cast<int>(rhs));
69 static_cast<int>(rhs));
74 static_cast<int>(rhs));
78 return static_cast<ObjectOption>(~static_cast<int>(option));
90 if (
rom_ ==
nullptr) {
92 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
93 (
id_ >= 0x100 &&
id_ <= 0x103)) {
94 LOG_DEBUG(
"RoomObject",
"EnsureTilesLoaded: obj=0x%03X ROM is NULL!",
102 if (parser_status.ok()) {
105 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
106 (
id_ >= 0x100 &&
id_ <= 0x103)) {
108 "EnsureTilesLoaded: obj=0x%03X loaded %zu tiles via parser",
115 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
116 (
id_ >= 0x100 &&
id_ <= 0x103)) {
118 "EnsureTilesLoaded: obj=0x%03X parser failed: %s, trying legacy",
119 id_, std::string(parser_status.message()).c_str());
126 SubtypeTableInfo sti = GetSubtypeTable(
id_);
128 int index = ((
id_ - sti.id_offset) & sti.index_mask);
129 int tile_ptr = sti.base_ptr + (index * 2);
132 if (tile_ptr < 0 || tile_ptr + 1 >= (
int)
rom_->
size()) {
134 LOG_DEBUG(
"RoomObject",
"Tile pointer out of bounds for object %04X",
id_);
140 int tile_rel = (int16_t)((rom_data[tile_ptr + 1] << 8) + rom_data[tile_ptr]);
145 if (pos < 0 || pos + 7 >= (
int)
rom_->
size()) {
147 LOG_DEBUG(
"RoomObject",
"Tile data position out of bounds for object %04X",
155 uint16_t w0 = (uint16_t)(rom_data[pos] | (rom_data[pos + 1] << 8));
156 uint16_t w1 = (uint16_t)(rom_data[pos + 2] | (rom_data[pos + 3] << 8));
157 uint16_t w2 = (uint16_t)(rom_data[pos + 4] | (rom_data[pos + 5] << 8));
158 uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
190 if (
rom_ ==
nullptr) {
191 return absl::InvalidArgumentError(
"ROM is null");
197 return result.status();
200 tiles_ = std::move(result.value());
202 return absl::OkStatus();
211 return absl::FailedPreconditionError(
"No tiles loaded for object");
214 return std::span<const gfx::TileInfo>(
tiles_.data(),
tiles_.size());
222 if (index < 0 || index >=
static_cast<int>(
tiles_.size())) {
223 return absl::OutOfRangeError(absl::StrFormat(
224 "Tile index %d out of range (0-%d)", index,
tiles_.size() - 1));
277 id = (b3 & 0x3F) | 0x100;
278 x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
279 y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
282 "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
287 else if (b3 >= 0xF8) {
288 id = (
static_cast<uint16_t
>(b3) << 4) | 0x80 |
289 ((
static_cast<uint16_t
>(b2 & 0x03) << 2) + (b1 & 0x03));
290 x = (b1 & 0xFC) >> 2;
291 y = (b2 & 0xFC) >> 2;
292 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
294 "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
300 x = (b1 & 0xFC) >> 2;
301 y = (b2 & 0xFC) >> 2;
302 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
304 "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
309 obj.RefreshDerivedFlagsFromId();
318 if (
id_ >= 0x100 &&
id_ < 0x200) {
320 bytes.
b1 = 0xFC | ((
x_ & 0x30) >> 4);
321 bytes.
b2 = ((
x_ & 0x0F) << 4) | ((
y_ & 0x3C) >> 2);
322 bytes.
b3 = ((
y_ & 0x03) << 6) | (
id_ & 0x3F);
323 }
else if (
id_ >= 0xF00) {
325 bytes.
b1 = (
x_ << 2) | (
id_ & 0x03);
326 bytes.
b2 = (
y_ << 2) | ((
id_ >> 2) & 0x03);
327 bytes.
b3 = (
id_ >> 4) & 0xFF;
330 uint8_t clamped_size =
size_ > 15 ? 15 :
size_;
331 bytes.
b1 = (
x_ << 2) | ((clamped_size >> 2) & 0x03);
332 bytes.
b2 = (
y_ << 2) | (clamped_size & 0x03);
333 bytes.
b3 =
static_cast<uint8_t
>(
id_);
340 return object_id >= 0x000 && object_id <= 0x0F7;
345 return std::min<uint8_t>(requested_size, 15);
347 if (object_id >= 0x100 && object_id <= 0x13F) {
350 if (object_id >= 0xF80 && object_id <= 0xFFF) {
351 return static_cast<uint8_t
>(((object_id & 0x03) << 2) |
352 ((object_id >> 2) & 0x03));
354 return requested_size;
365 const int layer =
object.GetLayerValue();
367 return absl::InvalidArgumentError(absl::StrFormat(
368 "Room object 0x%03X has invalid stream list index %d; expected 0..2",
371 if (
object.x_ > 63 ||
object.y_ > 63) {
372 return absl::InvalidArgumentError(absl::StrFormat(
373 "Room object 0x%03X has out-of-range coordinates (%d, %d); "
375 object.id_,
object.x_,
object.y_));
378 int expected_type = 0;
379 if (
object.id_ >= 0x000 &&
object.id_ <= 0x0F7) {
381 }
else if (
object.id_ >= 0x100 &&
object.id_ <= 0x13F) {
383 }
else if (
object.id_ >= 0xF80 &&
object.id_ <= 0xFFF) {
386 return absl::InvalidArgumentError(absl::StrFormat(
387 "Room object ID 0x%03X is not representable without aliasing; "
388 "expected 0x000..0x0F7, 0x100..0x13F, or 0xF80..0xFFF",
392 const uint8_t canonical_size =
394 if (
object.size_ != canonical_size) {
395 if (expected_type == 1) {
396 return absl::InvalidArgumentError(
397 absl::StrFormat(
"Room object 0x%03X has noncanonical size 0x%02X; "
398 "Type 1 size must be 0x00..0x0F",
399 object.id_,
object.size_));
401 return absl::InvalidArgumentError(absl::StrFormat(
402 "Room object 0x%03X has noncanonical size 0x%02X; expected 0x%02X",
403 object.id_,
object.size_, canonical_size));
409 if (expected_type != 2 &&
object.x_ == 63) {
410 return absl::InvalidArgumentError(absl::StrFormat(
411 "Room object 0x%03X at x=63 collides with the Type 2 discriminator",
416 if ((encoded.
b1 == 0xFF && encoded.
b2 == 0xFF) ||
417 (encoded.
b1 == 0xF0 && encoded.
b2 == 0xFF)) {
418 return absl::InvalidArgumentError(absl::StrFormat(
419 "Room object 0x%03X encodes with reserved stream-control prefix "
421 object.id_, encoded.
b1, encoded.
b2));
424 const int encoded_type =
426 if (encoded_type != expected_type) {
427 return absl::InvalidArgumentError(absl::StrFormat(
428 "Room object 0x%03X encodes with Type %d discriminator, expected "
430 object.id_, encoded_type, expected_type));
434 encoded.
b1, encoded.
b2, encoded.
b3,
435 static_cast<uint8_t
>(
object.GetLayerValue()));
436 if (decoded.
id_ !=
object.id_ || decoded.
x_ !=
object.x_ ||
437 decoded.
y_ !=
object.y_) {
438 return absl::InvalidArgumentError(absl::StrFormat(
439 "Room object 0x%03X at (%d, %d) aliases to 0x%03X at (%d, %d) "
441 object.id_,
object.x_,
object.y_, decoded.
id_, decoded.
x_, decoded.
y_));
444 return absl::OkStatus();
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
void RefreshDerivedFlagsFromId()
static int DetermineObjectType(uint8_t b1, uint8_t b3)
absl::Status LoadTilesWithParser()
absl::StatusOr< std::span< const gfx::TileInfo > > GetTiles() const
void InvalidateTileCache()
RoomObject(int16_t id, uint8_t x, uint8_t y, uint8_t size, uint8_t layer=0)
#define LOG_DEBUG(category, format,...)
TileInfo WordToTileInfo(uint16_t word)
bool IsAllBgsObjectId(int object_id)
SubtypeTableInfo GetSubtypeTable(int object_id)
uint8_t DefaultRoomObjectSizeForPlacement(int object_id)
bool IsRoomObjectSizeEditable(int object_id)
constexpr int kRoomObjectSubtype3
ObjectOption operator|(ObjectOption lhs, ObjectOption rhs)
ObjectOption operator^(ObjectOption lhs, ObjectOption rhs)
uint8_t CanonicalRoomObjectSize(int object_id, uint8_t requested_size)
constexpr int kRoomObjectSubtype1
constexpr int kRoomObjectSubtype2
constexpr int kRoomObjectTileAddress
ObjectOption operator~(ObjectOption option)
ObjectOption operator&(ObjectOption lhs, ObjectOption rhs)
absl::Status ValidateRoomObjectStreamEntryForSave(const RoomObject &object)
SubtypeTableInfo(int base, int mask, int offset=0)