5#include "absl/status/status.h"
6#include "absl/strings/str_format.h"
24 : base_ptr(base), index_mask(mask), id_offset(offset) {}
30 if (object_id >= 0xF80) {
34 }
else if (object_id >= 0x100) {
47 static_cast<int>(rhs));
52 static_cast<int>(rhs));
57 static_cast<int>(rhs));
61 return static_cast<ObjectOption>(~static_cast<int>(option));
76 if (
rom_ ==
nullptr) {
78 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
79 (
id_ >= 0x100 &&
id_ <= 0x103)) {
80 LOG_DEBUG(
"RoomObject",
"EnsureTilesLoaded: obj=0x%03X ROM is NULL!",
88 if (parser_status.ok()) {
92 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
93 (
id_ >= 0x100 &&
id_ <= 0x103)) {
95 "EnsureTilesLoaded: obj=0x%03X loaded %zu tiles via parser",
102 if (
id_ == 0x001 ||
id_ == 0x002 ||
id_ == 0x061 ||
id_ == 0x062 ||
103 (
id_ >= 0x100 &&
id_ <= 0x103)) {
105 "EnsureTilesLoaded: obj=0x%03X parser failed: %s, trying legacy",
106 id_, std::string(parser_status.message()).c_str());
113 SubtypeTableInfo sti = GetSubtypeTable(
id_);
115 int index = ((
id_ - sti.id_offset) & sti.index_mask);
116 int tile_ptr = sti.base_ptr + (index * 2);
119 if (tile_ptr < 0 || tile_ptr + 1 >= (
int)
rom_->
size()) {
121 LOG_DEBUG(
"RoomObject",
"Tile pointer out of bounds for object %04X",
id_);
128 int tile_rel = (int16_t)((rom_data[tile_ptr + 1] << 8) + rom_data[tile_ptr]);
133 if (pos < 0 || pos + 7 >= (
int)
rom_->
size()) {
135 LOG_DEBUG(
"RoomObject",
"Tile data position out of bounds for object %04X",
144 uint16_t w0 = (uint16_t)(rom_data[pos] | (rom_data[pos + 1] << 8));
145 uint16_t w1 = (uint16_t)(rom_data[pos + 2] | (rom_data[pos + 3] << 8));
146 uint16_t w2 = (uint16_t)(rom_data[pos + 4] | (rom_data[pos + 5] << 8));
147 uint16_t w3 = (uint16_t)(rom_data[pos + 6] | (rom_data[pos + 7] << 8));
197 if (
rom_ ==
nullptr) {
198 return absl::InvalidArgumentError(
"ROM is null");
204 return result.status();
207 tiles_ = std::move(result.value());
209 return absl::OkStatus();
216 return absl::FailedPreconditionError(
"No tiles loaded for object");
219 return std::span<const gfx::TileInfo>(
tiles_.data(),
tiles_.size());
225 if (index < 0 || index >=
static_cast<int>(
tiles_.size())) {
226 return absl::OutOfRangeError(absl::StrFormat(
227 "Tile index %d out of range (0-%d)", index,
tiles_.size() - 1));
278 id = (b3 & 0x3F) | 0x100;
279 x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
280 y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
283 "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
288 else if (b3 >= 0xF8) {
289 id = (
static_cast<uint16_t
>(b3) << 4) | 0x80 |
290 ((
static_cast<uint16_t
>(b2 & 0x03) << 2) + (b1 & 0x03));
291 x = (b1 & 0xFC) >> 2;
292 y = (b2 & 0xFC) >> 2;
293 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
295 "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
301 x = (b1 & 0xFC) >> 2;
302 y = (b2 & 0xFC) >> 2;
303 size = ((b1 & 0x03) << 2) | (b2 & 0x03);
305 "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d", b1,
316 if (
id_ >= 0x100 &&
id_ < 0x200) {
318 bytes.
b1 = 0xFC | ((
x_ & 0x30) >> 4);
319 bytes.
b2 = ((
x_ & 0x0F) << 4) | ((
y_ & 0x3C) >> 2);
320 bytes.
b3 = ((
y_ & 0x03) << 6) | (
id_ & 0x3F);
321 }
else if (
id_ >= 0xF00) {
323 bytes.
b1 = (
x_ << 2) | (
id_ & 0x03);
324 bytes.
b2 = (
y_ << 2) | ((
id_ >> 2) & 0x03);
325 bytes.
b3 = (
id_ >> 4) & 0xFF;
328 uint8_t clamped_size =
size_ > 15 ? 15 :
size_;
329 bytes.
b1 = (
x_ << 2) | ((clamped_size >> 2) & 0x03);
330 bytes.
b2 = (
y_ << 2) | (clamped_size & 0x03);
331 bytes.
b3 =
static_cast<uint8_t
>(
id_);
338 return object_id >= 0x000 && object_id <= 0x0F7;
376 if (object_id == 0xC1 || object_id == 0xDC || object_id == 0xDD) {
377 const auto [width, height] =
379 return horizontal ? width : height;
381 const int axis = (size >> (horizontal ? 2 : 0)) & 0x03;
382 return (axis + 1) * step;
391 delta = std::clamp(delta, -15, 15);
393 const int shift = horizontal ? 2 : 0;
394 const int mask = 0x03 << shift;
395 const int axis = (size >> shift) & 0x03;
396 return static_cast<uint8_t
>(((size & 0x0F) & ~mask) |
397 (std::clamp(axis + delta, 0, 3) << shift));
399 return static_cast<uint8_t
>(
400 std::clamp(
static_cast<int>(size) + delta, 0, 15));
405 return std::min<uint8_t>(requested_size, 15);
407 if (object_id >= 0x100 && object_id <= 0x13F) {
410 if (object_id >= 0xF80 && object_id <= 0xFFF) {
411 return static_cast<uint8_t
>(((object_id & 0x03) << 2) |
412 ((object_id >> 2) & 0x03));
414 return requested_size;
425 const int layer =
object.GetLayerValue();
427 return absl::InvalidArgumentError(absl::StrFormat(
428 "Room object 0x%03X has invalid stream list index %d; expected 0..2",
431 if (
object.x_ > 63 ||
object.y_ > 63) {
432 return absl::InvalidArgumentError(absl::StrFormat(
433 "Room object 0x%03X has out-of-range coordinates (%d, %d); "
435 object.id_,
object.x_,
object.y_));
438 int expected_type = 0;
439 if (
object.id_ >= 0x000 &&
object.id_ <= 0x0F7) {
441 }
else if (
object.id_ >= 0x100 &&
object.id_ <= 0x13F) {
443 }
else if (
object.id_ >= 0xF80 &&
object.id_ <= 0xFFF) {
446 return absl::InvalidArgumentError(absl::StrFormat(
447 "Room object ID 0x%03X is not representable without aliasing; "
448 "expected 0x000..0x0F7, 0x100..0x13F, or 0xF80..0xFFF",
452 const uint8_t canonical_size =
454 if (
object.size_ != canonical_size) {
455 if (expected_type == 1) {
456 return absl::InvalidArgumentError(
457 absl::StrFormat(
"Room object 0x%03X has noncanonical size 0x%02X; "
458 "Type 1 size must be 0x00..0x0F",
459 object.id_,
object.size_));
461 return absl::InvalidArgumentError(absl::StrFormat(
462 "Room object 0x%03X has noncanonical size 0x%02X; expected 0x%02X",
463 object.id_,
object.size_, canonical_size));
469 if (expected_type != 2 &&
object.x_ == 63) {
470 return absl::InvalidArgumentError(absl::StrFormat(
471 "Room object 0x%03X at x=63 collides with the Type 2 discriminator",
476 if ((encoded.
b1 == 0xFF && encoded.
b2 == 0xFF) ||
477 (encoded.
b1 == 0xF0 && encoded.
b2 == 0xFF)) {
478 return absl::InvalidArgumentError(absl::StrFormat(
479 "Room object 0x%03X encodes with reserved stream-control prefix "
481 object.id_, encoded.
b1, encoded.
b2));
484 const int encoded_type =
486 if (encoded_type != expected_type) {
487 return absl::InvalidArgumentError(absl::StrFormat(
488 "Room object 0x%03X encodes with Type %d discriminator, expected "
490 object.id_, encoded_type, expected_type));
494 encoded.
b1, encoded.
b2, encoded.
b3,
495 static_cast<uint8_t
>(
object.GetLayerValue()));
496 if (decoded.
id_ !=
object.id_ || decoded.
x_ !=
object.x_ ||
497 decoded.
y_ !=
object.y_) {
498 return absl::InvalidArgumentError(absl::StrFormat(
499 "Room object 0x%03X at (%d, %d) aliases to 0x%03X at (%d, %d) "
501 object.id_,
object.x_,
object.y_, decoded.
id_, decoded.
x_, decoded.
y_));
504 return absl::OkStatus();
uint64_t object_tile_revision() const
int GetSubtypeCount(int object_id) const
static CustomObjectManager & Get()
static DrawRoutineRegistry & Get()
std::pair< int, int > GetDimensions(int object_id, int size) const
static ObjectDimensionTable & Get()
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 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)
constexpr int kTableRock4x4_1to16
constexpr int kClosedChestPlatform
constexpr int k4x4FloorIn4x4SuperSquare
constexpr int k4x4BlocksIn4x4SuperSquare
constexpr int k3x3FloorIn4x4SuperSquare
constexpr int k4x4FloorOneIn4x4SuperSquare
constexpr int kSpike2x2In4x4SuperSquare
constexpr int kOpenChestPlatform
constexpr int k4x4FloorTwoIn4x4SuperSquare
SubtypeTableInfo GetSubtypeTable(int object_id)
uint8_t ResizeRoomObjectByDelta(int object_id, uint8_t size, int delta, bool horizontal)
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
int RoomObjectSizeAxisStep(int object_id)
constexpr int kRoomObjectTileAddress
ObjectOption operator~(ObjectOption option)
ObjectOption operator&(ObjectOption lhs, ObjectOption rhs)
absl::Status ValidateRoomObjectStreamEntryForSave(const RoomObject &object)
bool IsRoomObjectResizable(int object_id)
int RoomObjectSizeAxisTiles(int object_id, uint8_t size, bool horizontal)
bool kEnableCustomObjects
SubtypeTableInfo(int base, int mask, int offset=0)