yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
room_collision.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cstdint>
6#include <optional>
7#include <vector>
8
9#include "rom/rom.h"
10#include "rom/snes.h"
12#include "zelda3/dungeon/room.h"
14
15namespace yaze::zelda3 {
16namespace {
17
18// ROM tables (PC offsets, vanilla addresses from usdasm).
19constexpr int kUnderworldTileTypes = 0x071659; // $0E:9659, 0x180 bytes
20constexpr int kLoadCustomTileTypes = 0x07142A; // $0E:942A
21constexpr int kCustomTileTypesOffset = 0x071000; // $0E:9000, 21 words
22constexpr int kCustomUnderworldTileTypes = 0x07102A; // $0E:902A
23constexpr int kDungeonMask = 0x0018C0; // $00:98C0, 16 words
24constexpr int kRoomFlagMask = 0x001900; // $00:9900
25constexpr int kDoorPositionsNorthWall = 0x00197E; // $00:997E
26constexpr int kDoorPositionsNorthMiddle = 0x00198A; // $00:998A
27constexpr int kDoorPositionsSouthMiddle = 0x001996; // $00:9996
28constexpr int kDoorPositionsWestWall = 0x0019AE; // $00:99AE
29constexpr int kDoorPositionsWestMiddle = 0x0019BA; // $00:99BA
30constexpr int kDoorPositionsEastMiddle = 0x0019C6; // $00:99C6
31constexpr int kExplodingWallPositions = 0x0019DE; // $00:99DE
32constexpr int kDoorwayReplacementDoorGfx = 0x001A02; // $00:9A02
33constexpr int kDoorwayTileProperties = 0x001A52; // $00:9A52
34
35uint8_t RomByte(const Rom& rom, int pc) {
36 if (pc < 0 || static_cast<size_t>(pc) >= rom.size()) {
37 return 0;
38 }
39 return rom.data()[pc];
40}
41
42uint16_t RomWord(const Rom& rom, int pc) {
43 return static_cast<uint16_t>(RomByte(rom, pc) | (RomByte(rom, pc + 1) << 8));
44}
45
46// The low WRAM ($0000-$1FFF) the draw routines and attribute passes share.
47// Lists overlap in the real game ($06B0 stairs run into $06B8, chests at
48// $06E0 into $06EC), so they live in one byte array like the game's.
49class LowWram {
50 public:
51 uint16_t Word(int address) const {
52 return static_cast<uint16_t>(bytes_[address & 0x1FFF] |
53 (bytes_[(address + 1) & 0x1FFF] << 8));
54 }
55 void SetWord(int address, uint16_t value) {
56 bytes_[address & 0x1FFF] = static_cast<uint8_t>(value & 0xFF);
57 bytes_[(address + 1) & 0x1FFF] = static_cast<uint8_t>(value >> 8);
58 }
59
60 private:
61 std::array<uint8_t, 0x2000> bytes_{};
62};
63
64// Low WRAM addresses (usdasm wram.asm / bank_01 comments).
65constexpr int kDoorListIndex = 0x0460;
66constexpr int kDoorTypes = 0x1980; // (slot << 8) | type
67constexpr int kDoorPositions = 0x19A0; // tilemap offset (+$2000 = BG2)
68constexpr int kDoorDirections = 0x19C0;
69constexpr int kExitDoorCount = 0x19E0;
70constexpr int kExitDoorPositions = 0x19E2; // 4 words
71constexpr int kDoorOpenMask = 0x068C;
72constexpr int kRoomFlags = 0x0402;
73constexpr int kShutterController = 0x0468;
74constexpr int kLayerToggleCount = 0x044E; // list at $06C0
75constexpr int kDungeonToggleCount = 0x0450; // list at $06D0
76constexpr int kDoorSwitch = 0x04B0;
77constexpr int kStarCount = 0x0432; // list at $06A0
78constexpr int kManipulableCount = 0x042C; // $0500 type, $0540 position
79constexpr int kTorchEnd = 0x042E;
80constexpr int kChestCount = 0x0496; // list at $06E0
81constexpr int kBigKeyLockEnd = 0x0498;
82
83struct RomTables {
84 explicit RomTables(const Rom& rom) : rom(rom) {}
85 uint16_t DungeonMask(int x) const {
86 return RomWord(rom, kDungeonMask + (x & 0xFFFF));
87 }
88 const Rom& rom;
89};
90
91// ---------------------------------------------------------------------------
92// Replays the list bookkeeping of the object and door draw routines.
93
95 public:
96 RoomLoadReplay(const Rom& rom, const RoomCollisionInput& input,
97 const UnderworldRoomLoadState& state)
98 : rom_(rom), tables_(rom), input_(input), state_(state) {
99 tag1_ = input.tag1;
100 tag2_ = input.tag2;
101 // Underworld_LoadHeader (#_01B6D1).
102 const uint16_t save = state.room_save_flags;
103 w_.SetWord(kDoorOpenMask,
104 state.door_open_mask.value_or(
105 static_cast<uint16_t>((save & 0xF000) | 0x0F00)));
106 w_.SetWord(kRoomFlags, static_cast<uint16_t>((save & 0x0FF0) << 4));
107 w_.SetWord(kShutterController, state.shutter_controller);
108 }
109
110 LowWram& wram() { return w_; }
111 uint8_t tag1() const { return tag1_; }
112 uint8_t tag2() const { return tag2_; }
113
114 // Underworld_LoadRoom (#_01873A): room object lists 0, 1, 2 (doors follow
115 // the list that holds the $FFF0 marker, the last one in vanilla), then
116 // pushable blocks, then torches.
117 void Run() {
118 for (int list = 0; list < 3; ++list) {
119 for (const auto& object : input_.objects) {
120 if (!UsesRoomObjectStream(object) || object.GetLayerValue() != list) {
121 continue;
122 }
123 RecordObject(object, /*lower_layer=*/list == 1);
124 }
125 }
126 for (const auto& door : input_.doors) {
127 RecordDoor(door);
128 }
129 for (const auto& object : input_.objects) {
130 if ((object.options() & ObjectOption::Block) != ObjectOption::Nothing) {
131 RecordPushableBlock(object);
132 }
133 }
134 // $042E = $042C before the torches draw (#_01889C).
135 w_.SetWord(kTorchEnd, w_.Word(kManipulableCount));
136 for (const auto& object : input_.objects) {
137 if ((object.options() & ObjectOption::Torch) != ObjectOption::Nothing) {
138 RecordTorch(object);
139 }
140 }
141 }
142
143 private:
144 // ---- objects -----------------------------------------------------------
145
146 static uint16_t TilemapOffset(const RoomObject& object) {
147 return static_cast<uint16_t>((object.y() & 0x3F) * 0x80 +
148 (object.x() & 0x3F) * 2);
149 }
150
151 // Sets each counter in `counters` to `value` (the stairs routines keep
152 // every later stairs list's end index in step).
153 void SetCounters(std::initializer_list<int> counters, uint16_t value) {
154 for (int counter : counters) {
155 w_.SetWord(counter, value);
156 }
157 }
158
159 // Appends `value` to a list at `list` whose end index lives at `counter`.
160 uint16_t Append(int counter, int list, uint16_t value) {
161 const uint16_t x = w_.Word(counter);
162 w_.SetWord(list + x, value);
163 return static_cast<uint16_t>(x + 2);
164 }
165
166 // DrawBigGraySegment / RoomDraw_SinglePot / RoomDraw_HammerPegSingle
167 // (#_01B33A, #_01B395, #_01B493): $0500 = type, $0540 = position.
168 void AddManipulable(uint16_t type, uint16_t y, bool lower_layer) {
169 const uint16_t x = w_.Word(kManipulableCount);
170 w_.SetWord(0x0500 + x, type);
171 w_.SetWord(kManipulableCount, static_cast<uint16_t>(x + 2));
172 w_.SetWord(0x0520 + x, 0); // $BA, the object stream offset (unused)
173 w_.SetWord(0x0540 + x, lower_layer ? (y | 0x2000) : y);
174 }
175
176 bool WaterFlagSet() const {
177 // LDA $0403 : AND DoorFlagMasks+1 (8-bit) = $0402 & 0x0800.
178 return (w_.Word(kRoomFlags) & 0x0800) != 0;
179 }
180 bool DamGateUnopened() const {
181 // $AF == 0x1B and the room's save word bit 0x0100 clear.
182 return tag2_ == 0x1B && (state_.room_save_flags & 0x0100) == 0;
183 }
184 // RoomDraw_WaterOverlayA/B: turns the "merged" water stairs into the
185 // regular ones.
187 w_.SetWord(0x0440, w_.Word(0x0442));
188 w_.SetWord(0x0448, w_.Word(0x0444));
189 w_.SetWord(0x0444, 0);
190 w_.SetWord(0x0442, 0);
191 w_.SetWord(0x049E, w_.Word(0x04AE));
192 w_.SetWord(0x04AE, 0);
193 }
194
195 void RecordObject(const RoomObject& object, bool lower_layer) {
196 const uint16_t y = TilemapOffset(object);
197 const uint16_t layer_y = lower_layer ? (y | 0x2000) : y;
198 const int count = (object.size() & 0x0F) + 1; // RoomDraw_GetSize_1to16
199 switch (object.id_) {
200 case 0x035: { // RoomDraw_DoorSwitcherer (#_01913F)
201 uint16_t value = layer_y;
202 if ((w_.Word(kRoomFlags) & 0x1000) == 0) {
203 value |= 0x8000; // not drawn: 5 columns instead of 6
204 }
205 w_.SetWord(kDoorSwitch, value);
206 break;
207 }
208 case 0x095: // RoomDraw_DownwardsPots2x2_1to16
209 for (int i = 0; i < count; ++i) {
210 AddManipulable(0x1111, static_cast<uint16_t>(y + i * 0x100),
211 lower_layer);
212 }
213 break;
214 case 0x0BC: // RoomDraw_RightwardsPots2x2_1to16
215 for (int i = 0; i < count; ++i) {
216 AddManipulable(0x1111, static_cast<uint16_t>(y + i * 4), lower_layer);
217 }
218 break;
219 case 0x096: // RoomDraw_DownwardsHammerPegs2x2_1to16
220 for (int i = 0; i < count; ++i) {
221 AddManipulable(0x4040, static_cast<uint16_t>(y + i * 0x100),
222 lower_layer);
223 }
224 break;
225 case 0x0BD: // RoomDraw_RightwardsHammerPegs2x2_1to16
226 for (int i = 0; i < count; ++i) {
227 AddManipulable(0x4040, static_cast<uint16_t>(y + i * 4), lower_layer);
228 }
229 break;
230 case 0xF96: // RoomDraw_HammerPegSingle (0x216)
231 AddManipulable(0x4040, y, lower_layer);
232 break;
233 case 0xFAF: // RoomDraw_SinglePot (0x22F)
234 AddManipulable(0x1111, y, lower_layer);
235 break;
236 case 0xFAB: // RoomDraw_WeirdGloveRequiredPot (0x22B)
237 AddManipulable(0x1010, y, lower_layer);
238 break;
239 case 0xFB0: // RoomDraw_WeirdUglyPot (0x230)
240 AddManipulable(0x1212, y, lower_layer);
241 break;
242 case 0xFAC: // RoomDraw_BigGrayRock (0x22C): four 2x2 segments
243 AddManipulable(0x2020, y, lower_layer);
244 AddManipulable(0x2121, static_cast<uint16_t>(y + 4), lower_layer);
245 AddManipulable(0x2222, static_cast<uint16_t>(y + 0x100), lower_layer);
246 AddManipulable(0x2323, static_cast<uint16_t>(y + 0x104), lower_layer);
247 break;
248 case 0xFC7: // RoomDraw_BombableFloor (0x247)
249 if (input_.room_id == 0x65 && (w_.Word(kRoomFlags) & 0x1000) != 0) {
250 break; // open Thieves' Town attic floor: a plain hole
251 }
252 AddManipulable(0x3030, y, lower_layer);
253 AddManipulable(0x3131, static_cast<uint16_t>(y + 4), lower_layer);
254 AddManipulable(0x3232, static_cast<uint16_t>(y + 0x100), lower_layer);
255 AddManipulable(0x3333, static_cast<uint16_t>(y + 0x104), lower_layer);
256 break;
257 case 0x0D8: // RoomDraw_WaterOverlayA8x8_1to16 (#_019501)
258 if (WaterFlagSet()) {
259 tag2_ = 0;
260 MoveWaterStairLists();
261 }
262 break;
263 case 0x0DA: // RoomDraw_WaterOverlayB8x8_1to16 (#_0195EF)
264 if (WaterFlagSet()) {
265 tag2_ = 0;
266 } else {
267 MoveWaterStairLists();
268 }
269 break;
270 case 0x11F: // RoomDraw_EnabledStarSwitch (#_019A6F)
271 w_.SetWord(kStarCount, Append(kStarCount, 0x06A0, layer_y >> 1));
272 break;
273 case 0x12D: // RoomDraw_InterRoomFatStairsUp (#_01A41B)
274 SetCounters({0x0438, 0x047E, 0x0482, 0x04A2, 0x04A4, 0x043A, 0x0480,
275 0x0484, 0x04A6, 0x04A8},
276 Append(0x0438, 0x06B0, layer_y >> 1));
277 break;
278 case 0x12E: // RoomDraw_InterRoomFatStairsDown_A
279 case 0x12F: // RoomDraw_InterRoomFatStairsDown_B
280 SetCounters({0x043A, 0x0480, 0x0484, 0x04A6, 0x04A8},
281 Append(0x043A, 0x06B0, layer_y >> 1));
282 break;
283 case 0x138: { // RoomDraw_SpiralStairsGoingUpUpper (#_01A4B4)
284 const uint16_t v = static_cast<uint16_t>(
285 (static_cast<uint16_t>(y - 0x80) | (lower_layer ? 0x2000 : 0)) >>
286 1);
287 SetCounters({0x047E, 0x0482, 0x04A2, 0x04A4, 0x043A, 0x0480, 0x0484,
288 0x04A6, 0x04A8},
289 Append(0x047E, 0x06B0, v));
290 break;
291 }
292 case 0x13A: { // RoomDraw_SpiralStairsGoingUpLower
293 const uint16_t v = static_cast<uint16_t>(
294 (static_cast<uint16_t>(y - 0x80) | (lower_layer ? 0x2000 : 0)) >>
295 1);
296 SetCounters(
297 {0x0482, 0x04A2, 0x04A4, 0x043A, 0x0480, 0x0484, 0x04A6, 0x04A8},
298 Append(0x0482, 0x06B0, v));
299 break;
300 }
301 case 0x139: { // RoomDraw_SpiralStairsGoingDownUpper
302 const uint16_t v = static_cast<uint16_t>(
303 (static_cast<uint16_t>(y - 0x80) | (lower_layer ? 0x2000 : 0)) >>
304 1);
305 SetCounters({0x0480, 0x0484, 0x04A6, 0x04A8},
306 Append(0x0480, 0x06B0, v));
307 break;
308 }
309 case 0x13B: { // RoomDraw_SpiralStairsGoingDownLower
310 const uint16_t v = static_cast<uint16_t>(
311 (static_cast<uint16_t>(y - 0x80) | (lower_layer ? 0x2000 : 0)) >>
312 1);
313 SetCounters({0x0484, 0x04A6, 0x04A8}, Append(0x0484, 0x06B0, v));
314 break;
315 }
316 // Straight inter-room stairs. The "Upper" variants ignore the layer.
317 case 0xF9E: // RoomDraw_StraightInterroomStairsGoingUpNorthUpper
318 SetCounters({0x04A2, 0x04A4, 0x043A, 0x0480, 0x0484, 0x04A6, 0x04A8},
319 Append(0x04A2, 0x06B0, y >> 1));
320 break;
321 case 0xFA6: // RoomDraw_StraightInterroomStairsGoingUpNorthLower
322 SetCounters({0x04A2, 0x04A4, 0x043A, 0x0480, 0x0484, 0x04A6, 0x04A8},
323 Append(0x04A2, 0x06B0, layer_y >> 1));
324 break;
325 case 0xF9F: // RoomDraw_StraightInterroomStairsGoingDownNorthUpper
326 SetCounters({0x04A6, 0x04A8}, Append(0x04A6, 0x06B0, y >> 1));
327 break;
328 case 0xFA7: // RoomDraw_StraightInterroomStairsGoingDownNorthLower
329 SetCounters({0x04A6, 0x04A8}, Append(0x04A6, 0x06B0, layer_y >> 1));
330 break;
331 case 0xFA0: // RoomDraw_StraightInterroomStairsGoingUpSouthUpper
332 SetCounters({0x04A4, 0x043A, 0x0480, 0x0484, 0x04A6, 0x04A8},
333 Append(0x04A4, 0x06B0, y >> 1));
334 break;
335 case 0xFA8: // RoomDraw_StraightInterroomStairsGoingUpSouthLower
336 SetCounters({0x04A4, 0x043A, 0x0480, 0x0484, 0x04A6, 0x04A8},
337 Append(0x04A4, 0x06B0, layer_y >> 1));
338 break;
339 case 0xFA1: // RoomDraw_StraightInterroomStairsGoingDownSouthUpper
340 SetCounters({0x04A8}, Append(0x04A8, 0x06B0, y >> 1));
341 break;
342 case 0xFA9: // RoomDraw_StraightInterroomStairsGoingDownSouthLower
343 SetCounters({0x04A8}, Append(0x04A8, 0x06B0, layer_y >> 1));
344 break;
345 // Auto stairs record Y >> 1 without the layer bit.
346 case 0x130: { // RoomDraw_AutoStairs_North_MultiLayer_A (#_01A25D)
347 const uint16_t end = Append(0x043C, 0x06B8, y >> 1);
348 SetCounters({0x043C, 0x0446, 0x0448}, end);
349 break;
350 }
351 case 0x131: { // RoomDraw_AutoStairs_North_MultiLayer_B
352 const uint16_t end = Append(0x043E, 0x06B8, y >> 1);
353 SetCounters({0x043E, 0x0446, 0x0448}, end);
354 break;
355 }
356 case 0x133: // RoomDraw_AutoStairs_North_MergedLayer_B (#_01A2DF)
357 if (!DamGateUnopened()) {
358 SetCounters({0x0442, 0x0444}, Append(0x0442, 0x06B8, y >> 1));
359 break;
360 }
361 [[fallthrough]]; // AutoStairsNorthMergedStart
362 case 0x132: // RoomDraw_AutoStairs_North_MergedLayer_A (#_01A2C7)
363 SetCounters({0x0440, 0x0446, 0x0448}, Append(0x0440, 0x06B8, y >> 1));
364 break;
365 case 0x135: // RoomDraw_WaterHopStairs_A (#_019B1E)
366 if (!DamGateUnopened()) {
367 w_.SetWord(0x0444, Append(0x0444, 0x06B8, y >> 1));
368 break;
369 }
370 [[fallthrough]];
371 case 0x136: // RoomDraw_WaterHopStairs_B (#_01A3AE)
372 SetCounters({0x0446, 0x0448}, Append(0x0446, 0x06B8, y >> 1));
373 break;
374 case 0xF9B: // RoomDraw_AutoStairs_South_MultiLayer_A (#_01A30C)
375 // Vanilla stores this one in the north list ($06B8) but counts it in
376 // $049A, which the attribute pass reads against $06EC.
377 w_.SetWord(0x049A, Append(0x049A, 0x06B8, y >> 1));
378 break;
379 case 0xF9C: // RoomDraw_AutoStairs_South_MultiLayer_B
380 w_.SetWord(0x049C, Append(0x049C, 0x06EC, y >> 1));
381 break;
382 case 0xFB3: // RoomDraw_AutoStairs_South_MergedLayer (#_01A380)
383 if (!DamGateUnopened()) {
384 w_.SetWord(0x04AE, Append(0x04AE, 0x06EC, y >> 1));
385 break;
386 }
387 [[fallthrough]]; // South_MergedStairs_BecomeMultiC
388 case 0xF9D: // RoomDraw_AutoStairs_South_MultiLayer_C
389 w_.SetWord(0x049E, Append(0x049E, 0x06EC, y >> 1));
390 break;
391 case 0xF98: { // RoomDraw_BigKeyLock (#_0198AE)
392 const uint16_t x = w_.Word(kBigKeyLockEnd);
393 const bool opened =
394 (w_.Word(kRoomFlags) & RomWord(rom_, kRoomFlagMask + x)) != 0;
395 w_.SetWord(0x06E0 + x, opened ? 0 : y);
396 w_.SetWord(kBigKeyLockEnd, static_cast<uint16_t>(x + 2));
397 break;
398 }
399 case 0xF99: // RoomDraw_Chest (#_0198D0)
400 case 0xFB1: { // RoomDraw_BigChest (#_0199BB)
401 const uint16_t x = w_.Word(kChestCount);
402 uint16_t value = layer_y;
403 if (object.id_ == 0xFB1) {
404 value |= 0x8000;
405 }
406 const bool opened =
407 (w_.Word(kRoomFlags) & RomWord(rom_, kRoomFlagMask + x)) != 0;
408 w_.SetWord(0x06E0 + x, opened ? 0 : value);
409 SetCounters({kChestCount, kBigKeyLockEnd},
410 static_cast<uint16_t>(x + 2));
411 break;
412 }
413 default:
414 break;
415 }
416 }
417
418 // RoomDraw_PushableBlock (#_01B4D6): $0500 = 0, $0540 = table word.
419 void RecordPushableBlock(const RoomObject& block) {
420 const uint16_t x = w_.Word(kManipulableCount);
421 w_.SetWord(kManipulableCount, static_cast<uint16_t>(x + 2));
422 w_.SetWord(0x0500 + x, 0);
423 w_.SetWord(0x0520 + x, 0);
424 uint16_t word = TilemapOffset(block);
425 if (block.GetLayerValue() & 1) {
426 word |= 0x2000;
427 }
428 word |= static_cast<uint16_t>((block.block_behavior_layer() & 1) << 14);
429 w_.SetWord(0x0540 + x, word);
430 }
431
432 // RoomDraw_LightableTorch (#_01B509): $0540,$042E = table word.
433 void RecordTorch(const RoomObject& torch) {
434 const uint16_t x = w_.Word(kTorchEnd);
435 uint16_t word = TilemapOffset(torch);
436 if (torch.GetLayerValue() & 1) {
437 word |= 0x2000;
438 }
439 if (torch.lit_) {
440 word |= 0x8000;
441 }
442 w_.SetWord(0x0540 + x, word);
443 w_.SetWord(kTorchEnd, static_cast<uint16_t>(x + 2));
444 }
445
446 // ---- doors -------------------------------------------------------------
447
448 // Door routine registers: $00 (door word), $02 (position * 2), $04 (type),
449 // $0A (graphics type), $08 (tilemap position).
450 struct DoorRegs {
451 uint16_t word = 0;
452 uint16_t position = 0;
453 uint16_t type = 0;
454 uint16_t gfx = 0;
455 };
456
457 struct FlagResult {
458 bool carry = false;
459 uint16_t gfx = 0; // returned in Y
460 uint16_t x = 0; // X on return
461 };
462
463 // RoomDraw_FlagDoorsAndGetFinalType (#_01B0DA).
464 FlagResult FlagDoors(uint16_t x, uint16_t direction, uint16_t y) {
465 w_.SetWord(kDoorDirections + x, direction);
466 w_.SetWord(kDoorPositions + x, y);
467 w_.SetWord(kDoorTypes + x,
468 static_cast<uint16_t>(((x >> 1) << 8) | (regs_.type & 0xFF)));
469 const uint16_t slot = x & 0x0F;
470 if (slot < 8 && (w_.Word(kDoorOpenMask) & tables_.DungeonMask(slot))) {
471 const uint16_t type = w_.Word(kDoorTypes + x) & 0xFF;
472 const bool shutter = type == 0x18 || type == 0x44;
473 if (!(shutter && w_.Word(kShutterController) != 0)) {
474 regs_.gfx = RomWord(rom_, kDoorwayReplacementDoorGfx + regs_.type);
475 }
476 }
477 FlagResult result;
478 result.gfx = regs_.gfx;
479 x = static_cast<uint16_t>(x + 2);
480 w_.SetWord(kDoorListIndex, x);
481 result.x = x;
482 if (result.gfx == 0x32 || result.gfx == 0x08) {
483 return result; // CLC_and_EXIT
484 }
485 if (regs_.type == 0x1A) {
486 // Both compares against $2F fall through to .flag_shutter_door.
487 x = static_cast<uint16_t>(x - 2);
488 w_.SetWord(0x0436,
489 static_cast<uint16_t>((x << 8) | ((regs_.word & 0x03) << 1)));
490 w_.SetWord(kDoorOpenMask, static_cast<uint16_t>(w_.Word(kDoorOpenMask) |
491 tables_.DungeonMask(x)));
492 result.gfx = 0;
493 result.x = x;
494 }
495 result.carry = true;
496 return result;
497 }
498
499 // Rewrites the type of the door just flagged ($197E,X after the INX INX).
500 void RewriteDoorType(const FlagResult& flag, uint16_t type) {
501 const int address = 0x197E + flag.x;
502 w_.SetWord(address,
503 static_cast<uint16_t>((w_.Word(address) & 0xFF00) | type));
504 }
505
506 // RoomDraw_ChangeTilemapAddressToLowerLayer (#_01A8FA).
508 const int address = 0x199E + w_.Word(kDoorListIndex);
509 w_.SetWord(address, static_cast<uint16_t>(w_.Word(address) | 0x2000));
510 }
511
512 // RoomDraw_MarkLayerToggleDoor / RoomDraw_MarkDungeonToggleDoor.
513 void MarkToggleDoor(int counter, int list, uint16_t position) {
514 w_.SetWord(counter, Append(counter, list, position >> 1));
515 }
516
517 void AddExitDoor(uint16_t y) {
518 const uint16_t x = w_.Word(kExitDoorCount);
519 w_.SetWord(kExitDoorPositions + x, y);
520 w_.SetWord(kExitDoorCount, static_cast<uint16_t>(x + 2));
521 }
522
523 uint16_t Slot() const { return w_.Word(kDoorListIndex); }
524
525 // The far side of a door on a middle seam goes in slot | 0x10 and does not
526 // advance $0460.
527 template <typename Fn>
528 void WithPartnerSlot(Fn fn) {
529 const uint16_t saved = Slot();
530 w_.SetWord(kDoorListIndex, static_cast<uint16_t>(saved | 0x10));
531 fn();
532 w_.SetWord(kDoorListIndex, saved);
533 }
534
535 void OneSidedShuttersNorth(uint16_t y) {
536 const auto flag = FlagDoors(Slot(), 0, y);
537 if (flag.carry) {
538 if (flag.gfx == 0x36) {
539 RewriteDoorType(flag, 0x18);
540 } else if (flag.gfx == 0x38) {
541 RewriteDoorType(flag, 0x00);
542 }
543 }
544 }
545 void OneSidedShuttersSouth(uint16_t y) {
546 const auto flag = FlagDoors(Slot(), 1, y);
547 if (flag.carry) {
548 if (flag.gfx == 0x1E || flag.gfx == 0x36) {
549 RewriteDoorType(flag, 0x00);
550 } else if (flag.gfx == 0x38) {
551 RewriteDoorType(flag, 0x18);
552 }
553 }
554 }
555 void OneSidedShuttersWest(uint16_t y) {
556 const auto flag = FlagDoors(Slot(), 2, y);
557 if (flag.carry) {
558 if (flag.gfx == 0x36) {
559 RewriteDoorType(flag, 0x18);
560 } else if (flag.gfx == 0x38) {
561 RewriteDoorType(flag, 0x00);
562 }
563 }
564 }
565 void OneSidedShuttersEast(uint16_t y) {
566 const auto flag = FlagDoors(Slot(), 3, y);
567 if (flag.carry) {
568 if (flag.gfx == 0x36) {
569 RewriteDoorType(flag, 0x00);
570 } else if (flag.gfx == 0x38) {
571 RewriteDoorType(flag, 0x18);
572 }
573 }
574 }
575 // RoomDraw_OneSidedLowerShutters_*: the rewrite ignores the carry.
576 void OneSidedLowerShutters(uint16_t direction, uint16_t y) {
577 const auto flag = FlagDoors(Slot(), direction, y);
578 const bool north_or_west = direction == 0 || direction == 2;
579 if (flag.gfx == 0x48) {
580 RewriteDoorType(flag, north_or_west ? 0x44 : 0x40);
581 } else if (flag.gfx == 0x4A) {
582 RewriteDoorType(flag, north_or_west ? 0x40 : 0x44);
583 }
584 MoveLastDoorToLowerLayer();
585 }
586
587 // RoomDraw_CheckIfLowerLayerDoors_Vertical (#_01AA66).
588 void CheckIfLowerLayerDoorsVertical(uint16_t type, uint16_t y) {
589 OneSidedShuttersSouth(y);
590 if (type == 0x08) {
591 MoveLastDoorToLowerLayer();
592 }
593 }
594
595 // RoomDraw_NormalRangedDoors_North (#_01A90F).
596 void NormalRangedDoorsNorth(uint16_t y) {
597 if (regs_.position >= 0x0C) {
598 WithPartnerSlot([&] {
599 CheckIfLowerLayerDoorsVertical(
600 regs_.type,
601 RomWord(rom_, kDoorPositionsNorthMiddle + regs_.position));
602 });
603 regs_.gfx = regs_.type;
604 }
605 OneSidedShuttersNorth(y);
606 }
607
608 // RoomDraw_NormalRangedDoors_East (#_01ABC8).
609 void NormalRangedDoorsEast(uint16_t type, uint16_t y) {
610 OneSidedShuttersEast(y);
611 if (type == 0x08) {
612 MoveLastDoorToLowerLayer();
613 }
614 }
615
616 // RoomDraw_NormalRangedDoors_West (#_01AB1F).
617 void NormalRangedDoorsWest(uint16_t y) {
618 if (regs_.position >= 0x0C) {
619 WithPartnerSlot([&] {
620 NormalRangedDoorsEast(
621 regs_.type,
622 RomWord(rom_, kDoorPositionsWestMiddle + regs_.position));
623 });
624 regs_.gfx = regs_.type;
625 }
626 OneSidedShuttersWest(y);
627 }
628
629 // RoomDraw_Door_ExplodingWall (#_01AC70).
631 const uint16_t y = RomWord(rom_, kExplodingWallPositions + regs_.position);
632 const uint16_t x = Slot();
633 w_.SetWord(kDoorPositions + x, static_cast<uint16_t>(y + 0x14));
634 w_.SetWord(kDoorTypes + x, static_cast<uint16_t>(((x >> 1) << 8) | 0x30));
635 if ((w_.Word(kDoorOpenMask) & tables_.DungeonMask(x & 0x0F)) == 0) {
636 w_.SetWord(kDoorDirections + x, 0);
637 } else if (tag1_ == 0x20 || tag1_ == 0x25 || tag1_ == 0x28) {
638 tag1_ = 0;
639 } else {
640 tag2_ = 0;
641 }
642 w_.SetWord(kDoorListIndex, static_cast<uint16_t>(x + 2));
643 }
644
645 // RoomDraw_Door_North (#_01A81C).
646 void DoorNorth() {
647 const uint16_t y = RomWord(rom_, kDoorPositionsNorthWall + regs_.position);
648 const uint16_t t = regs_.type;
649 if (t == 0x30) {
651 } else if (t == 0x16) {
652 MarkToggleDoor(kLayerToggleCount, 0x06C0,
653 static_cast<uint16_t>(y - 0xFE));
654 } else if (t == 0x32) { // RoomDraw_NorthCurtainDoor
655 FlagDoors(Slot(), 0, y);
656 } else if (t == 0x06) {
657 // RoomDraw_MakeDoorHighPriorityLowerLayer_North flags the slot in X,
658 // which still holds the position index here, not $0460.
659 FlagDoors(regs_.position, 0, y);
660 } else if (t == 0x14) {
661 MarkToggleDoor(kDungeonToggleCount, 0x06D0,
662 static_cast<uint16_t>(y - 0xFE));
663 } else if (t == 0x02) {
664 NormalRangedDoorsNorth(y);
665 } else if (t == 0x12) {
666 AddExitDoor(y);
667 } else if (t == 0x08) {
668 NormalRangedDoorsNorth(y);
669 MoveLastDoorToLowerLayer();
670 } else if (t == 0x20 || t == 0x22 || t == 0x24 || t == 0x26) {
671 const uint16_t x = Slot();
672 w_.SetWord(kDoorDirections + x, 0);
673 w_.SetWord(kDoorPositions + x, y);
674 w_.SetWord(kDoorTypes + x, static_cast<uint16_t>(((x >> 1) << 8) | t));
675 if (tables_.DungeonMask(x & 0x0F) & w_.Word(kDoorOpenMask)) {
676 w_.SetWord(kDoorListIndex, static_cast<uint16_t>(x + 2));
677 } else if (t < 0x24) {
678 OneSidedShuttersNorth(y);
679 } else {
680 FlagDoors(Slot(), 0, y);
681 MoveLastDoorToLowerLayer();
682 }
683 } else if (t >= 0x40) { // RoomDraw_HighRangeDoor_North (#_01AD41)
684 if (regs_.position >= 0x0C && t != 0x46) {
685 WithPartnerSlot([&] {
686 OneSidedLowerShutters(
687 1, RomWord(rom_, kDoorPositionsNorthMiddle + regs_.position));
688 });
689 }
690 OneSidedLowerShutters(0, y);
691 } else {
692 NormalRangedDoorsNorth(y);
693 }
694 }
695
696 // RoomDraw_Door_South (#_01A984).
697 void DoorSouth() {
698 uint16_t y = RomWord(rom_, kDoorPositionsSouthMiddle + regs_.position);
699 const uint16_t t = regs_.type;
700 if (t == 0x16) {
701 MarkToggleDoor(kLayerToggleCount, 0x06C0,
702 static_cast<uint16_t>(y + 0x202));
703 } else if (t == 0x06) {
704 FlagDoors(regs_.position, 1, y);
705 } else if (t == 0x14) {
706 MarkToggleDoor(kDungeonToggleCount, 0x06D0,
707 static_cast<uint16_t>(y + 0x202));
708 } else if (t == 0x12) {
709 AddExitDoor(y);
710 } else if (t >= 0x40) {
711 OneSidedLowerShutters(1, y);
712 } else if (t == 0x0A || t == 0x0E || t == 0x10) {
713 // Fancy dungeon exit / RoomDraw_CaveExitLight /
714 // RoomDraw_HighPriorityExitLight.
715 FlagDoors(Slot(), 1, y);
716 } else if (t == 0x0C || t == 0x04) {
717 // Lower-layer fancy exit / RoomDraw_CheckIfExitDoor: Y | $2000.
718 y |= 0x2000;
719 FlagDoors(Slot(), 1, y);
720 } else {
721 CheckIfLowerLayerDoorsVertical(t, y);
722 }
723 }
724
725 // RoomDraw_Door_West (#_01AAD7).
726 void DoorWest() {
727 const uint16_t y = RomWord(rom_, kDoorPositionsWestWall + regs_.position);
728 const uint16_t t = regs_.type;
729 if (t == 0x16) {
730 MarkToggleDoor(kLayerToggleCount, 0x06C0,
731 static_cast<uint16_t>(y + 0x7C));
732 } else if (t == 0x06) {
733 FlagDoors(regs_.position, 2, y);
734 } else if (t == 0x14) {
735 MarkToggleDoor(kDungeonToggleCount, 0x06D0,
736 static_cast<uint16_t>(y + 0x7C));
737 } else if (t == 0x08) {
738 NormalRangedDoorsWest(y);
739 MoveLastDoorToLowerLayer();
740 } else if (t >= 0x40) { // RoomDraw_HighRangeDoor_West (#_01AE40)
741 if (regs_.position >= 0x0C) {
742 WithPartnerSlot([&] {
743 OneSidedLowerShutters(
744 3, RomWord(rom_, kDoorPositionsWestMiddle + regs_.position));
745 });
746 }
747 OneSidedLowerShutters(2, y);
748 } else {
749 NormalRangedDoorsWest(y);
750 }
751 }
752
753 // RoomDraw_Door_East (#_01AB99).
754 void DoorEast() {
755 const uint16_t y = RomWord(rom_, kDoorPositionsEastMiddle + regs_.position);
756 const uint16_t t = regs_.type;
757 if (t == 0x16) {
758 MarkToggleDoor(kLayerToggleCount, 0x06C0,
759 static_cast<uint16_t>(y + 0x88));
760 } else if (t == 0x06) {
761 FlagDoors(regs_.position, 3, y);
762 } else if (t == 0x14) {
763 MarkToggleDoor(kDungeonToggleCount, 0x06D0,
764 static_cast<uint16_t>(y + 0x88));
765 } else if (t >= 0x40) {
766 OneSidedLowerShutters(3, y);
767 } else {
768 NormalRangedDoorsEast(t, y);
769 }
770 }
771
772 // RoomDraw_DoorObject (#_018916).
773 void RecordDoor(const Room::Door& door) {
774 regs_.word = static_cast<uint16_t>(door.byte1 | (door.byte2 << 8));
775 regs_.position = static_cast<uint16_t>((door.byte1 & 0xF0) >> 3);
776 regs_.type = door.byte2;
777 regs_.gfx = door.byte2;
778 switch (door.byte1 & 0x03) {
779 case 0:
780 DoorNorth();
781 break;
782 case 1:
783 DoorSouth();
784 break;
785 case 2:
786 DoorWest();
787 break;
788 default:
789 DoorEast();
790 break;
791 }
792 }
793
794 const Rom& rom_;
800 uint8_t tag1_ = 0;
801 uint8_t tag2_ = 0;
802};
803
804// ---------------------------------------------------------------------------
805// Attribute passes over the recorded lists.
806
808 public:
809 explicit AttributeWriter(RoomCollisionMaps& maps) : maps_(maps) {}
810
811 // STA.l $7F2000,X (16-bit): two bytes. Bytes outside COLMAPA/B belong to
812 // other WRAM and are dropped.
813 void Store(int x, uint16_t value, CollisionSource source) {
814 StoreByte(x, static_cast<uint8_t>(value & 0xFF), source);
815 StoreByte(x + 1, static_cast<uint8_t>(value >> 8), source);
816 }
817 void StoreByte(int x, uint8_t value, CollisionSource source) {
818 if (x < 0 || x >= static_cast<int>(kRoomCollisionBytes)) {
819 return;
820 }
821 maps_.attributes[x] = value;
822 maps_.sources[x] = source;
823 }
824 uint16_t Word(int x) const {
825 auto byte = [&](int i) -> uint16_t {
826 return (i >= 0 && i < static_cast<int>(kRoomCollisionBytes))
827 ? maps_.attributes[i]
828 : 0;
829 };
830 return static_cast<uint16_t>(byte(x) | (byte(x + 1) << 8));
831 }
832
833 private:
835};
836
837// Loops `index` from its current value to `end` in steps of 2 (the game's
838// INY INY / CPY end / BNE). A list whose end is below the start would run
839// through all of WRAM in the game; stop at 0x80 instead.
840template <typename Fn>
841void ForEachEntry(uint16_t& index, uint16_t end, Fn fn) {
842 do {
843 fn(index);
844 index = static_cast<uint16_t>(index + 2);
845 } while (index != end && index < 0x80);
846}
847
848// Underworld_LoadObjectAttribute (#_01B967).
849void ApplyObjectAttributes(LowWram& w, uint8_t tag1, uint8_t tag2,
850 AttributeWriter& out) {
851 using S = CollisionSource;
852 uint16_t y = 0;
853 if (w.Word(kStarCount) != 0) {
854 ForEachEntry(y, w.Word(kStarCount), [&](uint16_t i) {
855 const int x = w.Word(0x06A0 + i);
856 out.Store(x, 0x3B3B, S::kStar);
857 out.Store(x + 0x40, 0x3B3B, S::kStar);
858 });
859 }
860
861 // Stairs: one list at $06B0 with running end indices; $00 counts up
862 // from 0x3030 (0x34.. for the "down" lists).
863 uint16_t counter = 0x3030;
864 y = 0;
865 auto next_counter = [&] {
866 counter = static_cast<uint16_t>(counter + 0x101);
867 };
868 auto stairs = [&](int end_address, auto&& body) {
869 const uint16_t end = w.Word(end_address);
870 if (y == end) {
871 return;
872 }
873 ForEachEntry(y, end, [&](uint16_t i) { body(w.Word(0x06B0 + i)); });
874 };
875 auto spiral = [&](uint16_t value) {
876 return [&, value](int x) {
877 out.Store(x + 0x01, value, S::kStairs);
878 out.Store(x + 0x81, value, S::kStairs);
879 out.Store(x + 0xC1, value, S::kStairs);
880 out.Store(x + 0x41, counter, S::kStairs);
881 next_counter();
882 };
883 };
884 auto straight_north = [&](int x) {
885 out.Store(x + 0x81, 0x0000, S::kStairs);
886 out.Store(x + 0xC1, 0x0000, S::kStairs);
887 out.Store(x + 0x01, 0x3838, S::kStairs);
888 out.Store(x + 0x41, counter, S::kStairs);
889 next_counter();
890 };
891 auto straight_south = [&](int x) {
892 out.Store(x + 0x01, 0x0000, S::kStairs);
893 out.Store(x + 0x41, 0x0000, S::kStairs);
894 out.Store(x + 0xC1, 0x3939, S::kStairs);
895 out.Store(x + 0x81, counter, S::kStairs);
896 next_counter();
897 };
898 if (w.Word(0x0438) != 0) {
899 stairs(0x0438, [&](int x) { // .next_intraroom_stairs_up
900 out.Store(x + 0x81, 0x0000, S::kStairs);
901 out.Store(x + 0x01, 0x2626, S::kStairs);
902 out.Store(x + 0x41, counter, S::kStairs);
903 next_counter();
904 });
905 }
906 stairs(0x047E, spiral(0x5E5E));
907 stairs(0x0482, spiral(0x5F5F));
908 stairs(0x04A2, straight_north);
909 stairs(0x04A4, straight_south);
910 counter = static_cast<uint16_t>((counter & 0x0707) | 0x3434);
911 stairs(0x043A, [&](int x) { // .next_intra_stairs_south
912 out.Store(x + 0xC1, 0x2626, S::kStairs);
913 out.Store(x + 0x81, counter, S::kStairs);
914 next_counter();
915 });
916 stairs(0x0480, spiral(0x5E5E));
917 stairs(0x0484, spiral(0x5F5F));
918 stairs(0x04A6, straight_north);
919 stairs(0x04A8, straight_south);
920
921 // Auto north multi-layer / merged stairs ($06B8).
922 y = 0;
923 uint16_t end = 0;
924 uint16_t value = 0;
925 if (w.Word(0x043C) != 0) {
926 value = 0x1F1F;
927 end = w.Word(0x043C);
928 } else if (w.Word(0x043E) != 0) {
929 value = 0x1E1E;
930 end = w.Word(0x043E);
931 } else if (w.Word(0x0440) != 0) {
932 value = 0x1D1D;
933 end = w.Word(0x0440);
934 }
935 if (value != 0) {
936 ForEachEntry(y, end, [&](uint16_t i) {
937 const int x = w.Word(0x06B8 + i);
938 out.Store(x + 0x0000, 0x0002, S::kLayerStairs);
939 out.Store(x + 0x10C0, 0x0002, S::kLayerStairs);
940 out.Store(x + 0x0002, 0x0200, S::kLayerStairs);
941 out.Store(x + 0x10C2, 0x0200, S::kLayerStairs);
942 out.Store(x + 0x0040, 0x0001, S::kLayerStairs);
943 out.Store(x + 0x1080, 0x0001, S::kLayerStairs);
944 out.Store(x + 0x0042, 0x0100, S::kLayerStairs);
945 out.Store(x + 0x1082, 0x0100, S::kLayerStairs);
946 out.Store(x + 0x0041, value, S::kLayerStairs);
947 out.Store(x + 0x1041, value, S::kLayerStairs);
948 out.Store(x + 0x0081, value, S::kLayerStairs);
949 out.Store(x + 0x1081, value, S::kLayerStairs);
950 });
951 }
952 if (y != w.Word(0x0448)) { // .next_water_overlay_a
953 ForEachEntry(y, w.Word(0x0448), [&](uint16_t i) {
954 const int x = w.Word(0x06B8 + i);
955 out.Store(x + 0x0000, 0x0A03, S::kWater);
956 out.Store(x + 0x1000, 0x0A03, S::kWater);
957 out.Store(x + 0x0002, 0x030A, S::kWater);
958 out.Store(x + 0x1002, 0x030A, S::kWater);
959 out.Store(x + 0x0040, 0x0803, S::kWater);
960 out.Store(x + 0x0042, 0x0308, S::kWater);
961 });
962 }
963 y = 0;
964 if (w.Word(0x0442) != 0) { // .next_water_overlay_a2
965 ForEachEntry(y, w.Word(0x0442), [&](uint16_t i) {
966 const int x = w.Word(0x06B8 + i);
967 out.Store(x + 0x0000, 0x0003, S::kWater);
968 out.Store(x + 0x0002, 0x0300, S::kWater);
969 out.Store(x + 0x1000, 0x0A03, S::kWater);
970 out.Store(x + 0x1002, 0x030A, S::kWater);
971 out.Store(x + 0x0040, 0x0808, S::kWater);
972 out.Store(x + 0x0042, 0x0808, S::kWater);
973 });
974 }
975 if (y != w.Word(0x0444)) { // .next_water_ladder
976 ForEachEntry(y, w.Word(0x0444), [&](uint16_t i) {
977 const int x = w.Word(0x06B8 + i);
978 out.Store(x + 0x0000, 0x0003, S::kWater);
979 out.Store(x + 0x0002, 0x0300, S::kWater);
980 out.Store(x + 0x1000, 0x0A03, S::kWater);
981 out.Store(x + 0x1002, 0x030A, S::kWater);
982 });
983 }
984
985 // Manipulables ($0500 type, $0540 position): 0x70, 0x71, ... skipping
986 // bombable floor segments (type 0x3X) but still counting them.
987 y = 0;
988 if (w.Word(kManipulableCount) != 0) {
989 counter = 0x7070;
990 ForEachEntry(y, w.Word(kManipulableCount), [&](uint16_t i) {
991 if ((w.Word(0x0500 + i) & 0x00F0) != 0x0030) {
992 const int x = (w.Word(0x0540 + i) & 0x3FFF) >> 1;
993 out.Store(x, counter, S::kManipulable);
994 out.Store(x + 0x40, counter, S::kManipulable);
995 }
996 next_counter();
997 });
998 }
999 // Torches follow in the same $0540 list: 0xC0, 0xC1, ...
1000 if (y != w.Word(kTorchEnd)) {
1001 counter = 0xC0C0;
1002 ForEachEntry(y, w.Word(kTorchEnd), [&](uint16_t i) {
1003 const int x = (w.Word(0x0540 + i) & 0x3FFF) >> 1;
1004 out.Store(x, counter, S::kTorch);
1005 out.Store(x + 0x40, counter, S::kTorch);
1006 counter = static_cast<uint16_t>((counter & 0xEFEF) + 0x0101);
1007 });
1008 }
1009
1010 // Chests and big key locks ($06E0): 0x58, 0x59, ...
1011 auto hides_chests = [](uint8_t tag) {
1012 return tag == 0x27 || tag == 0x3C || tag == 0x3E ||
1013 (tag >= 0x29 && tag < 0x33);
1014 };
1015 counter = 0x5858;
1016 y = 0;
1017 bool skip_locks = false;
1018 if (w.Word(kChestCount) != 0) {
1019 if (hides_chests(tag1) || hides_chests(tag2)) {
1020 skip_locks = true; // BEQ .hidden_chests skips the locks too
1021 } else {
1022 // Underworld_SetChestAttributes (#_01BDDB).
1023 ForEachEntry(y, w.Word(kChestCount), [&](uint16_t i) {
1024 const uint16_t entry = w.Word(0x06E0 + i);
1025 if (entry != 0) {
1026 const int x = (entry & 0x7FFF) >> 1;
1027 out.Store(x, counter, S::kChest);
1028 out.Store(x + 0x40, counter, S::kChest);
1029 if (entry & 0x8000) { // big chest: 4x4 tiles
1030 w.SetWord(0x06E0 + i, static_cast<uint16_t>(entry & 0x7FFF));
1031 out.Store(x + 0x42, counter, S::kChest);
1032 out.Store(x + 0x80, counter, S::kChest);
1033 out.Store(x + 0x82, counter, S::kChest);
1034 }
1035 }
1036 next_counter();
1037 });
1038 }
1039 }
1040 if (!skip_locks && y != w.Word(kBigKeyLockEnd)) {
1041 ForEachEntry(y, w.Word(kBigKeyLockEnd), [&](uint16_t i) {
1042 const uint16_t entry = w.Word(0x06E0 + i);
1043 w.SetWord(0x06E0 + i, static_cast<uint16_t>(entry | 0x8000));
1044 const int x = (entry & 0x7FFF) >> 1;
1045 out.Store(x, counter, S::kBigKeyLock);
1046 out.Store(x + 0x40, counter, S::kBigKeyLock);
1047 next_counter();
1048 });
1049 }
1050
1051 // Auto south multi-layer stairs ($06EC).
1052 y = 0;
1053 value = 0;
1054 if (w.Word(0x049A) != 0) {
1055 value = 0x3F3F;
1056 end = w.Word(0x049A);
1057 } else if (w.Word(0x049C) != 0) {
1058 value = 0x3E3E;
1059 end = w.Word(0x049C);
1060 } else if (w.Word(0x049E) != 0) {
1061 value = 0x3D3D;
1062 end = w.Word(0x049E);
1063 }
1064 if (value != 0) {
1065 ForEachEntry(y, end, [&](uint16_t i) {
1066 const int x = w.Word(0x06EC + i);
1067 out.Store(x + 0x1000, 0x0002, S::kLayerStairs);
1068 out.Store(x + 0x00C0, 0x0002, S::kLayerStairs);
1069 out.Store(x + 0x1040, 0x0001, S::kLayerStairs);
1070 out.Store(x + 0x0080, 0x0001, S::kLayerStairs);
1071 out.Store(x + 0x1002, 0x0200, S::kLayerStairs);
1072 out.Store(x + 0x00C2, 0x0200, S::kLayerStairs);
1073 out.Store(x + 0x1042, 0x0100, S::kLayerStairs);
1074 out.Store(x + 0x0082, 0x0100, S::kLayerStairs);
1075 out.Store(x + 0x0041, value, S::kLayerStairs);
1076 out.Store(x + 0x1041, value, S::kLayerStairs);
1077 out.Store(x + 0x0081, value, S::kLayerStairs);
1078 out.Store(x + 0x1081, value, S::kLayerStairs);
1079 });
1080 }
1081 y = 0;
1082 if (w.Word(0x04AE) != 0) { // .next_south_merged_auto_stairs
1083 ForEachEntry(y, w.Word(0x04AE), [&](uint16_t i) {
1084 const int x = w.Word(0x06EC + i);
1085 out.Store(x + 0x10C0, 0x0A03, S::kWater);
1086 out.Store(x + 0x10C2, 0x030A, S::kWater);
1087 out.Store(x + 0x00C0, 0x0003, S::kWater);
1088 out.Store(x + 0x00C2, 0x0300, S::kWater);
1089 out.Store(x + 0x0080, 0x0808, S::kWater);
1090 out.Store(x + 0x0082, 0x0808, S::kWater);
1091 });
1092 }
1093}
1094
1095// Underworld_LoadDoorAttribute (#_01BE17) and its callees.
1097 using S = CollisionSource;
1098 RomTables tables(rom);
1099 auto doorway_properties = [&](uint16_t type) {
1100 return RomWord(rom, kDoorwayTileProperties + type);
1101 };
1102 auto is_exit_door = [&](uint16_t position) {
1103 for (int i = 0; i < 4; ++i) {
1104 if (position == w.Word(kExitDoorPositions + i * 2)) {
1105 return true;
1106 }
1107 }
1108 return false;
1109 };
1110
1111 for (uint16_t y = 0; y < 0x20; y = static_cast<uint16_t>(y + 2)) {
1112 const uint16_t position = w.Word(kDoorPositions + y);
1113 if (position == 0) {
1114 continue;
1115 }
1116 const uint16_t type = w.Word(kDoorTypes + y) & 0x00FE;
1117 const uint16_t direction = w.Word(kDoorDirections + y) & 0x0003;
1118
1119 // Closed door: F0 | slot on the two middle rows/columns.
1120 auto locked = [&] {
1121 const uint8_t v = static_cast<uint8_t>((y >> 1) | 0xF0);
1122 const int x = position >> 1;
1123 out.Store(x + 0x41, static_cast<uint16_t>(v | (v << 8)), S::kLockedDoor);
1124 out.Store(x + 0x81, static_cast<uint16_t>(v | (v << 8)), S::kLockedDoor);
1125 };
1126
1127 // AddFullLongDoorDoorwayProps (#_01C0B8).
1128 auto full_long = [&] {
1129 uint16_t v = doorway_properties(type);
1130 if (direction == 0) {
1131 const int x = (position >> 1) & 0x783F;
1132 for (int k = 0; k < 10; ++k) {
1133 out.Store(x + 0x01 + 0x40 * k, v, S::kDoor);
1134 }
1135 } else if (direction == 1) {
1136 if (type == 0x0C || type == 0x10 || type == 0x04 ||
1137 is_exit_door(position & 0x1FFF)) {
1138 v = 0x8E8E;
1139 }
1140 const int x = (position >> 1) + 0x40;
1141 for (int k = 0; k < 8; ++k) {
1142 out.Store(x + 0x01 + 0x40 * k, v, S::kDoor);
1143 }
1144 } else {
1145 const int x =
1146 direction == 2 ? ((position >> 1) & 0xFFE0) : (position >> 1) + 1;
1147 const uint16_t v2 = static_cast<uint16_t>(v + 0x0101);
1148 for (int o : {0x40, 0x42, 0x44, 0x46, 0x80, 0x82, 0x84, 0x86}) {
1149 out.Store(x + o, v2, S::kDoor);
1150 }
1151 }
1152 };
1153
1154 // Underworld_LoadSingleDoorAttribute .apply_doorway (#_01BEB8).
1155 auto doorway = [&] {
1156 if (type >= 0x20 && type < 0x28) {
1157 return;
1158 }
1159 uint16_t v = doorway_properties(type);
1160 if (direction == 0) {
1161 if (is_exit_door(position)) {
1162 v = 0x8E8E;
1163 }
1164 const int x = (position >> 1) & 0x783F;
1165 for (int k = 0; k < 7; ++k) {
1166 out.Store(x + 0x01 + 0x40 * k, v, S::kDoor);
1167 }
1168 out.Store(x + 0x1C1, 0x0000, S::kDoor);
1169 } else if (direction == 1) {
1170 if (type == 0x0A || type == 0x0E || is_exit_door(position)) {
1171 v = 0x8E8E;
1172 }
1173 const int x = position >> 1;
1174 for (int o : {0x41, 0x81, 0xC1, 0x101, 0x141}) {
1175 out.Store(x + o, v, S::kDoor);
1176 }
1177 } else if (direction == 2) {
1178 const int x = (position >> 1) & 0xFFE0;
1179 const uint16_t v2 = static_cast<uint16_t>(v + 0x0101);
1180 for (int o : {0x40, 0x42, 0x80, 0x82}) {
1181 out.Store(x + o, v2, S::kDoor);
1182 }
1183 out.Store(x + 0x44, v2 & 0x00FF, S::kDoor);
1184 out.Store(x + 0x84, v2 & 0x00FF, S::kDoor);
1185 } else {
1186 const int x = position >> 1;
1187 const uint16_t v2 = static_cast<uint16_t>(v + 0x0101);
1188 for (int o : {0x42, 0x44, 0x82, 0x84}) {
1189 out.Store(x + o, v2, S::kDoor);
1190 }
1191 out.Store(x + 0x40, v2 & 0xFF00, S::kDoor);
1192 out.Store(x + 0x80, v2 & 0xFF00, S::kDoor);
1193 }
1194 };
1195
1196 const uint16_t open_mask = w.Word(kDoorOpenMask);
1197 if (type == 0x00 || type == 0x06 || type == 0x12 || type == 0x0A ||
1198 type == 0x0E) {
1199 doorway();
1200 } else if (type == 0x0C || type == 0x10 || type == 0x04 || type == 0x02 ||
1201 type == 0x08) {
1202 full_long();
1203 } else if (type == 0x30) {
1204 // Exploding wall: nothing until it is blown open.
1205 } else if (type >= 0x40) { // AddDoorwayPropsForWeirdos (#_01C085)
1206 if (type == 0x40 || type == 0x46 ||
1207 (open_mask & tables.DungeonMask(y & 0xFF))) {
1208 full_long();
1209 } else {
1210 locked();
1211 }
1212 } else {
1213 const uint16_t mask_index =
1214 type == 0x18 ? (y & 0xFF) : (y & 0x0F); // 0x44 is handled above.
1215 if (open_mask & tables.DungeonMask(mask_index)) {
1216 doorway();
1217 } else {
1218 locked();
1219 }
1220 }
1221 }
1222
1223 // Underworld_LoadSingleDoorTileType (#_01D51F): layer toggle doors OR
1224 // 0x10, dungeon toggle doors OR 0x20, on the upper map when the tile there
1225 // is a doorway (0x8X), otherwise on the lower map.
1226 auto toggles = [&](int counter, int list, uint16_t bits) {
1227 const uint16_t count = w.Word(counter);
1228 if (count == 0) {
1229 return;
1230 }
1231 uint16_t y = 0;
1232 ForEachEntry(y, count, [&](uint16_t i) {
1233 const int x = w.Word(list + i);
1234 if ((out.Word(x) & 0x00F0) == 0x0080) {
1235 const uint16_t v = out.Word(x) | bits;
1236 out.Store(x, v, S::kDoorLayerToggle);
1237 out.Store(x + 0x40, v, S::kDoorLayerToggle);
1238 } else {
1239 const uint16_t v = out.Word(x + 0x1000) | bits;
1240 out.Store(x + 0x1000, v, S::kDoorLayerToggle);
1241 out.Store(x + 0x1040, v, S::kDoorLayerToggle);
1242 }
1243 });
1244 };
1245 toggles(kLayerToggleCount, 0x06C0, 0x1010);
1246 toggles(kDungeonToggleCount, 0x06D0, 0x2020);
1247
1248 // ChangeDoorToSwitch (#_01C1BA).
1249 const uint16_t door_switch = w.Word(kDoorSwitch);
1250 if (door_switch != 0) {
1251 int x = (door_switch & 0x3FFF) >> 1;
1252 const int columns = (door_switch & 0x8000) ? 6 : 5;
1253 const bool open = (w.Word(kRoomFlags) & 0x1000) != 0;
1254 for (int i = 0; i < columns; ++i, x += 2) {
1255 if (open) {
1256 out.Store(x, 0x0101, S::kDoorSwitch);
1257 out.Store(x + 0x280, 0x0101, S::kDoorSwitch);
1258 for (int o : {0x80, 0x100, 0x180, 0x200}) {
1259 out.Store(x + o, 0x0000, S::kDoorSwitch);
1260 }
1261 } else {
1262 for (int o : {0x80, 0x100, 0x180, 0x200}) {
1263 out.Store(x + o, 0x2323, S::kDoorSwitch);
1264 }
1265 }
1266 }
1267 }
1268}
1269
1270} // namespace
1271
1273 switch (source) {
1274 case CollisionSource::kTilemap:
1275 return "tilemap";
1276 case CollisionSource::kStar:
1277 return "star";
1278 case CollisionSource::kStairs:
1279 return "stairs";
1280 case CollisionSource::kLayerStairs:
1281 return "layer stairs";
1282 case CollisionSource::kWater:
1283 return "water stairs";
1284 case CollisionSource::kManipulable:
1285 return "pot/block";
1286 case CollisionSource::kTorch:
1287 return "torch";
1288 case CollisionSource::kChest:
1289 return "chest";
1290 case CollisionSource::kBigKeyLock:
1291 return "big key lock";
1292 case CollisionSource::kDoor:
1293 return "door";
1294 case CollisionSource::kLockedDoor:
1295 return "locked door";
1296 case CollisionSource::kDoorLayerToggle:
1297 return "toggle door";
1298 case CollisionSource::kDoorSwitch:
1299 return "door switch";
1300 case CollisionSource::kCrystalPeg:
1301 return "crystal peg";
1302 }
1303 return "?";
1304}
1305
1306namespace {
1307
1308// Underworld_LoadCustomTileTypes as assembled (vanilla and the Oracle of
1309// Secrets copy are byte-identical apart from the two table operands):
1310// PHB : PHK : PLB : REP #$30
1311// LDA.w $0AA2 : AND.w #$00FF : ASL A : TAX
1312// LDA.w <offsets>,X : TAY : LDX.w #$0000
1313// LDA.w <data>+$00,Y : STA.l $7EFF40,X
1314// LDA.w <data>+$40,Y : STA.l $7EFF80,X
1315// Returns the operands' PC offsets in the routine's own bank (PHK : PLB).
1316std::optional<CustomTileTypeTable> DecodeLoadCustomTileTypes(const Rom& rom,
1317 uint32_t snes) {
1318 constexpr int kPatternSize = 33;
1319 // -1 marks an operand byte.
1320 constexpr int kPattern[kPatternSize] = {
1321 0x8B, 0x4B, 0xAB, 0xC2, 0x30, 0xAD, 0xA2, 0x0A, 0x29, 0xFF, 0x00,
1322 0x0A, 0xAA, 0xBD, -1, -1, 0xA8, 0xA2, 0x00, 0x00, 0xB9, -1,
1323 -1, 0x9F, 0x40, 0xFF, 0x7E, 0xB9, -1, -1, 0x9F, 0x80, 0xFF};
1324 const int pc = static_cast<int>(SnesToPc(snes));
1325 if (pc < 0 || static_cast<size_t>(pc) + kPatternSize > rom.size()) {
1326 return std::nullopt;
1327 }
1328 for (int i = 0; i < kPatternSize; ++i) {
1329 if (kPattern[i] >= 0 && RomByte(rom, pc + i) != kPattern[i]) {
1330 return std::nullopt;
1331 }
1332 }
1333 const uint16_t offsets = RomWord(rom, pc + 14);
1334 const uint16_t data = RomWord(rom, pc + 21);
1335 if (RomWord(rom, pc + 28) != static_cast<uint16_t>(data + 0x40) ||
1336 offsets < 0x8000 || data < 0x8000) {
1337 return std::nullopt;
1338 }
1339 const uint32_t bank = snes & 0xFF0000;
1340 CustomTileTypeTable table;
1341 table.offsets_pc = static_cast<int>(SnesToPc(bank | offsets));
1342 table.data_pc = static_cast<int>(SnesToPc(bank | data));
1343 return table;
1344}
1345
1346} // namespace
1347
1349 switch (source) {
1350 case CustomTileTypeSource::kVanilla:
1351 return "vanilla";
1352 case CustomTileTypeSource::kHook:
1353 return "hook";
1354 case CustomTileTypeSource::kUnrecognizedHook:
1355 return "unrecognized hook (vanilla table)";
1356 }
1357 return "?";
1358}
1359
1361 CustomTileTypeTable vanilla;
1362 vanilla.offsets_pc = kCustomTileTypesOffset;
1363 vanilla.data_pc = kCustomUnderworldTileTypes;
1364 constexpr uint32_t kEntry = 0x0E942A;
1365 if (RomByte(rom, kLoadCustomTileTypes) == 0x22) { // JSL
1366 const uint32_t target =
1367 static_cast<uint32_t>(RomByte(rom, kLoadCustomTileTypes + 1)) |
1368 static_cast<uint32_t>(RomByte(rom, kLoadCustomTileTypes + 2)) << 8 |
1369 static_cast<uint32_t>(RomByte(rom, kLoadCustomTileTypes + 3)) << 16;
1370 if (auto table = DecodeLoadCustomTileTypes(rom, target)) {
1371 table->source = CustomTileTypeSource::kHook;
1372 table->hook_target = target;
1373 return *table;
1374 }
1375 vanilla.source = CustomTileTypeSource::kUnrecognizedHook;
1376 vanilla.hook_target = target;
1377 return vanilla;
1378 }
1379 if (auto table = DecodeLoadCustomTileTypes(rom, kEntry)) {
1380 return *table;
1381 }
1382 return vanilla;
1383}
1384
1385std::array<uint8_t, kTileAttributeTableSize> LoadUnderworldTileAttributeTable(
1386 const Rom& rom, uint8_t blockset) {
1387 return LoadUnderworldTileAttributeTable(rom, blockset,
1389}
1390
1391std::array<uint8_t, kTileAttributeTableSize> LoadUnderworldTileAttributeTable(
1392 const Rom& rom, uint8_t blockset, const CustomTileTypeTable& custom) {
1393 std::array<uint8_t, kTileAttributeTableSize> table{};
1394 // LoadDefaultTileTypes (#_0E97D9).
1395 for (int i = 0; i < 0x140; ++i) {
1396 table[i] = RomByte(rom, kUnderworldTileTypes + i);
1397 }
1398 for (int i = 0; i < 0x40; ++i) {
1399 table[0x1C0 + i] = RomByte(rom, kUnderworldTileTypes + 0x140 + i);
1400 }
1401 // Underworld_LoadCustomTileTypes (#_0E942A).
1402 const uint16_t offset = RomWord(rom, custom.offsets_pc + blockset * 2);
1403 for (int i = 0; i < 0x80; ++i) {
1404 table[0x140 + i] = RomByte(rom, custom.data_pc + offset + i);
1405 }
1406 return table;
1407}
1408
1410 const std::array<uint8_t, kTileAttributeTableSize>& table,
1411 const RoomTilemaps& tilemaps) {
1412 RoomCollisionMaps maps;
1413 // Underworld_LoadBasicAttribute_full (#_01B8F3).
1414 auto attribute = [&](uint16_t word) -> uint8_t {
1415 const uint16_t tile = word & 0x03FF;
1416 // Tiles 0x200+ index past TILEATTR into $7F0000 (decompression buffer);
1417 // no vanilla room uses them. Treat them as 0.
1418 uint8_t value = tile < kTileAttributeTableSize ? table[tile] : 0;
1419 if (value >= 0x10 && value < 0x1C) {
1420 // bit 1 = vertical flip (word bit 15), bit 0 = horizontal (bit 14).
1421 value |=
1422 static_cast<uint8_t>(((word >> 15) & 1) << 1 | ((word >> 14) & 1));
1423 }
1424 return value;
1425 };
1426 for (size_t i = 0; i < kCollisionMapTiles; ++i) {
1427 const uint16_t upper = i < tilemaps.bg1.size() ? tilemaps.bg1[i] : 0;
1428 const uint16_t lower = i < tilemaps.bg2.size() ? tilemaps.bg2[i] : 0;
1429 maps.attributes[i] = attribute(upper);
1430 maps.attributes[kCollisionMapTiles + i] = attribute(lower);
1431 }
1432 maps.sources.fill(CollisionSource::kTilemap);
1433 return maps;
1434}
1435
1437 const Rom& rom, const RoomCollisionInput& input,
1438 const UnderworldRoomLoadState& state) {
1439 // Underworld_LoadAttributeTable (#_01B8BF).
1442
1443 RoomLoadReplay replay(rom, input, state);
1444 replay.Run();
1445
1446 AttributeWriter out(maps);
1447 ApplyObjectAttributes(replay.wram(), replay.tag1(), replay.tag2(), out);
1448 ApplyDoorAttributes(rom, replay.wram(), out);
1449
1450 if (state.crystal_pegs_swapped) { // Underworld_FlipCrystalPegAttribute
1451 for (size_t i = 0; i < kRoomCollisionBytes; ++i) {
1452 if (maps.attributes[i] == 0x66 || maps.attributes[i] == 0x67) {
1453 maps.attributes[i] ^= 1;
1454 maps.sources[i] = CollisionSource::kCrystalPeg;
1455 }
1456 }
1457 }
1458 return maps;
1459}
1460
1461std::vector<StaircaseSlotResolution> ResolveVanillaStaircaseSlots(
1462 const Rom& rom, const RoomCollisionInput& input,
1463 const UnderworldRoomLoadState& state) {
1464 struct Trigger {
1465 int edge = 0;
1466 int index = 0;
1467 uint8_t edge_type = 0;
1468 };
1469 std::vector<StaircaseSlotResolution> results;
1470 std::vector<Trigger> triggers;
1471 for (size_t i = 0; i < input.objects.size(); ++i) {
1472 const auto& object = input.objects[i];
1473 if (!UsesRoomObjectStream(object))
1474 continue;
1475 int edge_row = object.y();
1476 int index_row = object.y() + 1;
1477 int last_row = object.y() + 3;
1478 bool lower = object.GetLayerValue() == 1;
1479 uint8_t edge_type = 0;
1480 switch (object.id_) {
1481 case 0x12D:
1482 edge_type = 0x26;
1483 break;
1484 case 0x12E:
1485 case 0x12F:
1486 edge_type = 0x26;
1487 edge_row += 3;
1488 index_row += 1;
1489 break;
1490 case 0x138:
1491 case 0x139:
1492 case 0x13A:
1493 case 0x13B:
1494 edge_type = object.id_ < 0x13A ? 0x5E : 0x5F;
1495 --edge_row;
1496 --index_row;
1497 --last_row;
1498 break;
1499 case 0xF9E:
1500 case 0xF9F:
1501 lower = false; // Upper straight writers ignore the stream layer.
1502 [[fallthrough]];
1503 case 0xFA6:
1504 case 0xFA7:
1505 edge_type = 0x38;
1506 break;
1507 case 0xFA0:
1508 case 0xFA1:
1509 lower = false;
1510 [[fallthrough]];
1511 case 0xFA8:
1512 case 0xFA9:
1513 edge_type = 0x39;
1514 edge_row += 3;
1515 index_row += 1;
1516 break;
1517 default:
1518 continue;
1519 }
1521 result.object_index = i;
1522 if (object.GetLayerValue() > 2 || object.x() > 60 || edge_row < 0 ||
1523 last_row >= 64 || index_row >= 64) {
1524 result.status = StaircaseSlotStatus::kOutsideRoom;
1525 }
1526 const int layer = lower ? kCollisionMapTiles : 0;
1527 triggers.push_back({layer + edge_row * 64 + object.x() + 1,
1528 layer + index_row * 64 + object.x() + 1, edge_type});
1529 results.push_back(result);
1530 }
1531 // The position table shares storage with other lists after its four words.
1532 // Do not infer editable destinations from overflow/wrapped indices.
1533 if (results.size() > 4) {
1534 for (auto& result : results)
1535 result.status = StaircaseSlotStatus::kTooManyStairs;
1536 return results;
1537 }
1538 if (results.empty())
1539 return results;
1540 const auto maps = ComputeRoomCollisionMaps(rom, input, state);
1541 for (size_t i = 0; i < results.size(); ++i) {
1542 auto& result = results[i];
1543 if (result.status == StaircaseSlotStatus::kOutsideRoom)
1544 continue;
1545 const auto& t = triggers[i];
1546 bool overlap = false;
1547 for (size_t j = 0; j < results.size(); ++j) {
1548 if (i == j || results[j].status == StaircaseSlotStatus::kOutsideRoom)
1549 continue;
1550 for (int a : {t.edge, t.edge + 1, t.index, t.index + 1}) {
1551 const auto& other = triggers[j];
1552 for (int b : {other.edge, other.edge + 1, other.index, other.index + 1})
1553 overlap |= a == b;
1554 }
1555 }
1556 if (overlap) {
1557 result.status = StaircaseSlotStatus::kOverlappingStairs;
1558 continue;
1559 }
1560 const uint8_t attribute = maps.attributes[t.index];
1561 bool intact = (attribute & 0xF8) == 0x30;
1562 for (int x = 0; x < 2; ++x) {
1563 intact &= maps.sources[t.index + x] == CollisionSource::kStairs &&
1564 maps.sources[t.edge + x] == CollisionSource::kStairs &&
1565 maps.attributes[t.index + x] == attribute &&
1566 maps.attributes[t.edge + x] == t.edge_type;
1567 }
1568 if (intact) {
1569 result.status = StaircaseSlotStatus::kResolved;
1570 result.slot = attribute & 3; // $01C3CA: runtime $0462 & 3.
1571 }
1572 }
1573 return results;
1574}
1575
1577 const RoomTilemaps& tilemaps) {
1578 RoomCollisionInput input;
1579 input.room_id = room.id();
1580 input.blockset = room.blockset();
1581 input.tag1 = static_cast<uint8_t>(room.tag1());
1582 input.tag2 = static_cast<uint8_t>(room.tag2());
1583 input.objects = room.GetTileObjects();
1584 input.doors = room.GetDoors();
1585 input.tilemaps = tilemaps;
1586 return input;
1587}
1588
1592
1593} // 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:52
auto data() const
Definition rom.h:213
auto size() const
Definition rom.h:212
uint8_t block_behavior_layer() const
uint8_t GetLayerValue() const
uint8_t blockset() const
Definition room.h:1015
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< RoomObject > & GetTileObjects() const
Definition room.h:445
int id() const
Definition room.h:1012
void StoreByte(int x, uint8_t value, CollisionSource source)
void Store(int x, uint16_t value, CollisionSource source)
void SetCounters(std::initializer_list< int > counters, uint16_t value)
void AddManipulable(uint16_t type, uint16_t y, bool lower_layer)
FlagResult FlagDoors(uint16_t x, uint16_t direction, uint16_t y)
uint16_t Append(int counter, int list, uint16_t value)
void MarkToggleDoor(int counter, int list, uint16_t position)
void RecordObject(const RoomObject &object, bool lower_layer)
RoomLoadReplay(const Rom &rom, const RoomCollisionInput &input, const UnderworldRoomLoadState &state)
void ForEachEntry(uint16_t &index, uint16_t end, Fn fn)
void ApplyDoorAttributes(const Rom &rom, LowWram &w, AttributeWriter &out)
std::optional< CustomTileTypeTable > DecodeLoadCustomTileTypes(const Rom &rom, uint32_t snes)
void ApplyObjectAttributes(LowWram &w, uint8_t tag1, uint8_t tag2, AttributeWriter &out)
Zelda 3 specific classes and functions.
@ ExplodingWall
Exploding wall.
RoomTilemaps ComposeYazeRoomTilemaps(const Room &room)
constexpr size_t kRoomCollisionBytes
constexpr size_t kTileAttributeTableSize
std::array< uint8_t, kTileAttributeTableSize > LoadUnderworldTileAttributeTable(const Rom &rom, uint8_t blockset)
RoomCollisionMaps ComputeBasicCollisionMaps(const std::array< uint8_t, kTileAttributeTableSize > &table, const RoomTilemaps &tilemaps)
RoomCollisionMaps ComputeRoomCollisionMaps(const Rom &rom, const RoomCollisionInput &input, const UnderworldRoomLoadState &state)
bool UsesRoomObjectStream(const RoomObject &object)
const char * CustomTileTypeSourceName(CustomTileTypeSource source)
CustomTileTypeTable ResolveCustomTileTypeTable(const Rom &rom)
RoomCollisionInput MakeRoomCollisionInput(const Room &room, const RoomTilemaps &tilemaps)
std::vector< StaircaseSlotResolution > ResolveVanillaStaircaseSlots(const Rom &rom, const RoomCollisionInput &input, const UnderworldRoomLoadState &state)
const char * CollisionSourceName(CollisionSource source)
std::vector< RoomObject > objects
std::vector< Room::Door > doors
std::array< CollisionSource, kRoomCollisionBytes > sources
std::array< uint8_t, kRoomCollisionBytes > attributes
Represents a door in a dungeon room.
Definition room.h:339
uint8_t byte1
Original ROM byte 1 (position data)
Definition room.h:344
uint8_t byte2
Original ROM byte 2 (type + direction)
Definition room.h:345
std::optional< uint16_t > door_open_mask