yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
special_routines.cc
Go to the documentation of this file.
1#include "special_routines.h"
2
3#include <algorithm>
4#include <array>
5
6#include "core/features.h"
7#include "util/log.h"
14
15namespace yaze {
16namespace zelda3 {
17namespace draw_routines {
18
19namespace {
20
21constexpr int kThievesTownEastAtticRoomId = 0x65;
22
23const gfx::TileInfo& TileAtWrapped(std::span<const gfx::TileInfo> tiles,
24 size_t index) {
25 return tiles[index % tiles.size()];
26}
27
28void DrawColumnMajor(gfx::BackgroundBuffer& bg, int base_x, int base_y, int w,
29 int h, std::span<const gfx::TileInfo> tiles,
30 size_t start_index = 0) {
31 if (tiles.empty())
32 return;
33
34 size_t index = start_index;
35 for (int x = 0; x < w; ++x) {
36 for (int y = 0; y < h; ++y) {
37 DrawRoutineUtils::WriteTile8(bg, base_x + x, base_y + y,
38 TileAtWrapped(tiles, index++));
39 }
40 }
41}
42
43void DrawRowMajor(gfx::BackgroundBuffer& bg, int base_x, int base_y, int w,
44 int h, std::span<const gfx::TileInfo> tiles,
45 size_t start_index = 0) {
46 if (tiles.empty())
47 return;
48
49 size_t index = start_index;
50 for (int y = 0; y < h; ++y) {
51 for (int x = 0; x < w; ++x) {
52 DrawRoutineUtils::WriteTile8(bg, base_x + x, base_y + y,
53 TileAtWrapped(tiles, index++));
54 }
55 }
56}
57
58void Draw1x3NRightwards(const DrawContext& ctx, int columns, size_t start_index,
59 int y_offset = 0) {
60 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_ + y_offset,
61 columns, 3, ctx.tiles, start_index);
62}
63
64void DrawNx4(const DrawContext& ctx, int columns, size_t start_index) {
65 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, columns, 4,
66 ctx.tiles, start_index);
67}
68
69void Draw1x5Column(const DrawContext& ctx, int x_offset, size_t start_index) {
70 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + x_offset, ctx.object.y_, 1, 5,
71 ctx.tiles, start_index);
72}
73
74void Draw4x4ColumnMajor(const DrawContext& ctx, int x_offset, int y_offset,
75 size_t start_index) {
76 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + x_offset,
77 ctx.object.y_ + y_offset, 4, 4, ctx.tiles, start_index);
78}
79
81 if (ctx.tiles.size() < 64)
82 return;
83
84 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/0, /*start_index=*/0);
85 Draw4x4ColumnMajor(ctx, /*x_offset=*/4, /*y_offset=*/0, /*start_index=*/16);
86 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/4, /*start_index=*/32);
87 Draw4x4ColumnMajor(ctx, /*x_offset=*/4, /*y_offset=*/4, /*start_index=*/48);
88}
89
91 std::span<const gfx::TileInfo> tiles) {
92 DrawColumnMajor(bg, object.x_, object.y_, 4, 4, tiles);
93}
94
95void DrawMany32x32Block(gfx::BackgroundBuffer& bg, int base_x, int base_y,
96 std::span<const gfx::TileInfo> tiles) {
97 // USDASM RoomDraw_A_Many32x32Blocks ($01:8A44) writes tiles 0..3 through
98 // row-0 tilemap pointers and tiles 4..7 through row-1 pointers, then repeats
99 // the same 4x2 stamp two tile rows lower.
100 DrawRowMajor(bg, base_x, base_y, /*w=*/4, /*h=*/2, tiles);
101 DrawRowMajor(bg, base_x, base_y + 2, /*w=*/4, /*h=*/2, tiles);
102}
103
105 // ASM: RoomDraw_WaterHopStairs_A ($019B1E) falls through PortraitOfMario's
106 // single-pass Downwards4x2VariableSpacing path, which is a fixed 4x2
107 // ROW-MAJOR stamp on the active layer.
108 if (ctx.tiles.size() < 8)
109 return;
110
111 DrawRowMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 4, 2, ctx.tiles);
112}
113
115 // ASM: RoomDraw_WaterHopStairs_B ($01A3AE) writes the same 4x2 ROW-MAJOR
116 // footprint to both BG tilemaps. The outer draw framework handles the dual
117 // pass for routines marked draws_to_both_bgs.
119}
120
122 // ASM: RoomDraw_DamFloodGate ($019BF8) stamps a 10x4 column-major tile block
123 // from the closed tile span by default, and swaps to the alternate water-open
124 // tile span when the overworld event is active.
125 constexpr size_t kSpanTiles = 40;
126 const bool use_open_tiles = ctx.state != nullptr &&
127 ctx.state->IsDamFloodgateOpen(ctx.room_id) &&
128 ctx.tiles.size() >= kSpanTiles * 2;
129 const size_t start_index = use_open_tiles ? kSpanTiles : 0;
130 if (ctx.tiles.size() < start_index + kSpanTiles)
131 return;
132
133 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 10, 4, ctx.tiles,
134 start_index);
135}
136
137void WritePlatformTile(const DrawContext& ctx, int dx, int dy, size_t index) {
138 if (index >= ctx.tiles.size())
139 return;
140
142 ctx.object.y_ + dy, ctx.tiles[index]);
143}
144
145void DrawPlatform1x3Rightwards(const DrawContext& ctx, int dx, int dy,
146 size_t start_index, int columns) {
147 for (int x = 0; x < columns; ++x) {
148 for (int y = 0; y < 3; ++y) {
149 WritePlatformTile(ctx, dx + x, dy + y, start_index + x * 3 + y);
150 }
151 }
152}
153
154void DrawPlatform2x2(const DrawContext& ctx, int dx, int dy,
155 size_t start_index) {
156 WritePlatformTile(ctx, dx + 0, dy + 0, start_index + 0);
157 WritePlatformTile(ctx, dx + 0, dy + 1, start_index + 1);
158 WritePlatformTile(ctx, dx + 1, dy + 0, start_index + 2);
159 WritePlatformTile(ctx, dx + 1, dy + 1, start_index + 3);
160}
161
162void DrawPlatform3x2(const DrawContext& ctx, int dx, int dy,
163 size_t start_index) {
164 WritePlatformTile(ctx, dx + 0, dy + 0, start_index + 0);
165 WritePlatformTile(ctx, dx + 1, dy + 0, start_index + 1);
166 WritePlatformTile(ctx, dx + 2, dy + 0, start_index + 2);
167 WritePlatformTile(ctx, dx + 0, dy + 1, start_index + 3);
168 WritePlatformTile(ctx, dx + 1, dy + 1, start_index + 4);
169 WritePlatformTile(ctx, dx + 2, dy + 1, start_index + 5);
170}
171
173constexpr std::array<uint16_t, 3> kMovingWallFallbackFillWords = {
174 0x3C15, 0x3C15, 0x3C15};
175
176std::array<gfx::TileInfo, 3> LoadMovingWallFillTiles(const DrawContext& ctx) {
177 std::array<gfx::TileInfo, 3> fill_tiles;
178 for (size_t i = 0; i < fill_tiles.size(); ++i) {
179 uint16_t word = kMovingWallFallbackFillWords[i];
180 if (ctx.rom != nullptr) {
181 const auto word_or = ctx.rom->ReadWord(kMovingWallFillDataOffset +
182 static_cast<int>(i * 2));
183 if (word_or.ok()) {
184 word = *word_or;
185 }
186 }
187 fill_tiles[i] = gfx::WordToTileInfo(word);
188 }
189 return fill_tiles;
190}
191
192void DrawMovingWallFill(const DrawContext& ctx, int start_x, int column_count,
193 int height) {
194 const auto fill_tiles = LoadMovingWallFillTiles(ctx);
195 for (int x = 0; x < column_count; ++x) {
196 DrawRoutineUtils::WriteTile8(ctx.target_bg, start_x + x, ctx.object.y_,
197 fill_tiles[0]);
198 for (int y = 1; y < height - 1; ++y) {
200 ctx.object.y_ + y, fill_tiles[1]);
201 }
203 ctx.object.y_ + height - 1, fill_tiles[2]);
204 }
205}
206
207void DrawMovingWallPlatform(const DrawContext& ctx, int direction) {
208 DrawPlatform1x3Rightwards(ctx, /*dx=*/0, /*dy=*/0, /*start_index=*/0,
209 /*columns=*/3);
210 for (int i = 0; i < direction; ++i) {
211 DrawPlatform3x2(ctx, /*dx=*/0, /*dy=*/3 + i * 2,
212 /*start_index=*/9);
213 }
214 // The ASM advances six words past the vertical pattern before stamping the
215 // second 3x3 corner (payload slots 15..23).
216 DrawPlatform1x3Rightwards(ctx, /*dx=*/0, /*dy=*/3 + direction * 2,
217 /*start_index=*/15, /*columns=*/3);
218}
219
221 size_t start_index, int fill_width) {
222 WritePlatformTile(ctx, /*dx=*/0, dy, start_index + 0);
223
224 for (int i = 0; i < fill_width; ++i) {
225 WritePlatformTile(ctx, /*dx=*/1 + i, dy, start_index + 3);
226 }
227
228 const int left_cap_x = 1 + fill_width;
229 WritePlatformTile(ctx, left_cap_x, dy, start_index + 6);
230
231 for (int i = 0; i < 4; ++i) {
232 WritePlatformTile(ctx, left_cap_x + 1 + i, dy, start_index + 9);
233 }
234
235 const int right_cap_x = left_cap_x + 5;
236 WritePlatformTile(ctx, right_cap_x, dy, start_index + 12);
237
238 for (int i = 0; i < fill_width; ++i) {
239 WritePlatformTile(ctx, right_cap_x + 1 + i, dy, start_index + 15);
240 }
241
242 WritePlatformTile(ctx, right_cap_x + 1 + fill_width, dy, start_index + 18);
243}
244
245} // namespace
246
247void DrawChest(const DrawContext& ctx, int chest_index) {
248 // Routine 39 is used only by stateful Chest (F99) and fixed OpenChest
249 // (F9A). BigChest uses its separate 4x3 routine.
250 bool is_open = ctx.object.id_ == 0xF9A;
251 if (!is_open && ctx.state) {
252 is_open = ctx.state->IsChestOpen(ctx.room_id, chest_index);
253 }
254
255 // Preserve the legacy subtype-1 0xF8-0xFD family handled by this registry
256 // routine. The subtype-3 chest opcodes below are always 2x2.
257 if (ctx.object.id_ < 0xF80 && ctx.tiles.size() >= 16) {
258 size_t start_index = is_open && ctx.tiles.size() >= 32 ? 16 : 0;
259 for (int x = 0; x < 4; ++x) {
260 for (int y = 0; y < 4; ++y) {
262 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_ + y,
263 ctx.tiles[start_index + static_cast<size_t>(x * 4 + y)]);
264 }
265 }
266 return;
267 }
268
269 size_t start_index = 0;
270 if (is_open && ctx.object.id_ == 0xF99 && ctx.tiles.size() >= 8) {
271 start_index = 4;
272 }
273 if (ctx.tiles.size() < start_index + 4) {
274 return;
275 }
276
278 ctx.tiles[start_index]);
280 ctx.tiles[start_index + 1]);
282 ctx.tiles[start_index + 2]);
284 ctx.object.y_ + 1, ctx.tiles[start_index + 3]);
285}
286
287void DrawNothing([[maybe_unused]] const DrawContext& ctx) {
288 // Intentionally empty - represents invisible logic objects or placeholders
289 // ASM: RoomDraw_Nothing_A ($0190F2), RoomDraw_Nothing_B ($01932E), etc.
290 // These routines typically just RTS.
291}
292
293void CustomDraw(const DrawContext& ctx) {
294 // Pattern: Custom draw routine (objects 0x31-0x32)
295 // When custom objects are enabled, load tile data from external binary files
296 // managed by CustomObjectManager. Each binary encodes SNES tilemap entries
297 // with relative x/y positions computed from the buffer stride layout.
298
299 if (!core::FeatureFlags::get().kEnableCustomObjects) {
300 // Feature disabled: fall back to vanilla 1x1 draw from ROM tile span
301 if (ctx.tiles.size() >= 1) {
303 ctx.tiles[0]);
304 }
305 return;
306 }
307
308 // Look up the custom object by ID and subtype.
309 // ctx.object.id_ is 0x31 or 0x32; ctx.object.size_ encodes the subtype.
311 ctx.object.size_);
312
313 if (!result.ok() || !result.value() || result.value()->IsEmpty()) {
314 // Custom object not found or empty: fall back to 1x1 draw
315 if (ctx.tiles.size() >= 1) {
317 ctx.tiles[0]);
318 }
319 return;
320 }
321
322 const auto& custom_obj = *result.value();
323
324 for (const auto& entry : custom_obj.tiles) {
325 // Convert SNES tilemap word (vhopppcc cccccccc) to TileInfo.
326 // Low byte = entry.tile_data & 0xFF, high byte = (entry.tile_data >> 8).
327 uint8_t lo = static_cast<uint8_t>(entry.tile_data & 0xFF);
328 uint8_t hi = static_cast<uint8_t>((entry.tile_data >> 8) & 0xFF);
329 gfx::TileInfo tile_info(lo, hi);
330
331 // rel_x/rel_y are already decoded as object-relative coordinates from the
332 // binary stream's buffer position arithmetic; preserve those offsets.
333 int draw_x = ctx.object.x_ + entry.rel_x;
334 int draw_y = ctx.object.y_ + entry.rel_y;
335
336 DrawRoutineUtils::WriteTile8(ctx.target_bg, draw_x, draw_y, tile_info);
337 }
338}
339
341 // Pattern: Door switcher (object 0x35)
342 // Special door logic
343 // Check state to decide graphic
344 int tile_index = 0;
345 if (ctx.state && ctx.state->IsDoorSwitchActive(ctx.room_id)) {
346 // Use active tile if available (assuming 2nd tile is active state)
347 if (ctx.tiles.size() >= 2) {
348 tile_index = 1;
349 }
350 }
351
352 if (ctx.tiles.size() > static_cast<size_t>(tile_index)) {
354 ctx.tiles[tile_index]);
355 }
356}
357
358void DrawSomariaLine(const DrawContext& ctx) {
359 // USDASM RoomDraw_SomariaLine writes one object-data word at the object's
360 // anchor. The apparent path is assembled from separate subtype-3 objects;
361 // the object's size bits do not extend an individual piece.
362 if (ctx.tiles.empty()) {
363 return;
364 }
365
367 ctx.tiles.front());
368}
369
370void DrawWaterFace(const DrawContext& ctx) {
371 // Legacy 2x2 helper used by older corner-family routines. The real subtype-3
372 // water-face objects are handled by DrawEmptyWaterFace /
373 // DrawSpittingWaterFace / DrawDrenchingWaterFace below.
374 if (ctx.tiles.size() >= 4) {
376 ctx.tiles[0]); // col 0, row 0
378 ctx.object.y_ + 1,
379 ctx.tiles[1]); // col 0, row 1
381 ctx.object.y_,
382 ctx.tiles[2]); // col 1, row 0
384 ctx.object.y_ + 1,
385 ctx.tiles[3]); // col 1, row 1
386 }
387}
388
389void DrawLargeCanvasObject(const DrawContext& ctx, int width, int height) {
390 // Generic large object drawer
391 if (ctx.tiles.size() >= static_cast<size_t>(width * height)) {
392 for (int y = 0; y < height; ++y) {
393 for (int x = 0; x < width; ++x) {
395 ctx.object.y_ + y,
396 ctx.tiles[y * width + x]);
397 }
398 }
399 }
400}
401
402void DrawBed4x5(const DrawContext& ctx) {
403 // ASM: RoomDraw_Bed4x5 ($019AEE) writes 4 columns per row, 5 rows total.
404 DrawRowMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 4, 5, ctx.tiles);
405}
406
408 // ASM: RoomDraw_DrawRightwards3x6 ($019B50) is RoomDraw_1x3N_rightwards with
409 // A=6 -> 6 columns x 3 rows, column-major.
410 Draw1x3NRightwards(ctx, /*columns=*/6, /*start_index=*/0);
411}
412
413void DrawUtility6x3(const DrawContext& ctx) {
414 // ASM: RoomDraw_Utility6x3 ($019A0C) is also 1x3N_rightwards with A=6.
415 Draw1x3NRightwards(ctx, /*columns=*/6, /*start_index=*/0);
416}
417
419 // ASM: RoomDraw_BigWallDecor ($019A06) calls
420 // RoomDraw_1x3N_rightwards with A=8. The encoded size is not consulted.
421 Draw1x3NRightwards(ctx, /*columns=*/8, /*start_index=*/0);
422}
423
424void DrawTableBowl(const DrawContext& ctx) {
425 // ASM: RoomDraw_TableBowl ($019D6C) loads A=2 and falls through to
426 // RoomDraw_WaterHoldingObject, which writes four tiles per row.
427 if (ctx.tiles.size() < 8)
428 return;
429
430 DrawRowMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 4, 2, ctx.tiles);
431}
432
434 // ASM: RoomDraw_SmithyFurnace ($019A66) loads A=6 before jumping to
435 // RoomDraw_SomeBigDecors, which writes six tiles per row for eight rows.
436 if (ctx.tiles.size() < 48)
437 return;
438
439 DrawRowMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 6, 8, ctx.tiles);
440}
441
442void DrawBigGrayRock(const DrawContext& ctx) {
443 // ASM: RoomDraw_BigGrayRock ($01B310) draws four 2x2 quadrants through
444 // RoomDraw_Rightwards2x2. Visually that is two 4x2 column-major bands:
445 // 0 2 4 6
446 // 1 3 5 7
447 // 8 10 12 14
448 // 9 11 13 15
449 // The liftable-rock bookkeeping in DrawBigGraySegment is runtime-only and
450 // intentionally not modeled by this visual draw routine.
451 if (ctx.tiles.size() < 16)
452 return;
453
454 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 4, 2, ctx.tiles,
455 /*start_index=*/0);
456 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_ + 2, 4, 2,
457 ctx.tiles, /*start_index=*/8);
458}
459
461 // ASM: RoomDraw_AgahnimsAltar ($019E30) reads six 14-tile columns from
462 // obj1B4A, duplicates the second column, then mirrors the source columns
463 // across a 14x14 destination. Columns 7..12 toggle the source H-flip with
464 // EOR #$4000; column 13 forces it with ORA #$4000.
465 if (ctx.tiles.size() < 84)
466 return;
467
468 constexpr std::array<int, 14> kSourceColumns = {
469 0, 1, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1, 0,
470 };
471 for (int y = 0; y < 14; ++y) {
472 for (int x = 0; x < 14; ++x) {
473 gfx::TileInfo tile = ctx.tiles[kSourceColumns[x] * 14 + y];
474 if (x >= 7 && x <= 12) {
476 } else if (x == 13) {
477 tile.horizontal_mirror_ = true;
478 }
480 ctx.object.y_ + y, tile);
481 }
482 }
483}
484
486 // ASM: RoomDraw_FortuneTellerRoom ($01A095) reads all 26 words from
487 // obj202E and writes 116 tiles into a sparse, fixed 14x14 BG1 footprint.
488 if (ctx.tiles.size() < 26)
489 return;
490
491 auto write = [&](int dx, int dy, size_t tile_index,
492 bool force_horizontal_mirror = false,
493 bool toggle_horizontal_mirror = false) {
494 gfx::TileInfo tile = ctx.tiles[tile_index];
495 if (force_horizontal_mirror) {
496 tile.horizontal_mirror_ = true;
497 } else if (toggle_horizontal_mirror) {
499 }
501 ctx.object.y_ + dy, tile);
502 };
503
504 // Six repeated two-column roof sections across rows 0..2.
505 for (int section = 0; section < 6; ++section) {
506 const int x = section * 2;
507 write(x + 1, 0, 0);
508 write(x + 2, 0, 0);
509 write(x + 1, 1, 0);
510 write(x + 2, 1, 0);
511 write(x + 1, 2, 1);
512 write(x + 2, 2, 1, /*force_horizontal_mirror=*/true);
513 }
514
515 // Three complete mirrored rows. The left/right wall tile advances through
516 // words 2..4 while the center tile advances through words 5..7.
517 for (int row = 0; row < 3; ++row) {
518 const size_t wall_tile = static_cast<size_t>(2 + row);
519 const size_t center_tile = static_cast<size_t>(5 + row);
520 const int y = 3 + row;
521
522 for (int x : {0, 2, 10, 12}) {
523 write(x, y, wall_tile);
524 }
525 for (int x : {1, 3, 11, 13}) {
526 write(x, y, wall_tile, /*force_horizontal_mirror=*/true);
527 }
528 for (int x : {4, 6, 8}) {
529 write(x, y, center_tile);
530 }
531 for (int x : {5, 7, 9}) {
532 write(x, y, center_tile, /*force_horizontal_mirror=*/true);
533 }
534 }
535
536 // Outer caps around the roof.
537 write(0, 0, 8);
538 write(0, 1, 8);
539 write(13, 0, 8, /*force_horizontal_mirror=*/true);
540 write(13, 1, 8, /*force_horizontal_mirror=*/true);
541 write(0, 2, 9);
542 write(13, 2, 9, /*force_horizontal_mirror=*/true);
543
544 // Four sparse rows at the bottom. Each source word is mirrored with EOR
545 // #$4000, so an existing H-flip must be toggled rather than forced on.
546 for (int row = 0; row < 4; ++row) {
547 const int y = 10 + row;
548 write(3, y, static_cast<size_t>(10 + row));
549 write(10, y, static_cast<size_t>(10 + row),
550 /*force_horizontal_mirror=*/false,
551 /*toggle_horizontal_mirror=*/true);
552 write(4, y, static_cast<size_t>(14 + row));
553 write(9, y, static_cast<size_t>(14 + row),
554 /*force_horizontal_mirror=*/false,
555 /*toggle_horizontal_mirror=*/true);
556 write(5, y, static_cast<size_t>(18 + row));
557 write(8, y, static_cast<size_t>(18 + row),
558 /*force_horizontal_mirror=*/false,
559 /*toggle_horizontal_mirror=*/true);
560 write(6, y, static_cast<size_t>(22 + row));
561 write(7, y, static_cast<size_t>(22 + row),
562 /*force_horizontal_mirror=*/false,
563 /*toggle_horizontal_mirror=*/true);
564 }
565}
566
568 // USDASM's final subtype-2 entry is object 0x13F (table index 0x3F), not a
569 // subtype-3 0x2xx object. RoomDraw_MagicBatAltar ($019A12) advances through
570 // eight source columns, reading seven words down each column from obj2086.
571 if (ctx.tiles.size() < 56)
572 return;
573
574 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 8, 7, ctx.tiles);
575}
576
577void DrawUtility3x5(const DrawContext& ctx) {
578 // ASM: RoomDraw_Utility3x5 ($01A194) uses:
579 // - top row: tiles 0..2
580 // - middle 3 rows: tiles 3..5 repeated per row
581 // - bottom row: tiles 6..8
582 if (ctx.tiles.empty())
583 return;
584
585 // Top row.
586 for (int x = 0; x < 3; ++x) {
588 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_,
589 TileAtWrapped(ctx.tiles, static_cast<size_t>(x)));
590 }
591
592 // Middle rows (rows 1..3) reuse tiles 3..5.
593 for (int y = 1; y <= 3; ++y) {
594 for (int x = 0; x < 3; ++x) {
596 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_ + y,
597 TileAtWrapped(ctx.tiles, static_cast<size_t>(3 + x)));
598 }
599 }
600
601 // Bottom row.
602 for (int x = 0; x < 3; ++x) {
604 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_ + 4,
605 TileAtWrapped(ctx.tiles, static_cast<size_t>(6 + x)));
606 }
607}
608
610 // ASM: RoomDraw_VerticalTurtleRockPipe ($019A90)
611 // Two stacked 4x3 sections: first uses tiles 0..11, second 12..23.
612 if (ctx.tiles.empty())
613 return;
614 Draw1x3NRightwards(ctx, /*columns=*/4, /*start_index=*/0, /*y_offset=*/0);
615 Draw1x3NRightwards(ctx, /*columns=*/4, /*start_index=*/12, /*y_offset=*/3);
616}
617
619 // ASM: RoomDraw_HorizontalTurtleRockPipe ($019AA3) -> RoomDraw_Nx4 with A=6.
620 DrawNx4(ctx, /*columns=*/6, /*start_index=*/0);
621}
622
624 // ASM: RoomDraw_LightBeamOnFloor ($01A7B6)
625 // Draw three 4x4 blocks at y offsets 0, +2, +6. The middle block resets X
626 // to obj2376 and reuses tiles 0..15; the bottom block uses obj2396 at
627 // tiles 16..31.
628 if (ctx.tiles.size() < 32)
629 return;
630 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/0, /*start_index=*/0);
631 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/2, /*start_index=*/0);
632 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/6, /*start_index=*/16);
633}
634
636 // ASM: RoomDraw_BigLightBeamOnFloor ($01A7D3) reads the persisted
637 // bombed-floor flag for fixed room 0x065 ($7EF0CA & $0100), then falls
638 // through to RoomDraw_FloorLight when it is active. Do not use ctx.room_id.
639 // Null state is reserved for object/geometry previews, which keep the beam
640 // visible and measurable.
641 if (ctx.state != nullptr &&
642 !ctx.state->IsFloorBombable(kThievesTownEastAtticRoomId)) {
643 return;
644 }
645 DrawFloorLightGrid(ctx);
646}
647
648void DrawFloorLight(const DrawContext& ctx) {
649 // ASM: RoomDraw_FloorLight ($01A7DC) is unconditional for object 0x274.
650 DrawFloorLightGrid(ctx);
651}
652
654 // ASM-mapped objects route through RoomDraw_4x4.
655 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/0, /*start_index=*/0);
656}
657
659 // USDASM RoomDraw_VitreousGooDamage ($01A809) invokes
660 // RoomDraw_A_Many32x32Blocks twice with A=5, moving the second call four
661 // tile rows down. The routine reads neither object size nor room state and
662 // writes through the currently selected layer's tilemap pointers.
663 if (ctx.tiles.size() < 8)
664 return;
665
666 for (int block_y = 0; block_y < 2; ++block_y) {
667 for (int block_x = 0; block_x < 5; ++block_x) {
668 DrawMany32x32Block(ctx.target_bg, ctx.object.x_ + block_x * 4,
669 ctx.object.y_ + block_y * 4, ctx.tiles);
670 }
671 }
672}
673
675 // ASM: RoomDraw_SolidWallDecor3x4 ($0199EC) -> RoomDraw_Nx4 with A=3.
676 DrawNx4(ctx, /*columns=*/3, /*start_index=*/0);
677}
678
680 // ASM: RoomDraw_ArcheryGameTargetDoor ($01A7A3)
681 // Two 3x3 sections stacked vertically.
682 if (ctx.tiles.empty())
683 return;
684 Draw1x3NRightwards(ctx, /*columns=*/3, /*start_index=*/0, /*y_offset=*/0);
685 Draw1x3NRightwards(ctx, /*columns=*/3, /*start_index=*/9, /*y_offset=*/3);
686}
687
689 // ASM: RoomDraw_GanonTriforceFloorDecor ($01A7F0)
690 // Top block uses 0..15 at +2 X, then two bottom 4x4 blocks use 16..31.
691 if (ctx.tiles.empty())
692 return;
693
694 Draw4x4ColumnMajor(ctx, /*x_offset=*/2, /*y_offset=*/0, /*start_index=*/0);
695 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/4, /*start_index=*/16);
696 Draw4x4ColumnMajor(ctx, /*x_offset=*/4, /*y_offset=*/4, /*start_index=*/16);
697}
698
699void DrawSingle2x2(const DrawContext& ctx) {
700 // ASM: RoomDraw_Single2x2 ($019A8D) -> RoomDraw_Downwards2x2
701 // Single 2x2 in column-major order.
702 if (ctx.tiles.size() < 4)
703 return;
704
706 ctx.tiles[0]);
708 ctx.tiles[1]);
710 ctx.tiles[2]);
712 ctx.object.y_ + 1, ctx.tiles[3]);
713}
714
715void DrawSingle4x4(const DrawContext& ctx) {
716 // ASM: RoomDraw_4x4 ($0197ED), column-major 4x4 block (16 tiles).
717 if (ctx.tiles.size() < 16)
718 return;
719
720 int tid = 0;
721 for (int x = 0; x < 4; ++x) {
722 for (int y = 0; y < 4; ++y) {
724 ctx.object.y_ + y, ctx.tiles[tid++]);
725 }
726 }
727}
728
729void DrawSingle4x3(const DrawContext& ctx) {
730 // ASM: RoomDraw_TableRock4x3 ($0199E6), column-major 4x3 block (12 tiles).
731 if (ctx.tiles.size() < 12)
732 return;
733
734 int tid = 0;
735 for (int x = 0; x < 4; ++x) {
736 for (int y = 0; y < 3; ++y) {
738 ctx.object.y_ + y, ctx.tiles[tid++]);
739 }
740 }
741}
742
743void DrawRupeeFloor(const DrawContext& ctx) {
744 // USDASM RoomDraw_RupeeFloor ($01:9AA9-$01:9AED) exits when the
745 // current-room $0402 & $1000 event is set. State-free selector/geometry
746 // previews remain visible.
747 if ((ctx.state != nullptr && ctx.state->IsRupeeFloorCleared(ctx.room_id)) ||
748 ctx.tiles.size() < 2) {
749 return;
750 }
751
752 constexpr std::array<int, 3> kTopRows = {0, 3, 6};
753 constexpr std::array<int, 3> kBottomRows = {1, 4, 7};
754 for (int col = 0; col < 3; ++col) {
755 const int x = ctx.object.x_ + col * 2;
756 for (int row : kTopRows) {
758 ctx.tiles[0]);
759 }
760 for (int row : kBottomRows) {
762 ctx.tiles[1]);
763 }
764 }
765}
766
767void DrawActual4x4(const DrawContext& ctx) {
768 // ASM: RoomDraw_4x4 ($0197ED), used for true 4x4 tile8 objects.
769 DrawSingle4x4(ctx);
770}
771
772void DrawWaterfall47(const DrawContext& ctx) {
773 // ASM: RoomDraw_Waterfall47 ($019466)
774 // First 1x5 column from 0..4, middle columns from 5..9, final from 10..14.
775 if (ctx.tiles.empty())
776 return;
777
778 const int size = ctx.object.size_ & 0x0F;
779 const int middle_columns = (size + 1) * 2; // ASL $B2
780
781 Draw1x5Column(ctx, /*x_offset=*/0, /*start_index=*/0);
782 for (int i = 0; i < middle_columns; ++i) {
783 Draw1x5Column(ctx, /*x_offset=*/1 + i, /*start_index=*/5);
784 }
785 Draw1x5Column(ctx, /*x_offset=*/1 + middle_columns, /*start_index=*/10);
786}
787
788void DrawWaterfall48(const DrawContext& ctx) {
789 // ASM: RoomDraw_Waterfall48 ($019488)
790 // First 1x3 column from 0..2, middle columns from 3..5, final from 6..8.
791 if (ctx.tiles.empty())
792 return;
793
794 const int size = ctx.object.size_ & 0x0F;
795 const int middle_columns = (size + 1) * 2; // ASL $B2
796
797 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, /*w=*/1, /*h=*/3,
798 ctx.tiles, /*start_index=*/0);
799 for (int i = 0; i < middle_columns; ++i) {
800 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + 1 + i, ctx.object.y_,
801 /*w=*/1, /*h=*/3, ctx.tiles, /*start_index=*/3);
802 }
803 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + 1 + middle_columns,
804 ctx.object.y_, /*w=*/1, /*h=*/3, ctx.tiles,
805 /*start_index=*/6);
806}
807
808// ============================================================================
809// SuperSquare Routines (Phase 4)
810// ============================================================================
811// A "SuperSquare" is a 16x16 tile area (4 rows of 4 tiles each).
812// These routines draw floor patterns that repeat in super square units.
813
815 // ASM: RoomDraw_4x4BlocksIn4x4SuperSquare ($018B94)
816 // Draws solid 4x4 blocks using a single tile, repeated in a grid pattern.
817 // Size determines number of super squares in X and Y directions.
818 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
819 int size_y = (ctx.object.size_ & 0x03) + 1;
820
821 LOG_DEBUG("DrawRoutines",
822 "Draw4x4BlocksIn4x4SuperSquare: obj=0x%03X pos=(%d,%d) size=0x%02X "
823 "tiles=%zu size_x=%d size_y=%d",
824 ctx.object.id_, ctx.object.x_, ctx.object.y_, ctx.object.size_,
825 ctx.tiles.size(), size_x, size_y);
826
827 if (ctx.tiles.empty()) {
828 LOG_DEBUG("DrawRoutines",
829 "Draw4x4BlocksIn4x4SuperSquare: SKIPPING - no tiles loaded!");
830 return;
831 }
832
833 // Use first tile for all blocks
834 const auto& tile = ctx.tiles[0];
835 LOG_DEBUG("DrawRoutines",
836 "Draw4x4BlocksIn4x4SuperSquare: tile[0] id=%d palette=%d", tile.id_,
837 tile.palette_);
838
839 for (int sy = 0; sy < size_y; ++sy) {
840 for (int sx = 0; sx < size_x; ++sx) {
841 // Each super square is 4x4 tiles
842 int base_x = ctx.object.x_ + (sx * 4);
843 int base_y = ctx.object.y_ + (sy * 4);
844
845 // Draw 4x4 block with same tile
846 for (int y = 0; y < 4; ++y) {
847 for (int x = 0; x < 4; ++x) {
848 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
849 tile);
850 }
851 }
852 }
853 }
854}
855
857 // ASM: RoomDraw_3x3FloorIn4x4SuperSquare ($018D8A)
858 // Draws 3x3 floor patterns within super square units.
859 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
860 int size_y = (ctx.object.size_ & 0x03) + 1;
861
862 if (ctx.tiles.empty())
863 return;
864
865 const auto& tile = ctx.tiles[0];
866 for (int sy = 0; sy < size_y; ++sy) {
867 for (int sx = 0; sx < size_x; ++sx) {
868 int base_x = ctx.object.x_ + (sx * 3);
869 int base_y = ctx.object.y_ + (sy * 3);
870
871 for (int y = 0; y < 3; ++y) {
872 for (int x = 0; x < 3; ++x) {
873 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
874 tile);
875 }
876 }
877 }
878 }
879}
880
882 // ASM: RoomDraw_4x4FloorIn4x4SuperSquare ($018FA5)
883 // Most common floor pattern - draws 4x4 floor tiles in super square units.
884 // Uses RoomDraw_A_Many32x32Blocks internally in assembly.
885 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
886 int size_y = (ctx.object.size_ & 0x03) + 1;
887
888 if (ctx.tiles.empty())
889 return;
890 if (ctx.tiles.size() < 8) {
891 // Some hacks provide abbreviated tile payloads for these objects.
892 // Fall back to a visible fill instead of silently skipping draw.
894 return;
895 }
896
897 for (int sy = 0; sy < size_y; ++sy) {
898 for (int sx = 0; sx < size_x; ++sx) {
899 int base_x = ctx.object.x_ + (sx * 4);
900 int base_y = ctx.object.y_ + (sy * 4);
901
902 DrawMany32x32Block(ctx.target_bg, base_x, base_y, ctx.tiles);
903 }
904 }
905}
906
908 // ASM: RoomDraw_4x4FloorOneIn4x4SuperSquare ($018FA2)
909 // Single 4x4 floor pattern (starts at different tile offset in assembly).
910 // For our purposes, same as 4x4FloorIn4x4SuperSquare with offset tiles.
911 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
912 int size_y = (ctx.object.size_ & 0x03) + 1;
913
914 if (ctx.tiles.size() < 8) {
916 return;
917 }
918
919 for (int sy = 0; sy < size_y; ++sy) {
920 for (int sx = 0; sx < size_x; ++sx) {
921 int base_x = ctx.object.x_ + (sx * 4);
922 int base_y = ctx.object.y_ + (sy * 4);
923
924 DrawMany32x32Block(ctx.target_bg, base_x, base_y, ctx.tiles);
925 }
926 }
927}
928
930 // ASM: RoomDraw_4x4FloorTwoIn4x4SuperSquare ($018F9D)
931 // Two 4x4 floor patterns (uses $0490 offset in assembly).
932 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
933 int size_y = (ctx.object.size_ & 0x03) + 1;
934
935 if (ctx.tiles.size() < 8) {
937 return;
938 }
939
940 for (int sy = 0; sy < size_y; ++sy) {
941 for (int sx = 0; sx < size_x; ++sx) {
942 int base_x = ctx.object.x_ + (sx * 4);
943 int base_y = ctx.object.y_ + (sy * 4);
944
945 DrawMany32x32Block(ctx.target_bg, base_x, base_y, ctx.tiles);
946 }
947 }
948}
949
951 // ASM: Object 0xA4 - Big hole pattern
952 // Draws a rectangular hole with border tiles using Size as the expansion.
953 int size = ctx.object.size_ & 0x0F;
954
955 if (ctx.tiles.size() < 24)
956 return;
957
958 int base_x = ctx.object.x_;
959 int base_y = ctx.object.y_;
960 int max = size + 3; // Bottom/right edge offset
961
962 // Corners
963 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y, ctx.tiles[8]);
964 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + max, base_y,
965 ctx.tiles[14]);
966 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y + max,
967 ctx.tiles[17]);
968 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + max, base_y + max,
969 ctx.tiles[23]);
970
971 // Edges and interior
972 for (int xx = 1; xx < max; ++xx) {
973 for (int yy = 1; yy < max; ++yy) {
974 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + xx, base_y + yy,
975 ctx.tiles[0]);
976 }
977 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + xx, base_y,
978 ctx.tiles[10]);
979 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + xx, base_y + max,
980 ctx.tiles[19]);
981 }
982
983 for (int yy = 1; yy < max; ++yy) {
984 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y + yy,
985 ctx.tiles[9]);
986 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + max, base_y + yy,
987 ctx.tiles[15]);
988 }
989}
990
992 // ASM: RoomDraw_Spike2x2In4x4SuperSquare ($019708)
993 // Draws 2x2 spike patterns in a tiled grid
994 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
995 int size_y = (ctx.object.size_ & 0x03) + 1;
996
997 if (ctx.tiles.size() < 4)
998 return;
999
1000 for (int sy = 0; sy < size_y; ++sy) {
1001 for (int sx = 0; sx < size_x; ++sx) {
1002 int base_x = ctx.object.x_ + (sx * 2);
1003 int base_y = ctx.object.y_ + (sy * 2);
1004
1005 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y, ctx.tiles[0]);
1006 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y,
1007 ctx.tiles[2]);
1008 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y + 1,
1009 ctx.tiles[1]);
1010 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 1,
1011 ctx.tiles[3]);
1012 }
1013 }
1014}
1015
1017 // ASM: Object 0xDD - Table rock pattern
1018 int size_x = ((ctx.object.size_ >> 2) & 0x03);
1019 int size_y = (ctx.object.size_ & 0x03);
1020
1021 if (ctx.tiles.size() < 16)
1022 return;
1023
1024 int right_x = ctx.object.x_ + (3 + (size_x * 2));
1025 int bottom_y = ctx.object.y_ + (3 + (size_y * 2));
1026
1027 // Interior
1028 for (int xx = 0; xx < size_x + 1; ++xx) {
1029 for (int yy = 0; yy < size_y + 1; ++yy) {
1030 int base_x = ctx.object.x_ + (xx * 2);
1031 int base_y = ctx.object.y_ + (yy * 2);
1032 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 1,
1033 ctx.tiles[5]);
1034 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, base_y + 1,
1035 ctx.tiles[6]);
1036 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 2,
1037 ctx.tiles[9]);
1038 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, base_y + 2,
1039 ctx.tiles[10]);
1040 }
1041 }
1042
1043 // Left/right borders
1044 for (int yy = 0; yy < size_y + 1; ++yy) {
1045 int base_y = ctx.object.y_ + (yy * 2);
1046 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, base_y + 1,
1047 ctx.tiles[4]);
1048 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, base_y + 2,
1049 ctx.tiles[8]);
1050
1051 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 1,
1052 ctx.tiles[7]);
1053 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 2,
1054 ctx.tiles[11]);
1055 }
1056
1057 // Top/bottom borders
1058 for (int xx = 0; xx < size_x + 1; ++xx) {
1059 int base_x = ctx.object.x_ + (xx * 2);
1060 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, ctx.object.y_,
1061 ctx.tiles[1]);
1062 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, ctx.object.y_,
1063 ctx.tiles[2]);
1064
1065 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, bottom_y,
1066 ctx.tiles[13]);
1067 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, bottom_y,
1068 ctx.tiles[14]);
1069 }
1070
1071 // Corners
1073 ctx.tiles[0]);
1075 ctx.tiles[12]);
1077 ctx.tiles[3]);
1078 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, bottom_y, ctx.tiles[15]);
1079}
1080
1082 // ASM: RoomDraw_WaterOverlayA8x8_1to16 ($0195D6) / RoomDraw_WaterOverlayB8x8
1083 // NOTE: In the original game, this is an HDMA control object that sets up
1084 // the wavy water distortion effect. It doesn't draw tiles directly.
1085 // For the editor, we draw the available tile data as a visual indicator.
1086
1087 int size_x = ((ctx.object.size_ >> 2) & 0x03);
1088 int size_y = (ctx.object.size_ & 0x03);
1089
1090 int count_x = size_x + 2;
1091 int count_y = size_y + 2;
1092
1093 if (ctx.tiles.empty())
1094 return;
1095 if (ctx.tiles.size() < 8) {
1096 // Fallback for abbreviated tile payloads: still stamp a visible overlay.
1097 for (int yy = 0; yy < count_y; ++yy) {
1098 for (int xx = 0; xx < count_x; ++xx) {
1099 int base_x = ctx.object.x_ + (xx * 4);
1100 int base_y = ctx.object.y_ + (yy * 4);
1101 const auto& tile =
1102 ctx.tiles[static_cast<size_t>((xx + yy) % ctx.tiles.size())];
1103 for (int y = 0; y < 4; ++y) {
1104 for (int x = 0; x < 4; ++x) {
1105 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
1106 tile);
1107 }
1108 }
1109 }
1110 }
1111 return;
1112 }
1113
1114 for (int yy = 0; yy < count_y; ++yy) {
1115 for (int xx = 0; xx < count_x; ++xx) {
1116 int base_x = ctx.object.x_ + (xx * 4);
1117 int base_y = ctx.object.y_ + (yy * 4);
1118
1119 for (int x = 0; x < 4; ++x) {
1120 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y,
1121 ctx.tiles[x]);
1122 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 2,
1123 ctx.tiles[x]);
1124 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 1,
1125 ctx.tiles[4 + x]);
1126 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 3,
1127 ctx.tiles[4 + x]);
1128 }
1129 }
1130 }
1131}
1132
1133// ============================================================================
1134// Stair Routines
1135// ============================================================================
1136
1138 // ASM: RoomDraw_InterRoomFatStairsUp ($01A41B)
1139 // Uses RoomDraw_4x4, which consumes a 4x4 tile block in COLUMN-MAJOR order.
1140 // In original game, registers position in $06B0 for transition handling
1141 // For editor display, we just draw the visual representation
1142
1143 if (ctx.tiles.size() < 16)
1144 return;
1145
1146 Draw4x4ColumnMajorTo(ctx.target_bg, ctx.object, ctx.tiles);
1147}
1148
1150 // ASM: RoomDraw_InterRoomFatStairsDownA ($01A458)
1151 // Uses tile data at obj10A8
1152 DrawInterRoomFatStairsUp(ctx); // Same visual structure
1153}
1154
1156 // ASM: RoomDraw_InterRoomFatStairsDownB ($01A486)
1157 // Uses tile data at obj10A8
1158 DrawInterRoomFatStairsUp(ctx); // Same visual structure
1159}
1160
1161void DrawSpiralStairs(const DrawContext& ctx, bool going_up, bool is_upper) {
1162 // ASM: RoomDraw_SpiralStairsGoingUpUpper, etc.
1163 // Calls RoomDraw_1x3N_rightwards with A=4 -> 4 columns x 3 rows = 12 tiles
1164 // Tile order is COLUMN-MAJOR (down first, then right).
1165 // Upper variants render to BG1, lower variants render to BG2.
1166 (void)going_up;
1167
1168 if (ctx.tiles.size() < 12)
1169 return;
1170
1171 gfx::BackgroundBuffer* dest = &ctx.target_bg;
1172 if (!is_upper && ctx.secondary_bg != nullptr) {
1173 dest = ctx.secondary_bg;
1174 }
1175
1176 DrawColumnMajor(*dest, ctx.object.x_, ctx.object.y_, 4, 3, ctx.tiles);
1177}
1178
1179void DrawAutoStairs(const DrawContext& ctx) {
1180 // ASM: RoomDraw_AutoStairs* routines
1181 // The multi-layer variants stamp both BG tilemaps; merged/single-layer
1182 // variants stay on the active target layer.
1183 if (ctx.tiles.size() < 16)
1184 return;
1185
1186 Draw4x4ColumnMajorTo(ctx.target_bg, ctx.object, ctx.tiles);
1187
1188 if (ctx.secondary_bg != nullptr &&
1190 Draw4x4ColumnMajorTo(*ctx.secondary_bg, ctx.object, ctx.tiles);
1191 }
1192}
1193
1195 // ASM: RoomDraw_StraightInterroomStairs* routines
1196 // Upper variants render on BG1 only. Lower variants render the 4x4 block on
1197 // BG2 and expose the front edge on BG1 as well.
1198 if (ctx.tiles.size() < 16)
1199 return;
1200
1202 ctx.object.id_) ||
1203 ctx.secondary_bg == nullptr ||
1205 Draw4x4ColumnMajorTo(ctx.target_bg, ctx.object, ctx.tiles);
1206 return;
1207 }
1208
1209 const bool south_lower =
1211 ctx.object.id_);
1212 size_t tile_index = 0;
1213 for (int col = 0; col < 4; ++col) {
1214 for (int row = 0; row < 4; ++row) {
1215 const auto& tile = TileAtWrapped(ctx.tiles, tile_index++);
1217 ctx.object.y_ + row, tile);
1218 if ((!south_lower && row == 0) || (south_lower && row == 3)) {
1220 ctx.object.y_ + row, tile);
1221 }
1222 }
1223 }
1224}
1225
1226// ============================================================================
1227// Interactive Object Routines
1228// ============================================================================
1229
1230void DrawPrisonCell(const DrawContext& ctx) {
1231 // ASM: RoomDraw_PrisonCell ($019C44)
1232 // The routine selects the current object-list tilemap through $BF, then
1233 // writes one sparse 16x4 cell. Offsets below are the tile-coordinate form of
1234 // the long writes at $019C5A-$019CC5. In particular, columns 7 and 8 remain
1235 // open on rows 1-3 so objects drawn earlier (room 0x080's big-key lock) show
1236 // through the cell opening.
1237 if (ctx.tiles.size() < 6) {
1238 return;
1239 }
1240
1241 const int base_x = ctx.object.x_;
1242 const int base_y = ctx.object.y_;
1243
1244 auto with_horizontal_mirror = [](gfx::TileInfo tile) {
1245 // USDASM uses ORA #$4000 for these writes: force H-flip on rather than
1246 // toggling whatever bit a modified ROM supplied in obj1488.
1247 tile.horizontal_mirror_ = true;
1248 return tile;
1249 };
1250
1251 for (int col = 0; col < 5; ++col) {
1252 const int left_x = base_x + 2 + col;
1253 const int right_x = base_x + 9 + col;
1254 DrawRoutineUtils::WriteTile8(ctx.target_bg, left_x, base_y, ctx.tiles[1]);
1255 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y, ctx.tiles[1]);
1256 DrawRoutineUtils::WriteTile8(ctx.target_bg, left_x, base_y + 1,
1257 ctx.tiles[2]);
1258 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 1,
1259 with_horizontal_mirror(ctx.tiles[2]));
1260 DrawRoutineUtils::WriteTile8(ctx.target_bg, left_x, base_y + 2,
1261 ctx.tiles[4]);
1262 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 2,
1263 with_horizontal_mirror(ctx.tiles[4]));
1264 DrawRoutineUtils::WriteTile8(ctx.target_bg, left_x, base_y + 3,
1265 ctx.tiles[5]);
1266 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 3,
1267 with_horizontal_mirror(ctx.tiles[5]));
1268 }
1269
1270 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y, ctx.tiles[0]);
1271 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 15, base_y,
1272 with_horizontal_mirror(ctx.tiles[0]));
1273 for (int x_offset : {1, 7, 8, 14}) {
1274 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x_offset, base_y,
1275 ctx.tiles[1]);
1276 }
1277 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 2,
1278 ctx.tiles[3]);
1279 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 14, base_y + 2,
1280 with_horizontal_mirror(ctx.tiles[3]));
1281}
1282
1283void DrawBigKeyLock(const DrawContext& ctx) {
1284 // ASM: RoomDraw_BigKeyLock ($0198AE)
1285 // The opened-state room-event check and shared chest/lock slot advancement
1286 // live in ObjectDrawer, which owns stream ordering. The ROM routine's closed
1287 // branch jumps to RoomDraw_Rightwards2x2 and consumes obj1494 in column-major
1288 // order; there is no second tile state.
1289 DrawSingle2x2(ctx);
1290}
1291
1293 // USDASM RoomDraw_BombableFloor ($01:B3E1) draws four 2x2 quadrants.
1294 // ParseSubtype3 stores the intact obj0220 block at tiles[0..15] and the
1295 // non-contiguous obj05BA replacement block at tiles[16..31]. Each quadrant
1296 // is ordered upper-left, lower-left, upper-right, lower-right.
1297 if (ctx.tiles.size() < 16) {
1298 return;
1299 }
1300
1301 // Vanilla persists this state for room 0x65, while ROM hacks can relocate
1302 // the floor (Oracle of Secrets uses 0xAD). Keep preview selection keyed to
1303 // the current room instead of hardcoding a vanilla room ID.
1304 const bool is_open = ctx.state != nullptr &&
1305 ctx.state->IsFloorBombable(ctx.room_id) &&
1306 ctx.tiles.size() >= 32;
1307 const size_t state_offset = is_open ? 16 : 0;
1308
1309 for (int block_y = 0; block_y < 2; ++block_y) {
1310 for (int block_x = 0; block_x < 2; ++block_x) {
1311 const size_t block_offset =
1312 state_offset + static_cast<size_t>((block_y * 2 + block_x) * 4);
1313 for (int x = 0; x < 2; ++x) {
1314 for (int y = 0; y < 2; ++y) {
1316 ctx.target_bg, ctx.object.x_ + block_x * 2 + x,
1317 ctx.object.y_ + block_y * 2 + y,
1318 ctx.tiles[block_offset + static_cast<size_t>(x * 2 + y)]);
1319 }
1320 }
1321 }
1322 }
1323}
1324
1326 // USDASM RoomDraw_MovingWallWest ($01:9190): moved walls emit no tiles.
1327 if ((ctx.state != nullptr && ctx.state->IsWallMoved(ctx.room_id)) ||
1328 ctx.tiles.size() < 24) {
1329 return;
1330 }
1331
1332 const int direction = moving_wall::DirectionForSize(ctx.object.size_);
1333 const int column_count = moving_wall::ObjectCountForSize(ctx.object.size_);
1334 const int height = direction * 2 + 6;
1335
1336 // West writes the obj03D8 fill first, growing left from the object anchor,
1337 // then stamps the 3x3 corner and repeated 3x2 vertical wall at the anchor.
1338 DrawMovingWallFill(ctx, ctx.object.x_ - column_count, column_count, height);
1339 DrawMovingWallPlatform(ctx, direction);
1340}
1341
1343 // USDASM RoomDraw_MovingWallEast ($01:921C): moved walls emit no tiles.
1344 if ((ctx.state != nullptr && ctx.state->IsWallMoved(ctx.room_id)) ||
1345 ctx.tiles.size() < 24) {
1346 return;
1347 }
1348
1349 const int direction = moving_wall::DirectionForSize(ctx.object.size_);
1350 const int column_count = moving_wall::ObjectCountForSize(ctx.object.size_);
1351 const int height = direction * 2 + 6;
1352
1353 // East stamps the platform first, then grows the obj03D8 fill right from
1354 // the first column beyond the three-tile-wide platform.
1355 DrawMovingWallPlatform(ctx, direction);
1356 DrawMovingWallFill(ctx, ctx.object.x_ + 3, column_count, height);
1357}
1358
1359// ============================================================================
1360// Water Face Variants
1361// ============================================================================
1362
1363namespace {
1364constexpr int kWaterFaceWidthTiles = 4;
1365
1366void DrawWaterFaceRows(const DrawContext& ctx, int row_count, int tile_offset) {
1367 if (row_count <= 0 || tile_offset < 0 || ctx.tiles.empty()) {
1368 return;
1369 }
1370
1371 if (tile_offset >= static_cast<int>(ctx.tiles.size())) {
1372 return;
1373 }
1374
1375 const int available_tiles = static_cast<int>(ctx.tiles.size()) - tile_offset;
1376 const int available_rows = available_tiles / kWaterFaceWidthTiles;
1377 const int rows_to_draw = std::min(row_count, available_rows);
1378
1379 for (int row = 0; row < rows_to_draw; ++row) {
1380 const int row_base = tile_offset + (row * kWaterFaceWidthTiles);
1381 for (int col = 0; col < kWaterFaceWidthTiles; ++col) {
1383 ctx.object.y_ + row,
1384 ctx.tiles[row_base + col]);
1385 }
1386 }
1387}
1388} // namespace
1389
1391 // ASM: RoomDraw_EmptyWaterFace ($019D29)
1392 //
1393 // usdasm behavior:
1394 // - Base state draws a 4x3 face using data at offset 0x1614.
1395 // - "Water active" branch draws a 4x5 variant using data at offset 0x162C.
1396 //
1397 // IMPORTANT: this uses dedicated water-face state, not door state. Tying
1398 // this branch to IsDoorOpen created cross-feature rendering regressions.
1399 const bool water_active =
1400 (ctx.state != nullptr) && ctx.state->IsWaterFaceActive(ctx.room_id);
1401
1402 const int row_count = water_active ? 5 : 3;
1403 const int tile_offset = water_active ? 12 : 0; // 0x162C - 0x1614 = 24 bytes
1404 DrawWaterFaceRows(ctx, row_count, tile_offset);
1405}
1406
1408 // ASM: RoomDraw_SpittingWaterFace ($019D64)
1409 // Draws a 4x5 face/spout shape.
1410 DrawWaterFaceRows(ctx, /*row_count=*/5, /*tile_offset=*/0);
1411}
1412
1414 // ASM: RoomDraw_DrenchingWaterFace ($019D83)
1415 // Draws a 4x7 continuous stream.
1416 DrawWaterFaceRows(ctx, /*row_count=*/7, /*tile_offset=*/0);
1417}
1418
1419// ============================================================================
1420// Chest Platform Multi-Part Routines
1421// ============================================================================
1422
1424 // ASM: RoomDraw_ClosedChestPlatform ($018CC7)
1425 // Size fields are subtype-1 packed 2-bit values: $B2=size_x, $B4=size_y.
1426 // The routine expands them to width_blocks=size_x+4 and
1427 // height_blocks=size_y+1 before drawing top, center, bottom, and the center
1428 // 2x2 overlay.
1429
1430 if (ctx.tiles.size() < 68)
1431 return;
1432
1433 const int size_x = (ctx.object.size_ >> 2) & 0x03;
1434 const int size_y = ctx.object.size_ & 0x03;
1435 const int width_blocks = size_x + 4;
1436 const int height_blocks = size_y + 1;
1437
1438 // Top 3 rows: left cap, repeated 2-column middle, right cap.
1439 DrawPlatform1x3Rightwards(ctx, /*dx=*/0, /*dy=*/0, /*start_index=*/0,
1440 /*columns=*/3);
1441 for (int x = 0; x < width_blocks; ++x) {
1442 DrawPlatform1x3Rightwards(ctx, /*dx=*/3 + x * 2, /*dy=*/0,
1443 /*start_index=*/9, /*columns=*/2);
1444 }
1445 DrawPlatform1x3Rightwards(ctx, /*dx=*/3 + width_blocks * 2, /*dy=*/0,
1446 /*start_index=*/15, /*columns=*/3);
1447
1448 // Center rows: left wall, repeated 2x2 carpet, right wall.
1449 for (int y = 0; y < height_blocks; ++y) {
1450 const int dy = 3 + y * 2;
1451 DrawPlatform3x2(ctx, /*dx=*/0, dy, /*start_index=*/24);
1452 for (int x = 0; x < width_blocks; ++x) {
1453 DrawPlatform2x2(ctx, /*dx=*/3 + x * 2, dy, /*start_index=*/30);
1454 }
1455 DrawPlatform3x2(ctx, /*dx=*/3 + width_blocks * 2, dy,
1456 /*start_index=*/34);
1457 }
1458
1459 // Bottom 3 rows.
1460 const int bottom_y = 3 + height_blocks * 2;
1461 DrawPlatform1x3Rightwards(ctx, /*dx=*/0, bottom_y, /*start_index=*/40,
1462 /*columns=*/3);
1463 for (int x = 0; x < width_blocks; ++x) {
1464 DrawPlatform1x3Rightwards(ctx, /*dx=*/3 + x * 2, bottom_y,
1465 /*start_index=*/49, /*columns=*/2);
1466 }
1467 DrawPlatform1x3Rightwards(ctx, /*dx=*/3 + width_blocks * 2, bottom_y,
1468 /*start_index=*/55, /*columns=*/3);
1469
1470 // Center 2x2 overlay, matching the final SrcPtr(0x590) draw in the native
1471 // C++ port and ZScream's tile indices 64,66/65,67.
1472 DrawPlatform2x2(ctx, /*dx=*/width_blocks + 2,
1473 /*dy=*/bottom_y - (height_blocks + 1),
1474 /*start_index=*/64);
1475}
1476
1478 // ASM: RoomDraw_OpenChestPlatform ($019733). The helper draws one row of a
1479 // 10+2*size_x tile platform, repeated (2*size_y+5) times, then two closing
1480 // rows from src+1/src+2.
1481 if (ctx.tiles.size() < 21)
1482 return;
1483
1484 const int size_x = (ctx.object.size_ >> 2) & 0x03;
1485 const int size_y = ctx.object.size_ & 0x03;
1486 const int fill_width = size_x + 1;
1487 const int body_rows = size_y * 2 + 5;
1488
1489 for (int row = 0; row < body_rows; ++row) {
1490 DrawOpenChestPlatformSegment(ctx, row, /*start_index=*/0, fill_width);
1491 }
1492 DrawOpenChestPlatformSegment(ctx, body_rows, /*start_index=*/1, fill_width);
1493 DrawOpenChestPlatformSegment(ctx, body_rows + 1, /*start_index=*/2,
1494 fill_width);
1495}
1496
1498 // ASM: RoomDraw_ChestPlatformHorizontalWallWithCorners ($018D0D)
1499 int width = (ctx.object.size_ & 0x0F) + 1;
1500
1501 if (ctx.tiles.size() < 3)
1502 return;
1503
1504 for (int x = 0; x < width; ++x) {
1505 size_t tile_idx = (x == 0) ? 0 : ((x == width - 1) ? 2 : 1);
1507 ctx.object.y_, ctx.tiles[tile_idx]);
1508 }
1509}
1510
1512 // ASM: RoomDraw_ChestPlatformVerticalWall ($019E70)
1513 int height = (ctx.object.size_ & 0x0F) + 1;
1514
1515 if (ctx.tiles.empty())
1516 return;
1517
1518 for (int y = 0; y < height; ++y) {
1520 ctx.object.y_ + y, ctx.tiles[0]);
1521 }
1522}
1523
1524void RegisterSpecialRoutines(std::vector<DrawRoutineInfo>& registry) {
1525 // Note: Routine IDs are assigned based on the assembly routine table
1526 // These special routines handle chests, doors, and other non-standard objects
1527
1528 // Chest routine - uses a wrapper since it needs chest_index
1529 registry.push_back(DrawRoutineInfo{
1530 .id = 39, // DrawChest (special index)
1531 .name = "Chest",
1532 .function =
1533 [](const DrawContext& ctx) {
1534 // Default chest index 0 - actual index tracked externally
1535 DrawChest(ctx, 0);
1536 },
1537 .draws_to_both_bgs = false,
1538 .base_width = 2,
1539 .base_height = 2,
1540 .min_tiles = 4, // 2x2 block
1542 });
1543
1544 registry.push_back(DrawRoutineInfo{
1545 .id = 38, // DrawNothing
1546 .name = "Nothing",
1547 .function = DrawNothing,
1548 .draws_to_both_bgs = false,
1549 .base_width = 0,
1550 .base_height = 0,
1552 });
1553
1554 registry.push_back(DrawRoutineInfo{
1555 .id = 26, // DrawDoorSwitcherer
1556 .name = "DoorSwitcherer",
1557 .function = DrawDoorSwitcherer,
1558 .draws_to_both_bgs = false,
1559 .base_width = 1,
1560 .base_height = 1,
1561 .min_tiles = 1, // at least one tile for door graphic
1563 });
1564
1565 registry.push_back(DrawRoutineInfo{
1566 .id = 33, // DrawSomariaLine
1567 .name = "SomariaLine",
1568 .function = DrawSomariaLine,
1569 .draws_to_both_bgs = false,
1570 .base_width = 1,
1571 .base_height = 1,
1572 .min_tiles = 1,
1574 });
1575
1576 registry.push_back(DrawRoutineInfo{
1577 .id = 34, // DrawWaterFace
1578 .name = "WaterFace",
1579 .function = DrawWaterFace,
1580 .draws_to_both_bgs = false,
1581 .base_width = 2,
1582 .base_height = 2,
1583 .min_tiles = 4, // 2x2 block
1585 });
1586
1587 // ============================================================================
1588 // SuperSquare Routines (Phase 4) - IDs 56-64
1589 // ============================================================================
1590
1591 registry.push_back(DrawRoutineInfo{
1592 .id = 56, // Draw4x4BlocksIn4x4SuperSquare
1593 .name = "4x4BlocksIn4x4SuperSquare",
1595 .draws_to_both_bgs = false,
1596 .base_width = 0, // Variable: width = (((size >> 2) & 3) + 1) * 4
1597 .base_height = 0, // Variable: height = ((size & 3) + 1) * 4
1599 });
1600
1601 registry.push_back(DrawRoutineInfo{
1602 .id = 57, // Draw3x3FloorIn4x4SuperSquare
1603 .name = "3x3FloorIn4x4SuperSquare",
1604 .function = Draw3x3FloorIn4x4SuperSquare,
1605 .draws_to_both_bgs = false,
1606 .base_width = 0, // Variable
1607 .base_height = 0, // Variable
1609 });
1610
1611 registry.push_back(DrawRoutineInfo{
1612 .id = 58, // Draw4x4FloorIn4x4SuperSquare
1613 .name = "4x4FloorIn4x4SuperSquare",
1614 .function = Draw4x4FloorIn4x4SuperSquare,
1615 .draws_to_both_bgs = false,
1616 .base_width = 0, // Variable
1617 .base_height = 0, // Variable
1619 });
1620
1621 registry.push_back(DrawRoutineInfo{
1622 .id = 59, // Draw4x4FloorOneIn4x4SuperSquare
1623 .name = "4x4FloorOneIn4x4SuperSquare",
1625 .draws_to_both_bgs = false,
1626 .base_width = 0, // Variable
1627 .base_height = 0, // Variable
1629 });
1630
1631 registry.push_back(DrawRoutineInfo{
1632 .id = 60, // Draw4x4FloorTwoIn4x4SuperSquare
1633 .name = "4x4FloorTwoIn4x4SuperSquare",
1635 .draws_to_both_bgs = false,
1636 .base_width = 0, // Variable
1637 .base_height = 0, // Variable
1639 });
1640
1641 registry.push_back(DrawRoutineInfo{
1642 .id = 61, // DrawBigHole4x4_1to16
1643 .name = "BigHole4x4_1to16",
1644 .function = DrawBigHole4x4_1to16,
1645 .draws_to_both_bgs = false,
1646 .base_width = 0, // Variable
1647 .base_height = 0, // Variable
1649 });
1650
1651 registry.push_back(DrawRoutineInfo{
1652 .id = 62, // DrawSpike2x2In4x4SuperSquare
1653 .name = "Spike2x2In4x4SuperSquare",
1654 .function = DrawSpike2x2In4x4SuperSquare,
1655 .draws_to_both_bgs = false,
1656 .base_width = 0, // Variable
1657 .base_height = 0, // Variable
1659 });
1660
1661 registry.push_back(DrawRoutineInfo{
1662 .id = 63, // DrawTableRock4x4_1to16
1663 .name = "TableRock4x4_1to16",
1664 .function = DrawTableRock4x4_1to16,
1665 .draws_to_both_bgs = false,
1666 .base_width = 0, // Variable
1667 .base_height = 0, // Variable
1669 });
1670
1671 registry.push_back(DrawRoutineInfo{
1672 .id = 64, // DrawWaterOverlay8x8_1to16
1673 .name = "WaterOverlay8x8_1to16",
1674 .function = DrawWaterOverlay8x8_1to16,
1675 .draws_to_both_bgs = false,
1676 .base_width = 0, // Variable
1677 .base_height = 0, // Variable
1679 });
1680
1681 // Stair routines (IDs 83-88)
1682 registry.push_back(DrawRoutineInfo{
1683 .id = 83, // DrawInterRoomFatStairsUp
1684 .name = "InterRoomFatStairsUp",
1685 .function = DrawInterRoomFatStairsUp,
1686 .draws_to_both_bgs = false,
1687 .base_width = 4,
1688 .base_height = 4,
1689 .min_tiles = 16, // 4x4 stair pattern
1691 });
1692
1693 registry.push_back(DrawRoutineInfo{
1694 .id = 84, // DrawInterRoomFatStairsDownA
1695 .name = "InterRoomFatStairsDownA",
1696 .function = DrawInterRoomFatStairsDownA,
1697 .draws_to_both_bgs = false,
1698 .base_width = 4,
1699 .base_height = 4,
1700 .min_tiles = 16, // 4x4 stair pattern
1702 });
1703
1704 registry.push_back(DrawRoutineInfo{
1705 .id = 85, // DrawInterRoomFatStairsDownB
1706 .name = "InterRoomFatStairsDownB",
1707 .function = DrawInterRoomFatStairsDownB,
1708 .draws_to_both_bgs = false,
1709 .base_width = 4,
1710 .base_height = 4,
1711 .min_tiles = 16, // 4x4 stair pattern
1713 });
1714
1715 registry.push_back(DrawRoutineInfo{
1716 .id = 86, // DrawAutoStairs
1717 .name = "AutoStairs",
1718 .function = DrawAutoStairs,
1719 .draws_to_both_bgs = false,
1720 .base_width = 4,
1721 .base_height = 4,
1722 .min_tiles = 16, // 4x4 stair pattern
1724 });
1725
1726 registry.push_back(DrawRoutineInfo{
1727 .id = 87, // DrawStraightInterRoomStairs
1728 .name = "StraightInterRoomStairs",
1729 .function = DrawStraightInterRoomStairs,
1730 .draws_to_both_bgs = false,
1731 .base_width = 4,
1732 .base_height = 4,
1733 .min_tiles = 16, // 4x4 stair pattern
1735 });
1736
1737 // Spiral stairs variants (IDs 88-91)
1738 registry.push_back(DrawRoutineInfo{
1739 .id = 88, // DrawSpiralStairsGoingUpUpper
1740 .name = "SpiralStairsGoingUpUpper",
1741 .function =
1742 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, true, true); },
1743 .draws_to_both_bgs = false,
1744 .base_width = 4,
1745 .base_height = 3, // 4x3 pattern
1746 .min_tiles = 12, // 4x3 block
1748 });
1749
1750 registry.push_back(DrawRoutineInfo{
1751 .id = 89, // DrawSpiralStairsGoingDownUpper
1752 .name = "SpiralStairsGoingDownUpper",
1753 .function =
1754 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, false, true); },
1755 .draws_to_both_bgs = false,
1756 .base_width = 4,
1757 .base_height = 3, // 4x3 pattern
1758 .min_tiles = 12, // 4x3 block
1760 });
1761
1762 registry.push_back(DrawRoutineInfo{
1763 .id = 90, // DrawSpiralStairsGoingUpLower
1764 .name = "SpiralStairsGoingUpLower",
1765 .function =
1766 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, true, false); },
1767 .draws_to_both_bgs = false,
1768 .base_width = 4,
1769 .base_height = 3, // 4x3 pattern
1770 .min_tiles = 12, // 4x3 block
1772 });
1773
1774 registry.push_back(DrawRoutineInfo{
1775 .id = 91, // DrawSpiralStairsGoingDownLower
1776 .name = "SpiralStairsGoingDownLower",
1777 .function =
1778 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, false, false); },
1779 .draws_to_both_bgs = false,
1780 .base_width = 4,
1781 .base_height = 3, // 4x3 pattern
1782 .min_tiles = 12, // 4x3 block
1784 });
1785
1786 registry.push_back(DrawRoutineInfo{
1788 .name = "WaterHopStairsA",
1789 .function = DrawWaterHopStairsA,
1790 .draws_to_both_bgs = false,
1791 .base_width = 4,
1792 .base_height = 2,
1793 .min_tiles = 8,
1795 });
1796
1797 registry.push_back(DrawRoutineInfo{
1799 .name = "WaterHopStairsB",
1800 .function = DrawWaterHopStairsB,
1801 .draws_to_both_bgs = true,
1802 .base_width = 4,
1803 .base_height = 2,
1804 .min_tiles = 8,
1806 });
1807
1808 registry.push_back(DrawRoutineInfo{
1810 .name = "DamFloodGate",
1811 .function = DrawDamFloodGate,
1812 .draws_to_both_bgs = false,
1813 .base_width = 10,
1814 .base_height = 4,
1815 .min_tiles = 40,
1817 });
1818
1819 // Interactive object routines (IDs 92-95)
1820 registry.push_back(DrawRoutineInfo{
1821 .id = 92, // DrawBigKeyLock
1822 .name = "BigKeyLock",
1823 .function = DrawBigKeyLock,
1824 .draws_to_both_bgs = false,
1825 .base_width = 2,
1826 .base_height = 2,
1827 .min_tiles = 4, // 2x2 block
1829 });
1830
1831 registry.push_back(DrawRoutineInfo{
1832 .id = 93, // DrawBombableFloor
1833 .name = "BombableFloor",
1834 .function = DrawBombableFloor,
1835 .draws_to_both_bgs = false,
1836 .base_width = 4,
1837 .base_height = 4,
1838 .min_tiles = 16, // One complete 4x4 state
1840 });
1841
1842 // Water face variants (IDs 94-96)
1843 registry.push_back(DrawRoutineInfo{
1844 .id = 94, // DrawEmptyWaterFace
1845 .name = "EmptyWaterFace",
1846 .function = DrawEmptyWaterFace,
1847 .draws_to_both_bgs = false,
1848 .base_width = 4,
1849 .base_height = 3,
1850 .min_tiles = 12, // base 4x3 variant
1852 });
1853
1854 registry.push_back(DrawRoutineInfo{
1855 .id = 95, // DrawSpittingWaterFace
1856 .name = "SpittingWaterFace",
1857 .function = DrawSpittingWaterFace,
1858 .draws_to_both_bgs = false,
1859 .base_width = 4,
1860 .base_height = 5,
1861 .min_tiles = 20, // 4x5 variant
1863 });
1864
1865 registry.push_back(DrawRoutineInfo{
1866 .id = 96, // DrawDrenchingWaterFace
1867 .name = "DrenchingWaterFace",
1868 .function = DrawDrenchingWaterFace,
1869 .draws_to_both_bgs = false,
1870 .base_width = 4,
1871 .base_height = 7,
1872 .min_tiles = 28, // 4x7 variant
1874 });
1875
1876 // Prison cell (Type 3 objects 0x20D, 0x217) writes only to the tilemap
1877 // selected by the current object stream ($BF in USDASM).
1878 registry.push_back(DrawRoutineInfo{
1879 .id = 97, // DrawPrisonCell
1880 .name = "PrisonCell",
1881 .function = DrawPrisonCell,
1882 .draws_to_both_bgs = false,
1883 .base_width = 16,
1884 .base_height = 4,
1885 .min_tiles = 6,
1887 });
1888
1889 registry.push_back(DrawRoutineInfo{
1890 .id = DrawRoutineIds::kBed4x5, // 98
1891 .name = "Bed4x5",
1892 .function = DrawBed4x5,
1893 .draws_to_both_bgs = false,
1894 .base_width = 4,
1895 .base_height = 5,
1897 });
1898
1899 registry.push_back(DrawRoutineInfo{
1901 .name = "Rightwards3x6",
1902 .function = DrawRightwards3x6,
1903 .draws_to_both_bgs = false,
1904 .base_width = 6,
1905 .base_height = 3,
1907 });
1908
1909 registry.push_back(DrawRoutineInfo{
1911 .name = "Utility6x3",
1912 .function = DrawUtility6x3,
1913 .draws_to_both_bgs = false,
1914 .base_width = 6,
1915 .base_height = 3,
1917 });
1918
1919 registry.push_back(DrawRoutineInfo{
1921 .name = "Utility3x5",
1922 .function = DrawUtility3x5,
1923 .draws_to_both_bgs = false,
1924 .base_width = 3,
1925 .base_height = 5,
1927 });
1928
1929 registry.push_back(DrawRoutineInfo{
1931 .name = "VerticalTurtleRockPipe",
1932 .function = DrawVerticalTurtleRockPipe,
1933 .draws_to_both_bgs = false,
1934 .base_width = 4,
1935 .base_height = 6,
1937 });
1938
1939 registry.push_back(DrawRoutineInfo{
1941 .name = "HorizontalTurtleRockPipe",
1942 .function = DrawHorizontalTurtleRockPipe,
1943 .draws_to_both_bgs = false,
1944 .base_width = 6,
1945 .base_height = 4,
1947 });
1948
1949 registry.push_back(DrawRoutineInfo{
1951 .name = "LightBeam",
1952 .function = DrawLightBeamOnFloor,
1953 .draws_to_both_bgs = false,
1954 .base_width = 4,
1955 .base_height = 10,
1956 .min_tiles = 32,
1958 });
1959
1960 registry.push_back(DrawRoutineInfo{
1962 .name = "BigLightBeam",
1963 .function = DrawBigLightBeamOnFloor,
1964 .draws_to_both_bgs = false,
1965 .base_width = 8,
1966 .base_height = 8,
1967 .min_tiles = 64,
1969 });
1970
1971 registry.push_back(DrawRoutineInfo{
1973 .name = "FloorLight",
1974 .function = DrawFloorLight,
1975 .draws_to_both_bgs = false,
1976 .base_width = 8,
1977 .base_height = 8,
1978 .min_tiles = 64,
1980 });
1981
1982 registry.push_back(DrawRoutineInfo{
1984 .name = "BossShell4x4",
1985 .function = DrawBossShell4x4,
1986 .draws_to_both_bgs = false,
1987 .base_width = 4,
1988 .base_height = 4,
1990 });
1991
1992 registry.push_back(DrawRoutineInfo{
1994 .name = "VitreousGooDamage",
1995 .function = DrawVitreousGooDamage,
1996 .draws_to_both_bgs = false,
1997 .base_width = 20,
1998 .base_height = 8,
1999 .min_tiles = 8,
2001 });
2002
2003 registry.push_back(DrawRoutineInfo{
2005 .name = "SolidWallDecor3x4",
2006 .function = DrawSolidWallDecor3x4,
2007 .draws_to_both_bgs = false,
2008 .base_width = 3,
2009 .base_height = 4,
2011 });
2012
2013 registry.push_back(DrawRoutineInfo{
2015 .name = "ArcheryGameTargetDoor",
2016 .function = DrawArcheryGameTargetDoor,
2017 .draws_to_both_bgs = false,
2018 .base_width = 3,
2019 .base_height = 6,
2021 });
2022
2023 registry.push_back(DrawRoutineInfo{
2025 .name = "GanonTriforceFloorDecor",
2026 .function = DrawGanonTriforceFloorDecor,
2027 .draws_to_both_bgs = false,
2028 .base_width = 8,
2029 .base_height = 8,
2031 });
2032
2033 registry.push_back(DrawRoutineInfo{
2035 .name = "Single2x2",
2036 .function = DrawSingle2x2,
2037 .draws_to_both_bgs = false,
2038 .base_width = 2,
2039 .base_height = 2,
2040 .min_tiles = 4,
2042 });
2043
2044 registry.push_back(DrawRoutineInfo{
2046 .name = "Single4x4",
2047 .function = DrawSingle4x4,
2048 .draws_to_both_bgs = false,
2049 .base_width = 4,
2050 .base_height = 4,
2051 .min_tiles = 16,
2053 });
2054
2055 registry.push_back(DrawRoutineInfo{
2057 .name = "Single4x3",
2058 .function = DrawSingle4x3,
2059 .draws_to_both_bgs = false,
2060 .base_width = 4,
2061 .base_height = 3,
2062 .min_tiles = 12,
2064 });
2065
2066 registry.push_back(DrawRoutineInfo{
2068 .name = "RupeeFloor",
2069 .function = DrawRupeeFloor,
2070 .draws_to_both_bgs = false,
2071 .base_width = 5,
2072 .base_height = 8,
2073 .min_tiles = 2,
2075 });
2076
2077 registry.push_back(DrawRoutineInfo{
2079 .name = "Actual4x4",
2080 .function = DrawActual4x4,
2081 .draws_to_both_bgs = false,
2082 .base_width = 4,
2083 .base_height = 4,
2084 .min_tiles = 16,
2086 });
2087
2088 registry.push_back(DrawRoutineInfo{
2090 .name = "BigWallDecor",
2091 .function = DrawBigWallDecor,
2092 .draws_to_both_bgs = false,
2093 .base_width = 8,
2094 .base_height = 3,
2095 .min_tiles = 24,
2097 });
2098
2099 registry.push_back(DrawRoutineInfo{
2101 .name = "TableBowl",
2102 .function = DrawTableBowl,
2103 .draws_to_both_bgs = false,
2104 .base_width = 4,
2105 .base_height = 2,
2106 .min_tiles = 8,
2108 });
2109
2110 registry.push_back(DrawRoutineInfo{
2112 .name = "SmithyFurnace",
2113 .function = DrawSmithyFurnace,
2114 .draws_to_both_bgs = false,
2115 .base_width = 6,
2116 .base_height = 8,
2117 .min_tiles = 48,
2119 });
2120
2121 registry.push_back(DrawRoutineInfo{
2123 .name = "BigGrayRock",
2124 .function = DrawBigGrayRock,
2125 .draws_to_both_bgs = false,
2126 .base_width = 4,
2127 .base_height = 4,
2128 .min_tiles = 16,
2130 });
2131
2132 registry.push_back(DrawRoutineInfo{
2134 .name = "AgahnimsAltar",
2135 .function = DrawAgahnimsAltar,
2136 .draws_to_both_bgs = false,
2137 .base_width = 14,
2138 .base_height = 14,
2139 .min_tiles = 84,
2141 });
2142
2143 registry.push_back(DrawRoutineInfo{
2145 .name = "FortuneTellerRoom",
2146 .function = DrawFortuneTellerRoom,
2147 .draws_to_both_bgs = false,
2148 .base_width = 14,
2149 .base_height = 14,
2150 .min_tiles = 26,
2152 });
2153
2154 registry.push_back(DrawRoutineInfo{
2156 .name = "MagicBatAltar",
2157 .function = DrawMagicBatAltar,
2158 .draws_to_both_bgs = false,
2159 .base_width = 8,
2160 .base_height = 7,
2161 .min_tiles = 56,
2163 });
2164
2165 registry.push_back(DrawRoutineInfo{
2167 .name = "Waterfall47",
2168 .function = DrawWaterfall47,
2169 .draws_to_both_bgs = false,
2170 .base_width = 0, // Variable with size
2171 .base_height = 5,
2173 });
2174
2175 registry.push_back(DrawRoutineInfo{
2177 .name = "Waterfall48",
2178 .function = DrawWaterfall48,
2179 .draws_to_both_bgs = false,
2180 .base_width = 0, // Variable with size
2181 .base_height = 3,
2183 });
2184
2185 // Chest platform routines - use canonical IDs from DrawRoutineIds
2186 registry.push_back(DrawRoutineInfo{
2188 .name = "ClosedChestPlatform",
2189 .function = DrawClosedChestPlatform,
2190 .draws_to_both_bgs = false,
2191 .base_width = 0, // Variable: width = (size & 0x0F) + 4
2192 .base_height = 0, // Variable: height = ((size >> 4) & 0x0F) + 1
2194 });
2195
2196 // Moving wall routines
2197 registry.push_back(DrawRoutineInfo{
2199 .name = "MovingWallWest",
2200 .function = DrawMovingWallWest,
2201 .draws_to_both_bgs = false,
2202 .base_width = 0, // Variable: count selector + 3 platform columns
2203 .base_height = 0, // Variable: 2 * direction selector + 6
2204 .min_tiles = 24,
2206 });
2207
2208 registry.push_back(DrawRoutineInfo{
2210 .name = "MovingWallEast",
2211 .function = DrawMovingWallEast,
2212 .draws_to_both_bgs = false,
2213 .base_width = 0, // Variable: count selector + 3 platform columns
2214 .base_height = 0, // Variable: 2 * direction selector + 6
2215 .min_tiles = 24,
2217 });
2218
2219 registry.push_back(DrawRoutineInfo{
2221 .name = "OpenChestPlatform",
2222 .function = DrawOpenChestPlatform,
2223 .draws_to_both_bgs = false,
2224 .base_width = 0, // Variable
2225 .base_height = 0, // Variable
2227 });
2228
2229 // Long vertical rail with CORNER+MIDDLE+END pattern (ID 117) - object 0x8A
2230 // Matches horizontal rail 0x22 but in vertical orientation
2231 registry.push_back(DrawRoutineInfo{
2233 .name = "DownwardsHasEdge1x1_1to16_plus23",
2234 .function =
2235 [](const DrawContext& ctx) {
2236 // CORNER+MIDDLE+END pattern vertically
2237 int size = ctx.object.size_ & 0x0F;
2238 int count = size + 21;
2239 if (ctx.tiles.size() < 3)
2240 return;
2241
2242 int tile_y = ctx.object.y_;
2243 // USDASM $01:8EC9-$01:8ED4 suppresses the leading corner when the
2244 // current slot already contains the small vertical rail corner.
2246 ctx.target_bg, ctx.object.x_, tile_y, {0x00E3})) {
2247 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y,
2248 ctx.tiles[0]);
2249 }
2250 tile_y++;
2251 // Middle tiles
2252 for (int s = 0; s < count; s++) {
2253 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y,
2254 ctx.tiles[1]);
2255 tile_y++;
2256 }
2257 // End tile
2258 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y,
2259 ctx.tiles[2]);
2260 },
2261 .draws_to_both_bgs = false,
2262 .base_width = 1,
2263 .base_height = 23, // size + 23
2264 .min_tiles = 3, // corner + middle + end tiles
2266 });
2267
2268 // Custom Object routine (ID 130) - Oracle of Secrets objects 0x31, 0x32
2269 // These use external binary files instead of ROM tile data.
2270 // CustomDraw() handles feature-flag gating and binary file lookup.
2271 registry.push_back(DrawRoutineInfo{
2273 .name = "CustomObject",
2274 .function = CustomDraw,
2275 .draws_to_both_bgs = false,
2276 .base_width = 0, // Variable: depends on binary file content
2277 .base_height = 0, // Variable: depends on binary file content
2279 });
2280}
2281
2282} // namespace draw_routines
2283} // namespace zelda3
2284} // namespace yaze
absl::StatusOr< uint16_t > ReadWord(int offset) const
Definition rom.cc:571
static Flags & get()
Definition features.h:119
SNES 16-bit tile metadata container.
Definition snes_tile.h:52
static CustomObjectManager & Get()
absl::StatusOr< std::shared_ptr< CustomObject > > GetObjectInternal(int object_id, int subtype)
virtual bool IsWaterFaceActive(int room_id) const
virtual bool IsFloorBombable(int room_id) const =0
virtual bool IsWallMoved(int room_id) const =0
virtual bool IsChestOpen(int room_id, int chest_index) const =0
virtual bool IsDoorSwitchActive(int room_id) const =0
virtual bool IsDamFloodgateOpen(int room_id) const
virtual bool IsRupeeFloorCleared(int room_id) const =0
#define LOG_DEBUG(category, format,...)
Definition log.h:103
TileInfo WordToTileInfo(uint16_t word)
Definition snes_tile.cc:378
bool ExistingTileMatchesAny(const gfx::BackgroundBuffer &bg, int tile_x, int tile_y, std::initializer_list< uint16_t > tile_ids)
void WriteTile8(gfx::BackgroundBuffer &bg, int tile_x, int tile_y, const gfx::TileInfo &tile_info)
Write an 8x8 tile to the background buffer.
void DrawNx4(const DrawContext &ctx, int columns, size_t start_index)
std::array< gfx::TileInfo, 3 > LoadMovingWallFillTiles(const DrawContext &ctx)
void DrawMovingWallFill(const DrawContext &ctx, int start_x, int column_count, int height)
void DrawRowMajor(gfx::BackgroundBuffer &bg, int base_x, int base_y, int w, int h, std::span< const gfx::TileInfo > tiles, size_t start_index=0)
void DrawOpenChestPlatformSegment(const DrawContext &ctx, int dy, size_t start_index, int fill_width)
void Draw4x4ColumnMajor(const DrawContext &ctx, int x_offset, int y_offset, size_t start_index)
void DrawWaterFaceRows(const DrawContext &ctx, int row_count, int tile_offset)
void Draw1x3NRightwards(const DrawContext &ctx, int columns, size_t start_index, int y_offset=0)
void DrawPlatform3x2(const DrawContext &ctx, int dx, int dy, size_t start_index)
const gfx::TileInfo & TileAtWrapped(std::span< const gfx::TileInfo > tiles, size_t index)
void Draw4x4ColumnMajorTo(gfx::BackgroundBuffer &bg, const RoomObject &object, std::span< const gfx::TileInfo > tiles)
void Draw1x5Column(const DrawContext &ctx, int x_offset, size_t start_index)
void DrawMany32x32Block(gfx::BackgroundBuffer &bg, int base_x, int base_y, std::span< const gfx::TileInfo > tiles)
void DrawPlatform2x2(const DrawContext &ctx, int dx, int dy, size_t start_index)
void WritePlatformTile(const DrawContext &ctx, int dx, int dy, size_t index)
void DrawColumnMajor(gfx::BackgroundBuffer &bg, int base_x, int base_y, int w, int h, std::span< const gfx::TileInfo > tiles, size_t start_index=0)
void DrawPlatform1x3Rightwards(const DrawContext &ctx, int dx, int dy, size_t start_index, int columns)
void DrawTableRock4x4_1to16(const DrawContext &ctx)
Draw 4x4 table rock pattern.
void DrawInterRoomFatStairsDownA(const DrawContext &ctx)
Draw inter-room fat stairs going down A (Type 2 object 0x12E)
void DrawSingle2x2(const DrawContext &ctx)
Draw a single 2x2 block (column-major order)
void DrawWaterfall47(const DrawContext &ctx)
Draw waterfall object 0x47 pattern.
void DrawHorizontalTurtleRockPipe(const DrawContext &ctx)
Draw horizontal Turtle Rock pipe (6x4)
void DrawAutoStairs(const DrawContext &ctx)
Draw auto stairs (Type 2/3 objects 0x130-0x133, 0x21B-0x21D, 0x233)
void DrawSpittingWaterFace(const DrawContext &ctx)
Draw spitting water face (Type 3 object 0x201)
void DrawAgahnimsAltar(const DrawContext &ctx)
Draw the fixed 14x14 Agahnim altar with mirrored right half.
void DrawLargeCanvasObject(const DrawContext &ctx, int width, int height)
Draw large canvas object with arbitrary dimensions.
void DrawMovingWallEast(const DrawContext &ctx)
Draw the east-moving wall (Type 1 object 0xCE)
void DrawBigGrayRock(const DrawContext &ctx)
Draw the fixed 4x4 big gray rock as two column-major 4x2 bands.
void DrawChest(const DrawContext &ctx, int chest_index)
Draw a small chest with open/closed state support.
void DrawMovingWallWest(const DrawContext &ctx)
Draw the west-moving wall (Type 1 object 0xCD)
void DrawBed4x5(const DrawContext &ctx)
Draw a 4x5 bed pattern (row-major)
void DrawSingle4x4(const DrawContext &ctx)
Draw a single 4x4 block (column-major order)
void DrawUtility3x5(const DrawContext &ctx)
Draw utility 3x5 pattern (special row pattern)
void DrawDoorSwitcherer(const DrawContext &ctx)
Draw door switcher object with state-based graphics.
void Draw4x4BlocksIn4x4SuperSquare(const DrawContext &ctx)
Draw 4x4 solid blocks in a super square grid.
void DrawBigHole4x4_1to16(const DrawContext &ctx)
Draw 4x4 big hole pattern.
void DrawFloorLight(const DrawContext &ctx)
Draw unconditional floor light (8x8 footprint)
void Draw4x4FloorTwoIn4x4SuperSquare(const DrawContext &ctx)
Draw two 4x4 floor pattern variant.
void DrawSolidWallDecor3x4(const DrawContext &ctx)
Draw solid wall decor 3x4.
void DrawLightBeamOnFloor(const DrawContext &ctx)
Draw floor light beam composed of three 4x4 blocks.
void DrawWaterFace(const DrawContext &ctx)
Draw a generic 2x2 water-face helper pattern.
void DrawTableBowl(const DrawContext &ctx)
Draw a fixed 4x2 table bowl in row-major order.
void DrawChestPlatformVerticalWall(const DrawContext &ctx)
Draw chest platform vertical wall section.
void DrawClosedChestPlatform(const DrawContext &ctx)
Draw closed chest platform (Type 1 object 0xC1)
void DrawSmithyFurnace(const DrawContext &ctx)
Draw the fixed 6x8 smithy furnace in row-major order.
void DrawVitreousGooDamage(const DrawContext &ctx)
Draw the fixed 5x2 grid of repeated 4x4 Vitreous goo stamps.
void DrawSpike2x2In4x4SuperSquare(const DrawContext &ctx)
Draw 2x2 spike pattern in super square units.
void DrawSingle4x3(const DrawContext &ctx)
Draw a single 4x3 block (column-major order)
void DrawBigLightBeamOnFloor(const DrawContext &ctx)
Draw state-gated big floor light beam active variant (8x8 footprint)
void DrawOpenChestPlatform(const DrawContext &ctx)
Draw open chest platform (Type 1 object 0xDC)
void DrawSpiralStairs(const DrawContext &ctx, bool going_up, bool is_upper)
Draw spiral stairs (Type 2 objects 0x138-0x13B)
void DrawFortuneTellerRoom(const DrawContext &ctx)
Draw the fixed 14x14 fortune teller room on BG1.
void DrawSomariaLine(const DrawContext &ctx)
Draw one Somaria path tile at the object anchor.
void DrawWaterfall48(const DrawContext &ctx)
Draw waterfall object 0x48 pattern.
void DrawVerticalTurtleRockPipe(const DrawContext &ctx)
Draw vertical Turtle Rock pipe (two stacked 4x3 sections)
void DrawBombableFloor(const DrawContext &ctx)
Draw bombable floor (Type 3 object 0xFC7 / ASM object 0x247)
void DrawDrenchingWaterFace(const DrawContext &ctx)
Draw drenching water face (Type 3 object 0x202)
void DrawEmptyWaterFace(const DrawContext &ctx)
Draw empty water face (Type 3 object 0x200)
void DrawBossShell4x4(const DrawContext &ctx)
Draw boss shell 4x4.
void CustomDraw(const DrawContext &ctx)
Custom draw routine for special objects.
void DrawBigWallDecor(const DrawContext &ctx)
Draw fixed 8x3 big wall decor in column-major order.
void Draw4x4FloorIn4x4SuperSquare(const DrawContext &ctx)
Draw 4x4 floor pattern in super square units.
void DrawInterRoomFatStairsUp(const DrawContext &ctx)
Draw inter-room fat stairs going up (Type 2 object 0x12D)
void Draw3x3FloorIn4x4SuperSquare(const DrawContext &ctx)
Draw 3x3 floor pattern in super square units.
void DrawArcheryGameTargetDoor(const DrawContext &ctx)
Draw archery game target door (two 3x3 sections)
void DrawStraightInterRoomStairs(const DrawContext &ctx)
Draw straight inter-room stairs (Type 3 objects 0x21E-0x229)
void DrawGanonTriforceFloorDecor(const DrawContext &ctx)
Draw Ganon triforce floor decor (three 4x4 sections)
void RegisterSpecialRoutines(std::vector< DrawRoutineInfo > &registry)
Register all special/miscellaneous draw routines to the registry.
void DrawInterRoomFatStairsDownB(const DrawContext &ctx)
Draw inter-room fat stairs going down B (Type 2 object 0x12F)
void DrawUtility6x3(const DrawContext &ctx)
Draw utility 6x3 pattern via RoomDraw_1x3N_rightwards.
void DrawRightwards3x6(const DrawContext &ctx)
Draw a 6x3 pattern via RoomDraw_1x3N_rightwards semantics.
void DrawMagicBatAltar(const DrawContext &ctx)
Draw the fixed 8x7 Magic Bat altar in column-major order.
void DrawChestPlatformHorizontalWall(const DrawContext &ctx)
Draw chest platform horizontal wall section.
void DrawWaterOverlay8x8_1to16(const DrawContext &ctx)
Draw water overlay 8x8 pattern.
void DrawRupeeFloor(const DrawContext &ctx)
Draw the blue rupee floor pattern (5x8 bounds with gaps)
void Draw4x4FloorOneIn4x4SuperSquare(const DrawContext &ctx)
Draw single 4x4 floor pattern variant.
void DrawNothing(const DrawContext &ctx)
Draw nothing - represents invisible logic objects or placeholders.
void DrawActual4x4(const DrawContext &ctx)
Draw an actual 4x4 tile8 pattern (column-major order)
void DrawBigKeyLock(const DrawContext &ctx)
Draw a closed big key lock (Yaze 0xF98 / ASM object 0x218)
void DrawPrisonCell(const DrawContext &ctx)
Draw prison cell with bars (Type 3 objects 0x20D, 0x217)
constexpr int DirectionForSize(int size)
constexpr int ObjectCountForSize(int size)
constexpr bool IsFullBothAutoStairsObject(int object_id)
constexpr bool IsSouthMixedStraightInterroomObject(int object_id)
constexpr bool IsMixedStraightInterroomObject(int object_id)
constexpr bool IsFixedBg1StraightInterroomObject(int object_id)
constexpr int kRoomObjectTileAddress
Definition room_object.h:48
Context passed to draw routines containing all necessary state.
std::span< const gfx::TileInfo > tiles
gfx::BackgroundBuffer & target_bg
gfx::BackgroundBuffer * secondary_bg
const DungeonState * state
Metadata about a draw routine.