yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_links.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdlib>
5#include <utility>
6
7#include "absl/strings/str_format.h"
10#include "rom/rom.h"
11#include "util/macro.h"
16#include "zelda3/dungeon/room.h"
21
22namespace yaze::zelda3 {
23
24namespace {
25
26// Sprite 0xD6: Ganon_Phase3_DropTiles ($1D:9055) spawns overlords 0x0C-0x0F.
27constexpr uint8_t kGanonSprite = 0xD6;
28constexpr int kDirectionWest = 2;
29constexpr int kDirectionEast = 3;
30
31bool IsEncodedStreamObject(const RoomObject& object) {
32 return (object.options() & ObjectOption::Torch) == ObjectOption::Nothing &&
33 (object.options() & ObjectOption::Block) == ObjectOption::Nothing;
34}
35
36bool IsHeaderStairObject(int id) {
37 return id == 0x12D || id == 0x12E || id == 0x12F ||
38 (id >= 0x138 && id <= 0x13B) || (id >= 0xF9E && id <= 0xFA1) ||
39 (id >= 0xFA6 && id <= 0xFA9);
40}
41
48
49// Outer-wall door slots (USDASM RoomDraw_DoorPartner*): N/W 0..5, S/E 6..11.
50bool IsOuterDoorPosition(int direction, int position) {
51 if (position >= 12) {
52 return false;
53 }
54 return (direction == 0 || direction == 2) ? position < 6 : position >= 6;
55}
56
57int OppositeDirection(int direction) {
58 return direction ^ 1;
59}
60
61const char* DirectionName(int direction) {
62 switch (direction) {
63 case 0:
64 return "north";
65 case 1:
66 return "south";
67 case 2:
68 return "west";
69 case 3:
70 return "east";
71 }
72 return "?";
73}
74
75std::string RoomHex(int room_id) {
76 return absl::StrFormat("0x%02X", room_id);
77}
78
79// Hole-making overlords (usdasm bank_09 Overlord_ExecuteSingle table).
80const char* HoleOverlordName(int id) {
81 switch (id) {
82 case 0x0A:
83 return "falling square (overlord 0x0A)";
84 case 0x0B:
85 return "falling bridge (overlord 0x0B)";
86 case 0x0C:
87 case 0x0D:
88 case 0x0E:
89 case 0x0F:
90 return "falling tiles (overlord 0x0C-0x0F)";
91 }
92 return nullptr;
93}
94
95// The hole tag that makes the holewarp live, or -1. Tags the project
96// redefines as warp tags do not make holes.
97int LiveHoleTag(const RoomLinkFacts& facts,
98 const std::set<uint8_t>& warp_tag_ids) {
99 if (!facts.has_hole_tag) {
100 return -1;
101 }
102 for (uint8_t tag : {facts.tag1, facts.tag2}) {
103 if (IsHoleRoomTag(tag) && !warp_tag_ids.contains(tag)) {
104 return tag;
105 }
106 }
107 // Facts built by hand (tests) may set has_hole_tag without the tag bytes.
108 return (IsHoleRoomTag(facts.tag1) || IsHoleRoomTag(facts.tag2)) ? -1 : 0;
109}
110
111// Large brazier: its bowl tiles (0x115/0x125) have the pit attribute inside a
112// solid rim, so Link falls in only by landing there from the room above
113// (vanilla 0x31 -> 0x77 -> Hera fairy room 0xA7).
114constexpr int kLargeBrazierObject = 0x11C;
115
117 bool pit = false;
118 bool pit_outside_braziers = false;
119 bool warp = false;
120};
121
122// Draws the layout and room objects without graphics, the way
123// ComputeObjectTileOwners does, and looks up each drawn tile in the
124// blockset's TILEATTR ($7EFE00). Later draws replace earlier ones, as in the
125// game's tilemaps. Undrawn tiles are ignored.
126absl::StatusOr<DrawnTiles> ScanDrawnTiles(
127 Rom* rom, const Room& room, const std::vector<RoomObject>& layout_objects,
128 const CustomObjectRenderSnapshot* custom_objects,
129 util::StopToken stop_token) {
130 DrawnTiles result;
131 std::vector<int> bg1(kRoomTilemapWords, -1);
132 std::vector<int> bg2(kRoomTilemapWords, -1);
133 std::vector<int> bg1_owner(kRoomTilemapWords, -1);
134 std::vector<int> bg2_owner(kRoomTilemapWords, -1);
135 ObjectDrawer drawer(rom, room.id(), nullptr, custom_objects);
136 std::vector<ObjectDrawer::TileTrace> trace;
137 drawer.SetTraceCollector(&trace, /*trace_only=*/true);
138 gfx::BackgroundBuffer buffer1(512, 512);
139 gfx::BackgroundBuffer buffer2(512, 512);
140 gfx::PaletteGroup palette_group;
141 auto draw = [&](RoomObject object,
142 RoomObject::LayerType layer) -> absl::Status {
143 if (stop_token.stop_requested()) {
144 return absl::CancelledError("Room link scan cancelled");
145 }
146 object.SetRom(rom);
147 object.layer_ = layer;
148 trace.clear();
149 RETURN_IF_ERROR(drawer.DrawObject(object, buffer1, buffer2, palette_group));
150 if (stop_token.stop_requested()) {
151 return absl::CancelledError("Room link scan cancelled");
152 }
153 for (const auto& tile : trace) {
154 if (stop_token.stop_requested()) {
155 return absl::CancelledError("Room link scan cancelled");
156 }
157 if (tile.x_tile < 0 || tile.y_tile < 0 ||
158 tile.x_tile >= kRoomTilemapSize || tile.y_tile >= kRoomTilemapSize) {
159 continue;
160 }
161 const size_t position =
162 static_cast<size_t>(tile.y_tile) * kRoomTilemapSize + tile.x_tile;
163 const bool lower = tile.layer == RoomObject::BG2;
164 (lower ? bg2 : bg1)[position] = tile.tile_id & 0x3FF;
165 (lower ? bg2_owner : bg1_owner)[position] = object.id_;
166 }
167 return absl::OkStatus();
168 };
169 // The layout draws first, into the upper tilemap (RoomLayout::Draw).
170 for (const auto& object : layout_objects) {
172 }
173 const auto& objects = room.GetTileObjects();
174 for (int list = 0; list < 3; ++list) {
175 for (const auto& object : objects) {
176 if (stop_token.stop_requested()) {
177 return absl::CancelledError("Room link scan cancelled");
178 }
179 if (!IsEncodedStreamObject(object)) {
180 continue;
181 }
182 const int list_index = std::min<int>(object.GetLayerValue(), 2);
183 if (list_index != list) {
184 continue;
185 }
187 static_cast<uint8_t>(list))));
188 }
189 }
190 const auto table = LoadUnderworldTileAttributeTable(*rom, room.blockset());
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) {
195 if (stop_token.stop_requested()) {
196 return absl::CancelledError("Room link scan cancelled");
197 }
198 const int tile = words[position];
199 if (tile < 0 || tile >= static_cast<int>(kTileAttributeTableSize)) {
200 continue;
201 }
202 if (IsPitTileAttribute(table[tile])) {
203 result.pit = true;
204 result.pit_outside_braziers |= owners[position] != kLargeBrazierObject;
205 }
206 result.warp |= IsWarpTileAttribute(table[tile]);
207 }
208 }
209 return result;
210}
211
212} // namespace
213
215 switch (kind) {
217 return "entrance";
219 return "hole";
221 return "spawn";
223 return "unplaced_entrance";
225 return "door";
227 return "stair";
229 return "holewarp";
231 return "warp_tag";
233 return "header_only";
235 return "project";
237 return "dungeon_map";
239 return "teleport_door";
240 }
241 return "?";
242}
243
245 switch (role) {
247 return "neighbor";
249 return "teleport";
251 return "exit";
253 return "key_stairs";
254 }
255 return "?";
256}
257
258bool IsDungeonRoomId(int room_id) {
259 return room_id >= 0 && room_id < kDungeonRoomCount;
260}
261
262int DungeonNeighborRoom(int room_id, int direction) {
263 if (!IsDungeonRoomId(room_id)) {
264 return -1;
265 }
266 const int low = room_id & 0xFF;
267 const int row = low >> 4;
268 const int col = low & 0x0F;
269 int target = -1;
270 switch (direction) {
271 case 0: // North
272 target = row > 0 ? room_id - 16 : -1;
273 break;
274 case 1: // South
275 target = row < 15 ? room_id + 16 : -1;
276 break;
277 case 2: // West
278 target = col > 0 ? room_id - 1 : -1;
279 break;
280 case 3: // East
281 target = col < 15 ? room_id + 1 : -1;
282 break;
283 }
284 return IsDungeonRoomId(target) ? target : -1;
285}
286
287int CheckedHeaderDestinationRoom(int source_room_id, uint8_t header_byte) {
288 const int room = ResolveHeaderDestinationRoom(source_room_id, header_byte);
289 return IsDungeonRoomId(room) ? room : -1;
290}
291
293 const auto layout = ResolveEntranceLayout(rom.vector());
294 return layout.ok() && layout->kind == EntranceLayoutKind::kZscreamExpanded;
295}
296
297std::optional<DungeonEntranceTarget> ReadDungeonEntranceTarget(
298 const Rom& rom, int entrance_id) {
299 if (entrance_id < 0 || entrance_id >= kDungeonEntranceCount) {
300 return std::nullopt;
301 }
302 const auto& data = rom.vector();
303 const auto layout = ResolveEntranceLayout(data);
304 if (!layout.ok()) {
305 return std::nullopt;
306 }
307 const int room_pc = layout->address(EntranceField::kRoom, entrance_id);
308 const int dungeon_pc = layout->address(EntranceField::kDungeon, entrance_id);
309 if (room_pc + 1 >= static_cast<int>(data.size()) ||
310 dungeon_pc >= static_cast<int>(data.size())) {
311 return std::nullopt;
312 }
314 target.room_id = data[room_pc] | (data[room_pc + 1] << 8);
315 target.dungeon_id = data[dungeon_pc];
316 target.expanded = layout->kind == EntranceLayoutKind::kZscreamExpanded;
317 return target;
318}
319
320// Pits, pit edges, layer-2 pit masks, and bombable floor (bombing it opens a
321// hole that uses the holewarp, e.g. Oracle D5 0xAD -> boss room 0xAC).
322bool IsPitObjectId(int id) {
323 return (id >= 0x023 && id <= 0x02E) || id == 0x06A || id == 0x06B ||
324 id == 0x0A4 || id == 0x0C2 || id == 0x0C3 || id == 0xFC7 ||
325 id == 0xFE6;
326}
327
328// 0xFCA warp tile, 0xFCF warp tile drawn disabled. Vanilla puts 0xFCF in the
329// rooms above the fairy rooms that Module07_07_0F_FallingFadeIn ($02:8EBB)
330// special-cases: 0xA9 -> 0x89 (Eastern) and 0xBE -> 0x4F (Ice Palace).
331bool IsWarpTileObjectId(int id) {
332 return id == 0xFCA || id == 0xFCF;
333}
334
335bool IsHoleRoomTag(uint8_t tag) {
336 switch (static_cast<TagKey>(tag)) {
337 case TagKey::Holes_0:
339 case TagKey::Holes_1:
340 case TagKey::Holes_2:
341 case TagKey::Holes_3:
342 case TagKey::Holes_4:
343 case TagKey::Holes_5:
344 case TagKey::Holes_6:
345 case TagKey::Holes_7:
346 case TagKey::Holes_8:
348 return true;
349 default:
350 return false;
351 }
352}
353
354// TileBehavior_Pit: 0x20 and 0xB0-0xBD (pits under Somaria tracks).
355bool IsPitTileAttribute(uint8_t attribute) {
356 return attribute == 0x20 || (attribute >= 0xB0 && attribute <= 0xBD);
357}
358
359bool IsWarpTileAttribute(uint8_t attribute) {
360 return attribute == 0x4B;
361}
362
363absl::StatusOr<RoomLinkFacts> CollectRoomLinkFacts(
364 Rom* rom, const Room& room, const PitDamageTable* pit_table,
365 const CustomObjectRenderSnapshot* custom_objects,
366 util::StopToken stop_token) {
367 if (stop_token.stop_requested()) {
368 return absl::CancelledError("Room link scan cancelled");
369 }
370 RoomLinkFacts facts;
371 facts.room_id = room.id();
372 facts.blockset = room.blockset();
373 facts.tag1 = static_cast<uint8_t>(room.tag1());
374 facts.tag2 = static_cast<uint8_t>(room.tag2());
375 facts.holewarp_byte = room.holewarp();
376 for (int slot = 0; slot < 4; ++slot) {
377 facts.stair_bytes[slot] = room.staircase_room(slot);
378 }
379 facts.in_pit_damage_table =
380 pit_table != nullptr &&
381 pit_table->Contains(static_cast<uint16_t>(room.id()));
382 facts.has_hole_tag = IsHoleRoomTag(facts.tag1) || IsHoleRoomTag(facts.tag2);
383
384 RoomLayout layout(rom);
385 RETURN_IF_ERROR(layout.LoadLayout(room.layout_id(), stop_token));
386 const auto& layout_objects = layout.GetObjects();
387
388 bool has_stairs = false;
389 for (const auto& object : layout_objects) {
390 facts.has_pits |= IsPitObjectId(object.id_);
391 facts.has_warp_tiles |= IsWarpTileObjectId(object.id_);
392 }
393 for (const auto& object : room.GetTileObjects()) {
394 if (!IsEncodedStreamObject(object)) {
395 continue;
396 }
397 facts.has_pits |= IsPitObjectId(object.id_);
398 facts.has_warp_tiles |= IsWarpTileObjectId(object.id_);
399 has_stairs |= IsHeaderStairObject(object.id_);
400 }
401 if (room.has_custom_collision()) {
402 for (uint8_t tile : room.custom_collision().tiles) {
403 facts.has_pits |= IsPitTileAttribute(tile);
404 facts.has_warp_tiles |= IsWarpTileAttribute(tile);
405 }
406 }
407 if (rom != nullptr && rom->is_loaded()) {
409 const DrawnTiles drawn,
410 ScanDrawnTiles(rom, room, layout_objects, custom_objects, stop_token));
411 if (drawn.pit && !facts.has_pits) {
412 facts.pits_only_in_braziers = !drawn.pit_outside_braziers;
413 }
414 facts.has_pits |= drawn.pit;
415 facts.has_warp_tiles |= drawn.warp;
416 }
417
418 for (const auto& sprite : room.GetSprites()) {
419 if (sprite.IsOverlord()) {
420 if (const char* name = HoleOverlordName(sprite.id())) {
421 facts.hole_sprite = name;
422 break;
423 }
424 } else if (sprite.id() == kGanonSprite) {
425 facts.hole_sprite = "Ganon's falling floor (sprite 0xD6)";
426 break;
427 }
428 }
429
430 // Stair objects -> header slots, same replay as
431 // `dungeon-describe-room --include-staircase-resolution`.
432 if (has_stairs && rom != nullptr) {
433 auto collision_input = MakeRoomCollisionInput(room);
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 =
439 ResolveVanillaStaircaseSlots(*rom, collision_input);
440 int placement_index = 0;
441 for (const auto& resolution : resolutions) {
442 if (resolution.object_index < prefix_size) {
443 continue;
444 }
445 if (resolution.slot.has_value() && *resolution.slot >= 0 &&
446 *resolution.slot < 4) {
447 facts.stair_slot_used[*resolution.slot] = true;
448 facts.stair_slot_estimated[*resolution.slot] = false;
449 } else if (placement_index < 4 &&
450 !facts.stair_slot_used[placement_index]) {
451 // Replay could not place it; fall back to placement order.
452 facts.stair_slot_used[placement_index] = true;
453 facts.stair_slot_estimated[placement_index] = true;
454 }
455 ++placement_index;
456 }
457 }
458
459 for (const auto& door : room.GetDoors()) {
461 fact.direction = static_cast<int>(door.direction);
462 const auto [tile_x, tile_y] = door.GetTileCoords();
463 fact.tile_x = tile_x;
464 fact.tile_y = tile_y;
465 fact.along = (fact.direction <= 1) ? tile_x : tile_y;
466 fact.outer = IsOuterDoorPosition(fact.direction, door.position);
467 fact.type = static_cast<uint8_t>(door.type);
468 if (IsExitDoorType(door.type)) {
470 } else if (!IsRoomConnectionDoorType(door.type)) {
471 continue; // Layer and dungeon swap markers.
472 } else if (IsKeyStairDoor(door.type)) {
474 } else if (fact.type == kTeleportDoorType &&
475 (fact.direction == kDirectionWest ||
476 fact.direction == kDirectionEast)) {
478 }
479 facts.doors.push_back(fact);
480 }
481 if (stop_token.stop_requested()) {
482 return absl::CancelledError("Room link scan cancelled");
483 }
484 return facts;
485}
486
487std::vector<RoomLink> CollectRoomLinks(int room_id,
488 const RoomLinkFactsLookup& lookup,
489 const std::set<uint8_t>& warp_tag_ids) {
490 std::vector<RoomLink> links;
491 if (!IsDungeonRoomId(room_id) || !lookup) {
492 return links;
493 }
494 const RoomLinkFacts* self = lookup(room_id);
495 if (self == nullptr) {
496 return links;
497 }
498 const RoomLinkFacts& f = *self;
499 auto facts_of = [&](int room) -> const RoomLinkFacts* {
500 return IsDungeonRoomId(room) ? lookup(room) : nullptr;
501 };
502 auto make = [&](RoomReferenceKind kind, int raw_to) {
503 RoomLink link;
504 link.from = room_id;
505 link.raw_to = raw_to;
506 link.to = IsDungeonRoomId(raw_to) ? raw_to : -1;
507 link.kind = kind;
508 return link;
509 };
510 auto same_blockset = [&](int room) {
511 const RoomLinkFacts* other = facts_of(room);
512 return other != nullptr && other->blockset == f.blockset;
513 };
514 // Header slots that stair objects in `room` use.
515 auto stair_targets = [&](int room) {
516 std::vector<int> targets;
517 const RoomLinkFacts* other = facts_of(room);
518 if (other == nullptr) {
519 return targets;
520 }
521 for (int slot = 0; slot < 4; ++slot) {
522 if (other->stair_slot_used[slot]) {
523 targets.push_back(
524 ResolveHeaderDestinationRoom(room, other->stair_bytes[slot]));
525 }
526 }
527 return targets;
528 };
529
530 // ---- Doors ---------------------------------------------------------------
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];
534 if (!door.outer || (door.role != RoomDoorRole::kNeighbor &&
535 door.role != RoomDoorRole::kTeleport)) {
536 continue;
537 }
538 if (door.role == RoomDoorRole::kTeleport) {
539 const int slot = door.direction == kDirectionEast ? 3 : 2;
540 RoomLink link =
542 ResolveHeaderDestinationRoom(room_id, f.stair_bytes[slot]));
543 link.slot = slot;
544 link.direction = door.direction;
545 link.door = static_cast<int>(index);
546 link.strong = true;
547 link.owner_ok = same_blockset(link.to);
548 link.detail = absl::StrFormat(
549 "teleport door from %s (%s wall, stair "
550 "slot %d)",
551 RoomHex(room_id), DirectionName(door.direction), slot + 1);
552 teleport_slot[slot] = true;
553 links.push_back(std::move(link));
554 continue;
555 }
556 const int neighbor = DungeonNeighborRoom(room_id, door.direction);
557 if (neighbor < 0) {
558 continue;
559 }
560 bool mutual = false;
561 if (const RoomLinkFacts* other = facts_of(neighbor)) {
562 for (const auto& back : other->doors) {
563 if (back.outer &&
564 (back.role == RoomDoorRole::kNeighbor ||
565 back.role == RoomDoorRole::kTeleport) &&
566 back.direction == OppositeDirection(door.direction) &&
567 std::abs(back.along - door.along) <= 3) {
568 mutual = true;
569 break;
570 }
571 }
572 }
573 RoomLink link = make(RoomReferenceKind::kDoor, neighbor);
574 link.direction = door.direction;
575 link.door = static_cast<int>(index);
576 link.strong = mutual;
577 link.owner_ok = mutual;
578 link.detail =
579 mutual
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));
585 }
586
587 // ---- Header stair slots -------------------------------------------------
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) {
591 RoomLink link =
593 ResolveHeaderDestinationRoom(room_id, f.stair_bytes[slot]));
594 link.slot = slot;
595 if (f.stair_slot_used[slot]) {
596 link.strong = true;
597 const auto back = stair_targets(link.to);
598 link.owner_ok =
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),
603 f.stair_slot_estimated[slot] ? ", slot by placement order" : "");
604 } else if (warp_tag) {
606 link.strong = true;
607 link.owner_ok = same_blockset(link.to);
608 link.detail = absl::StrFormat("warp tag quadrant %d in %s", slot + 1,
609 RoomHex(room_id));
610 } else if (teleport_slot[slot]) {
611 continue; // The teleport door link already covers this slot.
612 } else if (f.stair_bytes[slot] != 0) {
614 link.detail = absl::StrFormat(
615 "header stair slot %d of %s (no stair object uses it)", slot + 1,
616 RoomHex(room_id));
617 } else {
618 continue; // Byte 0 with no user: not a reference.
619 }
620 links.push_back(std::move(link));
621 }
622
623 // ---- Holewarp -----------------------------------------------------------
624 if (warp_tag) {
625 // The project tag replaces the live holewarp with its quadrant stair
626 // slot, regardless of which floor source triggers the warp. Preserve an
627 // unused nonzero header byte for inspection without traversing it.
628 if (f.holewarp_byte != 0) {
629 RoomLink header =
632 header.detail = absl::StrFormat(
633 "header holewarp of %s (overridden by warp tag quadrant slots)",
634 RoomHex(room_id));
635 links.push_back(std::move(header));
636 }
637 return links;
638 }
641 hole.owner_ok = same_blockset(hole.to);
642 const int hole_tag = LiveHoleTag(f, warp_tag_ids);
643 std::string hole_source;
644 if (f.has_pits) {
646 hole_source = f.pits_only_in_braziers
647 ? "large-brazier pits (entered by falling from the "
648 "room above)"
649 : "pits";
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";
656 } else if (!f.hole_sprite.empty()) {
658 hole_source = f.hole_sprite;
659 }
660 if (f.has_warp_tiles) {
662 hole.strong = true;
663 hole.detail = absl::StrFormat("warp tiles in %s", RoomHex(room_id));
664 } else if (!hole_source.empty() && !f.in_pit_damage_table) {
665 hole.strong = true;
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);
671 } else if (f.holewarp_byte != 0) {
673 hole.detail = absl::StrFormat(
674 "header holewarp of %s (no pits, holes or warp tiles there)",
675 RoomHex(room_id));
676 } else {
677 return links; // Byte 0 with no user: not a reference.
678 }
679 if (hole.strong && !hole.owner_ok) {
680 hole.detail += " (other blockset)";
681 }
682 links.push_back(std::move(hole));
683 return links;
684}
685
687 : rom_(rom), facts_(kDungeonRoomCount) {
688 if (rom_ != nullptr && rom_->is_loaded()) {
689 auto table = std::make_unique<PitDamageTable>();
690 if (PitDamageTable::LoadFromRom(rom_, table.get()).ok()) {
691 pit_table_ = std::move(table);
692 }
693 }
694}
695
697
699 if (!status_.ok() || !IsDungeonRoomId(room_id) || rom_ == nullptr ||
700 !rom_->is_loaded()) {
701 return nullptr;
702 }
703 auto& slot = facts_[room_id];
704 if (!slot.has_value()) {
705 Room room = LoadRoomFromRom(rom_, room_id);
706 room.LoadSprites();
707 auto collected = CollectRoomLinkFacts(rom_, room, pit_table_.get());
708 if (!collected.ok()) {
709 status_ = collected.status();
710 return nullptr;
711 }
712 slot = std::move(*collected);
713 }
714 return &*slot;
715}
716
718 return [this](int room_id) {
719 return Get(room_id);
720 };
721}
722
723} // namespace yaze::zelda3
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:49
const auto & vector() const
Definition rom.h:205
bool is_loaded() const
Definition rom.h:187
bool stop_requested() const noexcept
Definition stop_token.h:21
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
Definition room_layout.h:34
absl::Status LoadLayout(int layout_id, util::StopToken stop_token={})
std::unique_ptr< PitDamageTable > pit_table_
Definition room_links.h:217
const RoomLinkFacts * Get(int room_id)
RoomLinkFactsLookup AsLookup()
std::vector< std::optional< RoomLinkFacts > > facts_
Definition room_links.h:218
uint8_t holewarp() const
Definition room.h:1028
const CustomCollisionMap & custom_collision() const
Definition room.h:559
uint8_t blockset() const
Definition room.h:1015
uint8_t staircase_room(int index) const
Definition room.h:999
const std::vector< Door > & GetDoors() const
Definition room.h:413
TagKey tag2() const
Definition room.h:988
TagKey tag1() const
Definition room.h:987
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:316
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:445
void LoadSprites()
Definition room.cc:2886
uint8_t layout_id() const
Definition room.h:1026
bool has_custom_collision() const
Definition room.h:563
int id() const
Definition room.h:1012
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
Zelda 3 specific classes and functions.
bool IsWarpTileObjectId(int id)
const char * RoomReferenceKindName(RoomReferenceKind kind)
DoorType
Door types from ALTTP.
Definition door_types.h:33
@ 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.
Definition door_types.h:333
RoomObject::LayerType MapRoomObjectListIndexToDrawLayer(uint8_t list_index)
@ Open_Chest_for_Holes_8
Definition room.h:212
@ Open_Chest_Activate_Holes_0
Definition room.h:187
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
Definition room_links.h:51
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)
Definition room.cc:655
constexpr int kDungeonEntranceCount
Definition room_links.h:53
constexpr bool IsExitDoorType(DoorType type)
Return true for terminal exits that do not pair with another room.
Definition door_types.h:315
bool IsPitTileAttribute(uint8_t attribute)
std::function< const RoomLinkFacts *(int room_id)> RoomLinkFactsLookup
Definition room_links.h:147
RoomCollisionInput MakeRoomCollisionInput(const Room &room, const RoomTilemaps &tilemaps)
constexpr uint8_t kTeleportDoorType
Definition room_links.h:55
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)
Definition snes.cc:22
Represents a group of palettes.
std::array< uint8_t, 64 *64 > tiles
std::array< bool, 4 > stair_slot_used
Definition room_links.h:108
std::array< bool, 4 > stair_slot_estimated
Definition room_links.h:109
std::vector< Door > doors
Definition room_links.h:119
std::array< uint8_t, 4 > stair_bytes
Definition room_links.h:106