7#include "absl/strings/str_format.h"
37 return id == 0x12D ||
id == 0x12E ||
id == 0x12F ||
38 (
id >= 0x138 &&
id <= 0x13B) || (
id >= 0xF9E &&
id <= 0xFA1) ||
39 (
id >= 0xFA6 &&
id <= 0xFA9);
54 return (direction == 0 || direction == 2) ? position < 6 : position >= 6;
75std::string RoomHex(
int room_id) {
76 return absl::StrFormat(
"0x%02X", room_id);
83 return "falling square (overlord 0x0A)";
85 return "falling bridge (overlord 0x0B)";
90 return "falling tiles (overlord 0x0C-0x0F)";
98 const std::set<uint8_t>& warp_tag_ids) {
102 for (uint8_t tag : {facts.
tag1, facts.
tag2}) {
118 bool pit_outside_braziers =
false;
127 Rom* rom,
const Room& room,
const std::vector<RoomObject>& layout_objects,
136 std::vector<ObjectDrawer::TileTrace> trace;
144 return absl::CancelledError(
"Room link scan cancelled");
147 object.layer_ = layer;
151 return absl::CancelledError(
"Room link scan cancelled");
153 for (
const auto& tile : trace) {
155 return absl::CancelledError(
"Room link scan cancelled");
157 if (tile.x_tile < 0 || tile.y_tile < 0 ||
161 const size_t position =
164 (lower ? bg2 : bg1)[position] = tile.tile_id & 0x3FF;
165 (lower ? bg2_owner : bg1_owner)[position] =
object.id_;
167 return absl::OkStatus();
170 for (
const auto&
object : layout_objects) {
174 for (
int list = 0; list < 3; ++list) {
175 for (
const auto&
object : objects) {
177 return absl::CancelledError(
"Room link scan cancelled");
179 if (!IsEncodedStreamObject(
object)) {
182 const int list_index = std::min<int>(
object.GetLayerValue(), 2);
183 if (list_index != list) {
187 static_cast<uint8_t
>(list))));
191 for (
int layer = 0; layer < 2; ++layer) {
192 const auto& words = layer == 0 ? bg1 : bg2;
193 const auto& owners = layer == 0 ? bg1_owner : bg2_owner;
194 for (
size_t position = 0; position < words.size(); ++position) {
196 return absl::CancelledError(
"Room link scan cancelled");
198 const int tile = words[position];
223 return "unplaced_entrance";
233 return "header_only";
237 return "dungeon_map";
239 return "teleport_door";
266 const int low = room_id & 0xFF;
267 const int row = low >> 4;
268 const int col = low & 0x0F;
272 target = row > 0 ? room_id - 16 : -1;
275 target = row < 15 ? room_id + 16 : -1;
278 target = col > 0 ? room_id - 1 : -1;
281 target = col < 15 ? room_id + 1 : -1;
298 const Rom& rom,
int entrance_id) {
302 const auto& data = rom.
vector();
309 if (room_pc + 1 >=
static_cast<int>(data.size()) ||
310 dungeon_pc >=
static_cast<int>(data.size())) {
314 target.
room_id = data[room_pc] | (data[room_pc + 1] << 8);
323 return (
id >= 0x023 &&
id <= 0x02E) ||
id == 0x06A ||
id == 0x06B ||
324 id == 0x0A4 ||
id == 0x0C2 ||
id == 0x0C3 ||
id == 0xFC7 ||
332 return id == 0xFCA ||
id == 0xFCF;
336 switch (
static_cast<TagKey>(tag)) {
356 return attribute == 0x20 || (attribute >= 0xB0 && attribute <= 0xBD);
360 return attribute == 0x4B;
368 return absl::CancelledError(
"Room link scan cancelled");
373 facts.
tag1 =
static_cast<uint8_t
>(room.
tag1());
374 facts.
tag2 =
static_cast<uint8_t
>(room.
tag2());
376 for (
int slot = 0; slot < 4; ++slot) {
380 pit_table !=
nullptr &&
381 pit_table->
Contains(
static_cast<uint16_t
>(room.
id()));
386 const auto& layout_objects = layout.
GetObjects();
388 bool has_stairs =
false;
389 for (
const auto&
object : layout_objects) {
394 if (!IsEncodedStreamObject(
object)) {
399 has_stairs |= IsHeaderStairObject(
object.id_);
407 if (rom !=
nullptr && rom->
is_loaded()) {
409 const DrawnTiles drawn,
410 ScanDrawnTiles(rom, room, layout_objects, custom_objects, stop_token));
418 for (
const auto& sprite : room.
GetSprites()) {
419 if (sprite.IsOverlord()) {
420 if (
const char*
name = HoleOverlordName(sprite.id())) {
424 }
else if (sprite.id() == kGanonSprite) {
425 facts.
hole_sprite =
"Ganon's falling floor (sprite 0xD6)";
432 if (has_stairs && rom !=
nullptr) {
434 collision_input.objects.insert(collision_input.objects.begin(),
435 layout_objects.begin(),
436 layout_objects.end());
437 const size_t prefix_size = layout_objects.size();
438 const auto resolutions =
440 int placement_index = 0;
441 for (
const auto& resolution : resolutions) {
442 if (resolution.object_index < prefix_size) {
445 if (resolution.slot.has_value() && *resolution.slot >= 0 &&
446 *resolution.slot < 4) {
449 }
else if (placement_index < 4 &&
459 for (
const auto& door : room.
GetDoors()) {
461 fact.
direction =
static_cast<int>(door.direction);
462 const auto [tile_x, tile_y] = door.GetTileCoords();
467 fact.
type =
static_cast<uint8_t
>(door.type);
472 }
else if (IsKeyStairDoor(door.type)) {
479 facts.
doors.push_back(fact);
482 return absl::CancelledError(
"Room link scan cancelled");
489 const std::set<uint8_t>& warp_tag_ids) {
490 std::vector<RoomLink> links;
495 if (self ==
nullptr) {
510 auto same_blockset = [&](
int room) {
515 auto stair_targets = [&](
int room) {
516 std::vector<int> targets;
518 if (other ==
nullptr) {
521 for (
int slot = 0; slot < 4; ++slot) {
531 std::array<bool, 4> teleport_slot{};
532 for (
size_t index = 0; index < f.
doors.size(); ++index) {
533 const auto& door = f.
doors[index];
539 const int slot = door.direction == kDirectionEast ? 3 : 2;
545 link.
door =
static_cast<int>(index);
548 link.
detail = absl::StrFormat(
549 "teleport door from %s (%s wall, stair "
551 RoomHex(room_id), DirectionName(door.direction), slot + 1);
552 teleport_slot[slot] =
true;
553 links.push_back(std::move(link));
562 for (
const auto& back : other->doors) {
566 back.direction == OppositeDirection(door.direction) &&
567 std::abs(back.along - door.along) <= 3) {
575 link.
door =
static_cast<int>(index);
580 ? absl::StrFormat(
"door from %s (%s wall)", RoomHex(room_id),
581 DirectionName(door.direction))
582 : absl::StrFormat(
"one-sided door from %s (%s wall, no door back)",
583 RoomHex(room_id), DirectionName(door.direction));
584 links.push_back(std::move(link));
588 const bool warp_tag =
589 warp_tag_ids.contains(f.
tag1) || warp_tag_ids.contains(f.
tag2);
590 for (
int slot = 0; slot < 4; ++slot) {
597 const auto back = stair_targets(link.
to);
599 same_blockset(link.
to) ||
600 std::find(back.begin(), back.end(), room_id) != back.end();
601 link.
detail = absl::StrFormat(
602 "stair (slot %d) in %s%s", slot + 1, RoomHex(room_id),
604 }
else if (warp_tag) {
608 link.
detail = absl::StrFormat(
"warp tag quadrant %d in %s", slot + 1,
610 }
else if (teleport_slot[slot]) {
614 link.
detail = absl::StrFormat(
615 "header stair slot %d of %s (no stair object uses it)", slot + 1,
620 links.push_back(std::move(link));
632 header.
detail = absl::StrFormat(
633 "header holewarp of %s (overridden by warp tag quadrant slots)",
635 links.push_back(std::move(header));
642 const int hole_tag = LiveHoleTag(f, warp_tag_ids);
643 std::string hole_source;
647 ?
"large-brazier pits (entered by falling from the "
650 }
else if (hole_tag > 0) {
652 hole_source = absl::StrFormat(
"tag-driven holes (tag 0x%02X)", hole_tag);
653 }
else if (hole_tag == 0) {
655 hole_source =
"tag-driven holes";
663 hole.
detail = absl::StrFormat(
"warp tiles in %s", RoomHex(room_id));
666 hole.
detail = absl::StrFormat(
"%s in %s", hole_source, RoomHex(room_id));
667 }
else if (!hole_source.empty()) {
668 hole.
detail = absl::StrFormat(
669 "holewarp of %s (its %s only cost a heart: RoomsWithPitDamage)",
670 RoomHex(room_id), hole_source);
673 hole.
detail = absl::StrFormat(
674 "header holewarp of %s (no pits, holes or warp tiles there)",
680 hole.
detail +=
" (other blockset)";
682 links.push_back(std::move(hole));
689 auto table = std::make_unique<PitDamageTable>();
703 auto& slot =
facts_[room_id];
704 if (!slot.has_value()) {
708 if (!collected.ok()) {
712 slot = std::move(*collected);
718 return [
this](
int room_id) {
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
bool stop_requested() const noexcept
Draws dungeon objects to background buffers using game patterns.
absl::Status DrawObject(const RoomObject &object, gfx::BackgroundBuffer &bg1, gfx::BackgroundBuffer &bg2, const gfx::PaletteGroup &palette_group, const DungeonState *state=nullptr, gfx::BackgroundBuffer *layout_bg1=nullptr, gfx::BackgroundBuffer *layout_bg2=nullptr)
Draw a room object to background buffers.
void SetTraceCollector(std::vector< TileTrace > *collector, bool trace_only=false)
static absl::Status LoadFromRom(Rom *rom, PitDamageTable *out)
bool Contains(uint16_t room_id) const
const std::vector< RoomObject > & GetObjects() const
absl::Status LoadLayout(int layout_id, util::StopToken stop_token={})
std::unique_ptr< PitDamageTable > pit_table_
RoomLinkFactsCache(Rom *rom)
const RoomLinkFacts * Get(int room_id)
RoomLinkFactsLookup AsLookup()
std::vector< std::optional< RoomLinkFacts > > facts_
const CustomCollisionMap & custom_collision() const
uint8_t staircase_room(int index) const
const std::vector< Door > & GetDoors() const
const std::vector< zelda3::Sprite > & GetSprites() const
const std::vector< RoomObject > & GetTileObjects() const
uint8_t layout_id() const
bool has_custom_collision() const
#define ASSIGN_OR_RETURN(type_variable_name, expression)
constexpr int kLargeBrazierObject
int LiveHoleTag(const RoomLinkFacts &facts, const std::set< uint8_t > &warp_tag_ids)
bool IsKeyStairDoor(DoorType type)
const char * HoleOverlordName(int id)
constexpr uint8_t kGanonSprite
bool IsHeaderStairObject(int id)
bool IsOuterDoorPosition(int direction, int position)
const char * DirectionName(int direction)
constexpr int kDirectionWest
int OppositeDirection(int direction)
absl::StatusOr< DrawnTiles > ScanDrawnTiles(Rom *rom, const Room &room, const std::vector< RoomObject > &layout_objects, const CustomObjectRenderSnapshot *custom_objects, util::StopToken stop_token)
constexpr int kDirectionEast
Zelda 3 specific classes and functions.
bool IsWarpTileObjectId(int id)
const char * RoomReferenceKindName(RoomReferenceKind kind)
DoorType
Door types from ALTTP.
@ SmallKeyStairsDown
Small key stairs (downwards)
@ SmallKeyStairsUp
Small key stairs (upwards)
@ SmallKeyStairsDownLower
Small key stairs (lower layer; downwards)
@ SmallKeyStairsUpLower
Small key stairs (lower layer; upwards)
constexpr int ResolveHeaderDestinationRoom(int source_room_id, uint8_t header_byte)
bool UsesExpandedEntranceTables(const Rom &rom)
constexpr int kRoomTilemapSize
std::optional< DungeonEntranceTarget > ReadDungeonEntranceTarget(const Rom &rom, int entrance_id)
constexpr bool IsRoomConnectionDoorType(DoorType type)
Return true when a door can represent an adjacent-room connection.
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
@ Open_Chest_Activate_Holes_0
constexpr size_t kTileAttributeTableSize
bool IsDungeonRoomId(int room_id)
bool IsPitObjectId(int id)
int CheckedHeaderDestinationRoom(int source_room_id, uint8_t header_byte)
std::vector< RoomLink > CollectRoomLinks(int room_id, const RoomLinkFactsLookup &lookup, const std::set< uint8_t > &warp_tag_ids)
int DungeonNeighborRoom(int room_id, int direction)
constexpr int kDungeonRoomCount
std::array< uint8_t, kTileAttributeTableSize > LoadUnderworldTileAttributeTable(const Rom &rom, uint8_t blockset)
absl::StatusOr< RoomLinkFacts > CollectRoomLinkFacts(Rom *rom, const Room &room, const PitDamageTable *pit_table, const CustomObjectRenderSnapshot *custom_objects, util::StopToken stop_token)
bool IsWarpTileAttribute(uint8_t attribute)
bool IsHoleRoomTag(uint8_t tag)
constexpr size_t kRoomTilemapWords
const char * RoomDoorRoleName(RoomDoorRole role)
Room LoadRoomFromRom(Rom *rom, int room_id)
constexpr int kDungeonEntranceCount
constexpr bool IsExitDoorType(DoorType type)
Return true for terminal exits that do not pair with another room.
bool IsPitTileAttribute(uint8_t attribute)
std::function< const RoomLinkFacts *(int room_id)> RoomLinkFactsLookup
RoomCollisionInput MakeRoomCollisionInput(const Room &room, const RoomTilemaps &tilemaps)
constexpr uint8_t kTeleportDoorType
std::vector< StaircaseSlotResolution > ResolveVanillaStaircaseSlots(const Rom &rom, const RoomCollisionInput &input, const UnderworldRoomLoadState &state)
absl::StatusOr< EntranceLayout > ResolveEntranceLayout(std::span< const uint8_t > data)
#define RETURN_IF_ERROR(expr)
Represents a group of palettes.
std::array< uint8_t, 64 *64 > tiles
std::array< bool, 4 > stair_slot_used
std::array< bool, 4 > stair_slot_estimated
bool pits_only_in_braziers
std::vector< Door > doors
std::array< uint8_t, 4 > stair_bytes
RoomHolewarpCause holewarp_cause
bool pit_outside_braziers