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));
93 if (
rom_ ==
nullptr) {
95 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
96 (
id_ >= 0x100 &&
id_ <= 0x103)) {
97 LOG_DEBUG(
"RoomObject",
"EnsureTilesLoaded: obj=0x%03X ROM is NULL!",
105 if (parser_status.ok()) {
109 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
110 (
id_ >= 0x100 &&
id_ <= 0x103)) {
112 "EnsureTilesLoaded: obj=0x%03X loaded %zu tiles via parser",
119 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
120 (
id_ >= 0x100 &&
id_ <= 0x103)) {
122 "EnsureTilesLoaded: obj=0x%03X parser failed: %s, trying legacy",
123 id_, std::string(parser_status.message()).c_str());
130 SubtypeTableInfo sti = GetSubtypeTable(
id_);
132 int index = ((
id_ - sti.id_offset) & sti.index_mask);
133 int tile_ptr = sti.base_ptr + (index * 2);
136 if (tile_ptr < 0 || tile_ptr + 1 >= (
int)
rom_->
size()) {
138 LOG_DEBUG(
"RoomObject",
"Tile pointer out of bounds for object %04X",
id_);
145 int tile_rel = (int16_t)((rom_data[tile_ptr + 1] << 8) + rom_data[tile_ptr]);
150 if (pos < 0 || pos + 7 >= (
int)
rom_->
size()) {
152 LOG_DEBUG(
"RoomObject",
"Tile data position out of bounds for object %04X",
161 uint16_t w0 = (uint16_t)(rom_data[pos] | (rom_data[pos + 1] << 8));
162 uint16_t w1 = (uint16_t)(rom_data[pos + 2] | (rom_data[pos + 3] << 8));
163 uint16_t w2 = (uint16_t)(rom_data[pos + 4] | (rom_data[pos + 5] << 8));
164 uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
216 if (
rom_ ==
nullptr) {
217 return absl::InvalidArgumentError(
"ROM is null");
223 return result.status();
226 tiles_ = std::move(result.value());
228 return absl::OkStatus();
235 return absl::FailedPreconditionError(
"No tiles loaded for object");
238 return std::span<const gfx::TileInfo>(
tiles_.data(),
tiles_.size());
244 if (index < 0 || index >=
static_cast<int>(
tiles_.size())) {
245 return absl::OutOfRangeError(absl::StrFormat(
246 "Tile index %d out of range (0-%d)", index,
tiles_.size() - 1));
297 id = (b3 & 0x3F) | 0x100;
298 x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
299 y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
302 "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
307 else if (b3 >= 0xF8) {
308 id = (
static_cast<uint16_t
>(b3) << 4) | 0x80 |
309 ((
static_cast<uint16_t
>(b2 & 0x03) << 2) + (b1 & 0x03));
310 x = (b1 & 0xFC) >> 2;
311 y = (b2 & 0xFC) >> 2;
312 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
314 "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
320 x = (b1 & 0xFC) >> 2;
321 y = (b2 & 0xFC) >> 2;
322 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
324 "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
329 obj.RefreshDerivedFlagsFromId();
338 if (
id_ >= 0x100 &&
id_ < 0x200) {
340 bytes.
b1 = 0xFC | ((
x_ & 0x30) >> 4);
341 bytes.
b2 = ((
x_ & 0x0F) << 4) | ((
y_ & 0x3C) >> 2);
342 bytes.
b3 = ((
y_ & 0x03) << 6) | (
id_ & 0x3F);
343 }
else if (
id_ >= 0xF00) {
345 bytes.
b1 = (
x_ << 2) | (
id_ & 0x03);
346 bytes.
b2 = (
y_ << 2) | ((
id_ >> 2) & 0x03);
347 bytes.
b3 = (
id_ >> 4) & 0xFF;
350 uint8_t clamped_size =
size_ > 15 ? 15 :
size_;
351 bytes.
b1 = (
x_ << 2) | ((clamped_size >> 2) & 0x03);
352 bytes.
b2 = (
y_ << 2) | (clamped_size & 0x03);
353 bytes.
b3 =
static_cast<uint8_t
>(
id_);
360 return object_id >= 0x000 && object_id <= 0x0F7;
365 return std::min<uint8_t>(requested_size, 15);
367 if (object_id >= 0x100 && object_id <= 0x13F) {
370 if (object_id >= 0xF80 && object_id <= 0xFFF) {
371 return static_cast<uint8_t
>(((object_id & 0x03) << 2) |
372 ((object_id >> 2) & 0x03));
374 return requested_size;
385 const int layer =
object.GetLayerValue();
387 return absl::InvalidArgumentError(absl::StrFormat(
388 "Room object 0x%03X has invalid stream list index %d; expected 0..2",
391 if (
object.x_ > 63 ||
object.y_ > 63) {
392 return absl::InvalidArgumentError(absl::StrFormat(
393 "Room object 0x%03X has out-of-range coordinates (%d, %d); "
395 object.id_,
object.x_,
object.y_));
398 int expected_type = 0;
399 if (
object.id_ >= 0x000 &&
object.id_ <= 0x0F7) {
401 }
else if (
object.id_ >= 0x100 &&
object.id_ <= 0x13F) {
403 }
else if (
object.id_ >= 0xF80 &&
object.id_ <= 0xFFF) {
406 return absl::InvalidArgumentError(absl::StrFormat(
407 "Room object ID 0x%03X is not representable without aliasing; "
408 "expected 0x000..0x0F7, 0x100..0x13F, or 0xF80..0xFFF",
412 const uint8_t canonical_size =
414 if (
object.size_ != canonical_size) {
415 if (expected_type == 1) {
416 return absl::InvalidArgumentError(
417 absl::StrFormat(
"Room object 0x%03X has noncanonical size 0x%02X; "
418 "Type 1 size must be 0x00..0x0F",
419 object.id_,
object.size_));
421 return absl::InvalidArgumentError(absl::StrFormat(
422 "Room object 0x%03X has noncanonical size 0x%02X; expected 0x%02X",
423 object.id_,
object.size_, canonical_size));
429 if (expected_type != 2 &&
object.x_ == 63) {
430 return absl::InvalidArgumentError(absl::StrFormat(
431 "Room object 0x%03X at x=63 collides with the Type 2 discriminator",
436 if ((encoded.
b1 == 0xFF && encoded.
b2 == 0xFF) ||
437 (encoded.
b1 == 0xF0 && encoded.
b2 == 0xFF)) {
438 return absl::InvalidArgumentError(absl::StrFormat(
439 "Room object 0x%03X encodes with reserved stream-control prefix "
441 object.id_, encoded.
b1, encoded.
b2));
444 const int encoded_type =
446 if (encoded_type != expected_type) {
447 return absl::InvalidArgumentError(absl::StrFormat(
448 "Room object 0x%03X encodes with Type %d discriminator, expected "
450 object.id_, encoded_type, expected_type));
454 encoded.
b1, encoded.
b2, encoded.
b3,
455 static_cast<uint8_t
>(
object.GetLayerValue()));
456 if (decoded.
id_ !=
object.id_ || decoded.
x_ !=
object.x_ ||
457 decoded.
y_ !=
object.y_) {
458 return absl::InvalidArgumentError(absl::StrFormat(
459 "Room object 0x%03X at (%d, %d) aliases to 0x%03X at (%d, %d) "
461 object.id_,
object.x_,
object.y_, decoded.
id_, decoded.
x_, decoded.
y_));
464 return absl::OkStatus();
uint64_t object_tile_revision() const
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_
bool IsTrackedTileCacheCurrent() const
ObjectBytes EncodeObjectToBytes() const
uint64_t tile_cache_revision_
void RefreshDerivedFlagsFromId()
void MarkTileCacheCurrent()
void MarkTileCacheUntracked()
static int DetermineObjectType(uint8_t b1, uint8_t b3)
absl::Status LoadTilesWithParser()
absl::StatusOr< std::span< const gfx::TileInfo > > GetTiles() const
const Rom * tile_cache_rom_identity_
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)