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
5#include "core/features.h"
6#include "util/log.h"
11
12namespace yaze {
13namespace zelda3 {
14namespace draw_routines {
15
16namespace {
17
18const gfx::TileInfo& TileAtWrapped(std::span<const gfx::TileInfo> tiles,
19 size_t index) {
20 return tiles[index % tiles.size()];
21}
22
23void DrawColumnMajor(gfx::BackgroundBuffer& bg, int base_x, int base_y, int w,
24 int h, std::span<const gfx::TileInfo> tiles,
25 size_t start_index = 0) {
26 if (tiles.empty())
27 return;
28
29 size_t index = start_index;
30 for (int x = 0; x < w; ++x) {
31 for (int y = 0; y < h; ++y) {
32 DrawRoutineUtils::WriteTile8(bg, base_x + x, base_y + y,
33 TileAtWrapped(tiles, index++));
34 }
35 }
36}
37
38void DrawRowMajor(gfx::BackgroundBuffer& bg, int base_x, int base_y, int w,
39 int h, std::span<const gfx::TileInfo> tiles,
40 size_t start_index = 0) {
41 if (tiles.empty())
42 return;
43
44 size_t index = start_index;
45 for (int y = 0; y < h; ++y) {
46 for (int x = 0; x < w; ++x) {
47 DrawRoutineUtils::WriteTile8(bg, base_x + x, base_y + y,
48 TileAtWrapped(tiles, index++));
49 }
50 }
51}
52
53void Draw1x3NRightwards(const DrawContext& ctx, int columns, size_t start_index,
54 int y_offset = 0) {
55 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_ + y_offset,
56 columns, 3, ctx.tiles, start_index);
57}
58
59void DrawNx4(const DrawContext& ctx, int columns, size_t start_index) {
60 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, columns, 4,
61 ctx.tiles, start_index);
62}
63
64void Draw1x5Column(const DrawContext& ctx, int x_offset, size_t start_index) {
65 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + x_offset, ctx.object.y_, 1, 5,
66 ctx.tiles, start_index);
67}
68
69void Draw4x4ColumnMajor(const DrawContext& ctx, int x_offset, int y_offset,
70 size_t start_index) {
71 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + x_offset,
72 ctx.object.y_ + y_offset, 4, 4, ctx.tiles, start_index);
73}
74
75} // namespace
76
77void DrawChest(const DrawContext& ctx, int chest_index) {
78 // Determine if chest is open
79 bool is_open = false;
80 if (ctx.state) {
81 is_open = ctx.state->IsChestOpen(ctx.room_id, chest_index);
82 }
83
84 // Draw logic
85 // Heuristic: If we have extra tiles loaded, assume they are for the open
86 // state. Standard chests are 2x2 (4 tiles). Big chests are 4x4 (16 tiles). If
87 // we have double the tiles, use the second half for open state.
88
89 if (is_open) {
90 if (ctx.tiles.size() >= 32) {
91 // Big chest open tiles (indices 16-31)
92 // Draw 4x4 pattern
93 for (int x = 0; x < 4; ++x) {
94 for (int y = 0; y < 4; ++y) {
96 ctx.object.y_ + y,
97 ctx.tiles[16 + x * 4 + y]);
98 }
99 }
100 return;
101 }
102 if (ctx.tiles.size() >= 8 && ctx.tiles.size() < 16) {
103 // Small chest open tiles (indices 4-7)
105 ctx.tiles[4]);
107 ctx.object.y_ + 1, ctx.tiles[5]);
109 ctx.object.y_, ctx.tiles[6]);
111 ctx.object.y_ + 1, ctx.tiles[7]);
112 return;
113 }
114 // If no extra tiles, fall through to draw closed chest (better than
115 // nothing)
116 }
117
118 // Fallback to standard 4x4 or 2x2 drawing based on tile count
119 if (ctx.tiles.size() >= 16) {
120 // Draw 4x4 pattern in COLUMN-MAJOR order
121 for (int x = 0; x < 4; ++x) {
122 for (int y = 0; y < 4; ++y) {
124 ctx.object.y_ + y, ctx.tiles[x * 4 + y]);
125 }
126 }
127 } else if (ctx.tiles.size() >= 4) {
128 // Draw 2x2 pattern in COLUMN-MAJOR order
130 ctx.tiles[0]);
132 ctx.object.y_ + 1, ctx.tiles[1]);
134 ctx.object.y_, ctx.tiles[2]);
136 ctx.object.y_ + 1, ctx.tiles[3]);
137 }
138}
139
140void DrawNothing([[maybe_unused]] const DrawContext& ctx) {
141 // Intentionally empty - represents invisible logic objects or placeholders
142 // ASM: RoomDraw_Nothing_A ($0190F2), RoomDraw_Nothing_B ($01932E), etc.
143 // These routines typically just RTS.
144}
145
146void CustomDraw(const DrawContext& ctx) {
147 // Pattern: Custom draw routine (objects 0x31-0x32)
148 // When custom objects are enabled, load tile data from external binary files
149 // managed by CustomObjectManager. Each binary encodes SNES tilemap entries
150 // with relative x/y positions computed from the buffer stride layout.
151
152 if (!core::FeatureFlags::get().kEnableCustomObjects) {
153 // Feature disabled: fall back to vanilla 1x1 draw from ROM tile span
154 if (ctx.tiles.size() >= 1) {
156 ctx.tiles[0]);
157 }
158 return;
159 }
160
161 // Look up the custom object by ID and subtype.
162 // ctx.object.id_ is 0x31 or 0x32; ctx.object.size_ encodes the subtype.
164 ctx.object.size_);
165
166 if (!result.ok() || !result.value() || result.value()->IsEmpty()) {
167 // Custom object not found or empty: fall back to 1x1 draw
168 if (ctx.tiles.size() >= 1) {
170 ctx.tiles[0]);
171 }
172 return;
173 }
174
175 const auto& custom_obj = *result.value();
176
177 for (const auto& entry : custom_obj.tiles) {
178 // Convert SNES tilemap word (vhopppcc cccccccc) to TileInfo.
179 // Low byte = entry.tile_data & 0xFF, high byte = (entry.tile_data >> 8).
180 uint8_t lo = static_cast<uint8_t>(entry.tile_data & 0xFF);
181 uint8_t hi = static_cast<uint8_t>((entry.tile_data >> 8) & 0xFF);
182 gfx::TileInfo tile_info(lo, hi);
183
184 // rel_x/rel_y are already decoded as object-relative coordinates from the
185 // binary stream's buffer position arithmetic; preserve those offsets.
186 int draw_x = ctx.object.x_ + entry.rel_x;
187 int draw_y = ctx.object.y_ + entry.rel_y;
188
189 DrawRoutineUtils::WriteTile8(ctx.target_bg, draw_x, draw_y, tile_info);
190 }
191}
192
194 // Pattern: Door switcher (object 0x35)
195 // Special door logic
196 // Check state to decide graphic
197 int tile_index = 0;
198 if (ctx.state && ctx.state->IsDoorSwitchActive(ctx.room_id)) {
199 // Use active tile if available (assuming 2nd tile is active state)
200 if (ctx.tiles.size() >= 2) {
201 tile_index = 1;
202 }
203 }
204
205 if (ctx.tiles.size() > static_cast<size_t>(tile_index)) {
207 ctx.tiles[tile_index]);
208 }
209}
210
211void DrawSomariaLine(const DrawContext& ctx) {
212 // Pattern: Somaria Line (objects 0x203-0x20F, 0x214)
213 // Draws a line of tiles based on direction encoded in object ID
214 // Direction mapping based on ZScream reference:
215 // 0x03: Horizontal right
216 // 0x04: Vertical down
217 // 0x05: Diagonal down-right
218 // 0x06: Diagonal down-left
219 // 0x07-0x09: Variations
220 // 0x0A-0x0C: More variations
221 // 0x0E-0x0F: Additional patterns
222 // 0x14: Another line type
223
224 if (ctx.tiles.empty())
225 return;
226
227 int length = (ctx.object.size_ & 0x0F) + 1;
228 int obj_subid = ctx.object.id_ & 0x0F; // Low nibble determines direction
229
230 // Determine direction based on object sub-ID
231 int dx = 1, dy = 0; // Default: horizontal right
232 switch (obj_subid) {
233 case 0x03:
234 dx = 1;
235 dy = 0;
236 break; // Horizontal right
237 case 0x04:
238 dx = 0;
239 dy = 1;
240 break; // Vertical down
241 case 0x05:
242 dx = 1;
243 dy = 1;
244 break; // Diagonal down-right
245 case 0x06:
246 dx = -1;
247 dy = 1;
248 break; // Diagonal down-left
249 case 0x07:
250 dx = 1;
251 dy = 0;
252 break; // Horizontal (variant)
253 case 0x08:
254 dx = 0;
255 dy = 1;
256 break; // Vertical (variant)
257 case 0x09:
258 dx = 1;
259 dy = 1;
260 break; // Diagonal (variant)
261 case 0x0A:
262 dx = 1;
263 dy = 0;
264 break; // Horizontal
265 case 0x0B:
266 dx = 0;
267 dy = 1;
268 break; // Vertical
269 case 0x0C:
270 dx = 1;
271 dy = 1;
272 break; // Diagonal
273 case 0x0E:
274 dx = 1;
275 dy = 0;
276 break; // Horizontal
277 case 0x0F:
278 dx = 0;
279 dy = 1;
280 break; // Vertical
281 default:
282 dx = 1;
283 dy = 0;
284 break; // Default horizontal
285 }
286
287 // Draw tiles along the path using first tile (Somaria uses single tile)
288 for (int i = 0; i < length; ++i) {
289 size_t tile_idx = i % ctx.tiles.size(); // Cycle through tiles if multiple
291 ctx.object.y_ + (i * dy), ctx.tiles[tile_idx]);
292 }
293}
294
295void DrawWaterFace(const DrawContext& ctx) {
296 // Pattern: Water Face (Type 3 objects 0xF80-0xF82)
297 // Draws a 2x2 face in COLUMN-MAJOR order
298 // TODO: Implement state check from RoomDraw_EmptyWaterFace ($019D29)
299 // Checks Room ID ($AF), Room State ($7EF000), Door Flags ($0403) to switch
300 // graphic
301 if (ctx.tiles.size() >= 4) {
303 ctx.tiles[0]); // col 0, row 0
305 ctx.object.y_ + 1,
306 ctx.tiles[1]); // col 0, row 1
308 ctx.object.y_,
309 ctx.tiles[2]); // col 1, row 0
311 ctx.object.y_ + 1,
312 ctx.tiles[3]); // col 1, row 1
313 }
314}
315
316void DrawLargeCanvasObject(const DrawContext& ctx, int width, int height) {
317 // Generic large object drawer
318 if (ctx.tiles.size() >= static_cast<size_t>(width * height)) {
319 for (int y = 0; y < height; ++y) {
320 for (int x = 0; x < width; ++x) {
322 ctx.object.y_ + y,
323 ctx.tiles[y * width + x]);
324 }
325 }
326 }
327}
328
329void DrawBed4x5(const DrawContext& ctx) {
330 // ASM: RoomDraw_Bed4x5 ($019AEE) writes 4 columns per row, 5 rows total.
331 DrawRowMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, 4, 5, ctx.tiles);
332}
333
335 // ASM: RoomDraw_DrawRightwards3x6 ($019B50) is RoomDraw_1x3N_rightwards with
336 // A=6 -> 6 columns x 3 rows, column-major.
337 Draw1x3NRightwards(ctx, /*columns=*/6, /*start_index=*/0);
338}
339
340void DrawUtility6x3(const DrawContext& ctx) {
341 // ASM: RoomDraw_Utility6x3 ($019A0C) is also 1x3N_rightwards with A=6.
342 Draw1x3NRightwards(ctx, /*columns=*/6, /*start_index=*/0);
343}
344
345void DrawUtility3x5(const DrawContext& ctx) {
346 // ASM: RoomDraw_Utility3x5 ($01A194) uses:
347 // - top row: tiles 0..2
348 // - middle 3 rows: tiles 3..5 repeated per row
349 // - bottom row: tiles 6..8
350 if (ctx.tiles.empty())
351 return;
352
353 // Top row.
354 for (int x = 0; x < 3; ++x) {
356 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_,
357 TileAtWrapped(ctx.tiles, static_cast<size_t>(x)));
358 }
359
360 // Middle rows (rows 1..3) reuse tiles 3..5.
361 for (int y = 1; y <= 3; ++y) {
362 for (int x = 0; x < 3; ++x) {
364 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_ + y,
365 TileAtWrapped(ctx.tiles, static_cast<size_t>(3 + x)));
366 }
367 }
368
369 // Bottom row.
370 for (int x = 0; x < 3; ++x) {
372 ctx.target_bg, ctx.object.x_ + x, ctx.object.y_ + 4,
373 TileAtWrapped(ctx.tiles, static_cast<size_t>(6 + x)));
374 }
375}
376
378 // ASM: RoomDraw_VerticalTurtleRockPipe ($019A90)
379 // Two stacked 4x3 sections: first uses tiles 0..11, second 12..23.
380 if (ctx.tiles.empty())
381 return;
382 Draw1x3NRightwards(ctx, /*columns=*/4, /*start_index=*/0, /*y_offset=*/0);
383 Draw1x3NRightwards(ctx, /*columns=*/4, /*start_index=*/12, /*y_offset=*/3);
384}
385
387 // ASM: RoomDraw_HorizontalTurtleRockPipe ($019AA3) -> RoomDraw_Nx4 with A=6.
388 DrawNx4(ctx, /*columns=*/6, /*start_index=*/0);
389}
390
392 // ASM: RoomDraw_LightBeamOnFloor ($01A7B6)
393 // Draw three 4x4 blocks at y offsets 0, +2, +6.
394 if (ctx.tiles.empty())
395 return;
396 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/0, /*start_index=*/0);
397 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/2, /*start_index=*/16);
398 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/6, /*start_index=*/32);
399}
400
402 // ASM: RoomDraw_BigLightBeamOnFloor / RoomDraw_FloorLight
403 // The active path draws four 4x4 blocks in a 2x2 grid (8x8 footprint).
404 // State-gating on $7EF0CA is not currently modeled in DungeonState.
405 if (ctx.tiles.empty())
406 return;
407
408 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/0, /*start_index=*/0);
409 Draw4x4ColumnMajor(ctx, /*x_offset=*/4, /*y_offset=*/0, /*start_index=*/16);
410 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/4, /*start_index=*/32);
411 Draw4x4ColumnMajor(ctx, /*x_offset=*/4, /*y_offset=*/4, /*start_index=*/48);
412}
413
415 // ASM-mapped objects route through RoomDraw_4x4.
416 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/0, /*start_index=*/0);
417}
418
420 // ASM: RoomDraw_SolidWallDecor3x4 ($0199EC) -> RoomDraw_Nx4 with A=3.
421 DrawNx4(ctx, /*columns=*/3, /*start_index=*/0);
422}
423
425 // ASM: RoomDraw_ArcheryGameTargetDoor ($01A7A3)
426 // Two 3x3 sections stacked vertically.
427 if (ctx.tiles.empty())
428 return;
429 Draw1x3NRightwards(ctx, /*columns=*/3, /*start_index=*/0, /*y_offset=*/0);
430 Draw1x3NRightwards(ctx, /*columns=*/3, /*start_index=*/9, /*y_offset=*/3);
431}
432
434 // ASM: RoomDraw_GanonTriforceFloorDecor ($01A7F0)
435 // Top block uses 0..15 at +2 X, then two bottom 4x4 blocks use 16..31.
436 if (ctx.tiles.empty())
437 return;
438
439 Draw4x4ColumnMajor(ctx, /*x_offset=*/2, /*y_offset=*/0, /*start_index=*/0);
440 Draw4x4ColumnMajor(ctx, /*x_offset=*/0, /*y_offset=*/4, /*start_index=*/16);
441 Draw4x4ColumnMajor(ctx, /*x_offset=*/4, /*y_offset=*/4, /*start_index=*/16);
442}
443
444void DrawSingle2x2(const DrawContext& ctx) {
445 // ASM: RoomDraw_Single2x2 ($019A8D) -> RoomDraw_Downwards2x2
446 // Single 2x2 in column-major order.
447 if (ctx.tiles.size() < 4)
448 return;
449
451 ctx.tiles[0]);
453 ctx.tiles[1]);
455 ctx.tiles[2]);
457 ctx.object.y_ + 1, ctx.tiles[3]);
458}
459
460void DrawSingle4x4(const DrawContext& ctx) {
461 // ASM: RoomDraw_4x4 ($0197ED), column-major 4x4 block (16 tiles).
462 if (ctx.tiles.size() < 16)
463 return;
464
465 int tid = 0;
466 for (int x = 0; x < 4; ++x) {
467 for (int y = 0; y < 4; ++y) {
469 ctx.object.y_ + y, ctx.tiles[tid++]);
470 }
471 }
472}
473
474void DrawSingle4x3(const DrawContext& ctx) {
475 // ASM: RoomDraw_TableRock4x3 ($0199E6), column-major 4x3 block (12 tiles).
476 if (ctx.tiles.size() < 12)
477 return;
478
479 int tid = 0;
480 for (int x = 0; x < 4; ++x) {
481 for (int y = 0; y < 3; ++y) {
483 ctx.object.y_ + y, ctx.tiles[tid++]);
484 }
485 }
486}
487
488void DrawRupeeFloor(const DrawContext& ctx) {
489 // ASM: RoomDraw_RupeeFloor ($019AA9), preview shape:
490 // 3 columns of 2-tile pairs at rows [0..1], [3..4], [6..7].
491 if (ctx.tiles.size() < 2)
492 return;
493
494 for (int col = 0; col < 3; ++col) {
495 int x = ctx.object.x_ + (col * 2);
496
499 ctx.tiles[0]);
501 ctx.tiles[1]);
503 ctx.tiles[1]);
504
506 ctx.tiles[0]);
508 ctx.tiles[0]);
510 ctx.tiles[1]);
512 ctx.tiles[1]);
513
515 ctx.tiles[0]);
517 ctx.tiles[0]);
519 ctx.tiles[1]);
521 ctx.tiles[1]);
522 }
523}
524
525void DrawActual4x4(const DrawContext& ctx) {
526 // ASM: RoomDraw_4x4 ($0197ED), used for true 4x4 tile8 objects.
527 DrawSingle4x4(ctx);
528}
529
530void DrawWaterfall47(const DrawContext& ctx) {
531 // ASM: RoomDraw_Waterfall47 ($019466)
532 // First 1x5 column from 0..4, middle columns from 5..9, final from 10..14.
533 if (ctx.tiles.empty())
534 return;
535
536 const int size = ctx.object.size_ & 0x0F;
537 const int middle_columns = (size + 1) * 2; // ASL $B2
538
539 Draw1x5Column(ctx, /*x_offset=*/0, /*start_index=*/0);
540 for (int i = 0; i < middle_columns; ++i) {
541 Draw1x5Column(ctx, /*x_offset=*/1 + i, /*start_index=*/5);
542 }
543 Draw1x5Column(ctx, /*x_offset=*/1 + middle_columns, /*start_index=*/10);
544}
545
546void DrawWaterfall48(const DrawContext& ctx) {
547 // ASM: RoomDraw_Waterfall48 ($019488)
548 // First 1x3 column from 0..2, middle columns from 3..5, final from 6..8.
549 if (ctx.tiles.empty())
550 return;
551
552 const int size = ctx.object.size_ & 0x0F;
553 const int middle_columns = (size + 1) * 2; // ASL $B2
554
555 DrawColumnMajor(ctx.target_bg, ctx.object.x_, ctx.object.y_, /*w=*/1, /*h=*/3,
556 ctx.tiles, /*start_index=*/0);
557 for (int i = 0; i < middle_columns; ++i) {
558 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + 1 + i, ctx.object.y_,
559 /*w=*/1, /*h=*/3, ctx.tiles, /*start_index=*/3);
560 }
561 DrawColumnMajor(ctx.target_bg, ctx.object.x_ + 1 + middle_columns,
562 ctx.object.y_, /*w=*/1, /*h=*/3, ctx.tiles,
563 /*start_index=*/6);
564}
565
566// ============================================================================
567// SuperSquare Routines (Phase 4)
568// ============================================================================
569// A "SuperSquare" is a 16x16 tile area (4 rows of 4 tiles each).
570// These routines draw floor patterns that repeat in super square units.
571
573 // ASM: RoomDraw_4x4BlocksIn4x4SuperSquare ($018B94)
574 // Draws solid 4x4 blocks using a single tile, repeated in a grid pattern.
575 // Size determines number of super squares in X and Y directions.
576 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
577 int size_y = (ctx.object.size_ & 0x03) + 1;
578
579 LOG_DEBUG("DrawRoutines",
580 "Draw4x4BlocksIn4x4SuperSquare: obj=0x%03X pos=(%d,%d) size=0x%02X "
581 "tiles=%zu size_x=%d size_y=%d",
582 ctx.object.id_, ctx.object.x_, ctx.object.y_, ctx.object.size_,
583 ctx.tiles.size(), size_x, size_y);
584
585 if (ctx.tiles.empty()) {
586 LOG_DEBUG("DrawRoutines",
587 "Draw4x4BlocksIn4x4SuperSquare: SKIPPING - no tiles loaded!");
588 return;
589 }
590
591 // Use first tile for all blocks
592 const auto& tile = ctx.tiles[0];
593 LOG_DEBUG("DrawRoutines",
594 "Draw4x4BlocksIn4x4SuperSquare: tile[0] id=%d palette=%d", tile.id_,
595 tile.palette_);
596
597 for (int sy = 0; sy < size_y; ++sy) {
598 for (int sx = 0; sx < size_x; ++sx) {
599 // Each super square is 4x4 tiles
600 int base_x = ctx.object.x_ + (sx * 4);
601 int base_y = ctx.object.y_ + (sy * 4);
602
603 // Draw 4x4 block with same tile
604 for (int y = 0; y < 4; ++y) {
605 for (int x = 0; x < 4; ++x) {
606 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
607 tile);
608 }
609 }
610 }
611 }
612}
613
615 // ASM: RoomDraw_3x3FloorIn4x4SuperSquare ($018D8A)
616 // Draws 3x3 floor patterns within super square units.
617 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
618 int size_y = (ctx.object.size_ & 0x03) + 1;
619
620 if (ctx.tiles.empty())
621 return;
622
623 const auto& tile = ctx.tiles[0];
624 for (int sy = 0; sy < size_y; ++sy) {
625 for (int sx = 0; sx < size_x; ++sx) {
626 int base_x = ctx.object.x_ + (sx * 3);
627 int base_y = ctx.object.y_ + (sy * 3);
628
629 for (int y = 0; y < 3; ++y) {
630 for (int x = 0; x < 3; ++x) {
631 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
632 tile);
633 }
634 }
635 }
636 }
637}
638
640 // ASM: RoomDraw_4x4FloorIn4x4SuperSquare ($018FA5)
641 // Most common floor pattern - draws 4x4 floor tiles in super square units.
642 // Uses RoomDraw_A_Many32x32Blocks internally in assembly.
643 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
644 int size_y = (ctx.object.size_ & 0x03) + 1;
645
646 if (ctx.tiles.empty())
647 return;
648 if (ctx.tiles.size() < 8) {
649 // Some hacks provide abbreviated tile payloads for these objects.
650 // Fall back to a visible fill instead of silently skipping draw.
652 return;
653 }
654
655 for (int sy = 0; sy < size_y; ++sy) {
656 for (int sx = 0; sx < size_x; ++sx) {
657 int base_x = ctx.object.x_ + (sx * 4);
658 int base_y = ctx.object.y_ + (sy * 4);
659
660 // Tile order is COLUMN-MAJOR 4x2, matching RoomDraw_A_Many32x32Blocks:
661 // [col0 row0, col0 row1, col1 row0, col1 row1, ...].
662 for (int x = 0; x < 4; ++x) {
663 const auto& row0 = ctx.tiles[(x * 2) + 0];
664 const auto& row1 = ctx.tiles[(x * 2) + 1];
665
666 // Top half (rows 0-1)
667 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 0,
668 row0);
669 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 1,
670 row1);
671
672 // Bottom half (rows 2-3) repeats the same 4x2 pattern.
673 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 2,
674 row0);
675 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 3,
676 row1);
677 }
678 }
679 }
680}
681
683 // ASM: RoomDraw_4x4FloorOneIn4x4SuperSquare ($018FA2)
684 // Single 4x4 floor pattern (starts at different tile offset in assembly).
685 // For our purposes, same as 4x4FloorIn4x4SuperSquare with offset tiles.
686 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
687 int size_y = (ctx.object.size_ & 0x03) + 1;
688
689 if (ctx.tiles.size() < 8) {
691 return;
692 }
693
694 for (int sy = 0; sy < size_y; ++sy) {
695 for (int sx = 0; sx < size_x; ++sx) {
696 int base_x = ctx.object.x_ + (sx * 4);
697 int base_y = ctx.object.y_ + (sy * 4);
698
699 for (int x = 0; x < 4; ++x) {
700 const auto& row0 = ctx.tiles[(x * 2) + 0];
701 const auto& row1 = ctx.tiles[(x * 2) + 1];
702
703 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 0,
704 row0);
705 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 1,
706 row1);
707 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 2,
708 row0);
709 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 3,
710 row1);
711 }
712 }
713 }
714}
715
717 // ASM: RoomDraw_4x4FloorTwoIn4x4SuperSquare ($018F9D)
718 // Two 4x4 floor patterns (uses $0490 offset in assembly).
719 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
720 int size_y = (ctx.object.size_ & 0x03) + 1;
721
722 if (ctx.tiles.size() < 8) {
724 return;
725 }
726
727 for (int sy = 0; sy < size_y; ++sy) {
728 for (int sx = 0; sx < size_x; ++sx) {
729 int base_x = ctx.object.x_ + (sx * 4);
730 int base_y = ctx.object.y_ + (sy * 4);
731
732 for (int x = 0; x < 4; ++x) {
733 const auto& row0 = ctx.tiles[(x * 2) + 0];
734 const auto& row1 = ctx.tiles[(x * 2) + 1];
735
736 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 0,
737 row0);
738 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 1,
739 row1);
740 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 2,
741 row0);
742 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 3,
743 row1);
744 }
745 }
746 }
747}
748
750 // ASM: Object 0xA4 - Big hole pattern
751 // Draws a rectangular hole with border tiles using Size as the expansion.
752 int size = ctx.object.size_ & 0x0F;
753
754 if (ctx.tiles.size() < 24)
755 return;
756
757 int base_x = ctx.object.x_;
758 int base_y = ctx.object.y_;
759 int max = size + 3; // Bottom/right edge offset
760
761 // Corners
762 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y, ctx.tiles[8]);
763 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + max, base_y,
764 ctx.tiles[14]);
765 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y + max,
766 ctx.tiles[17]);
767 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + max, base_y + max,
768 ctx.tiles[23]);
769
770 // Edges and interior
771 for (int xx = 1; xx < max; ++xx) {
772 for (int yy = 1; yy < max; ++yy) {
773 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + xx, base_y + yy,
774 ctx.tiles[0]);
775 }
776 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + xx, base_y,
777 ctx.tiles[10]);
778 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + xx, base_y + max,
779 ctx.tiles[19]);
780 }
781
782 for (int yy = 1; yy < max; ++yy) {
783 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y + yy,
784 ctx.tiles[9]);
785 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + max, base_y + yy,
786 ctx.tiles[15]);
787 }
788}
789
791 // ASM: RoomDraw_Spike2x2In4x4SuperSquare ($019708)
792 // Draws 2x2 spike patterns in a tiled grid
793 int size_x = ((ctx.object.size_ >> 2) & 0x03) + 1;
794 int size_y = (ctx.object.size_ & 0x03) + 1;
795
796 if (ctx.tiles.size() < 4)
797 return;
798
799 for (int sy = 0; sy < size_y; ++sy) {
800 for (int sx = 0; sx < size_x; ++sx) {
801 int base_x = ctx.object.x_ + (sx * 2);
802 int base_y = ctx.object.y_ + (sy * 2);
803
804 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y, ctx.tiles[0]);
805 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y,
806 ctx.tiles[2]);
807 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x, base_y + 1,
808 ctx.tiles[1]);
809 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 1,
810 ctx.tiles[3]);
811 }
812 }
813}
814
816 // ASM: Object 0xDD - Table rock pattern
817 int size_x = ((ctx.object.size_ >> 2) & 0x03);
818 int size_y = (ctx.object.size_ & 0x03);
819
820 if (ctx.tiles.size() < 16)
821 return;
822
823 int right_x = ctx.object.x_ + (3 + (size_x * 2));
824 int bottom_y = ctx.object.y_ + (3 + (size_y * 2));
825
826 // Interior
827 for (int xx = 0; xx < size_x + 1; ++xx) {
828 for (int yy = 0; yy < size_y + 1; ++yy) {
829 int base_x = ctx.object.x_ + (xx * 2);
830 int base_y = ctx.object.y_ + (yy * 2);
831 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 1,
832 ctx.tiles[5]);
833 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, base_y + 1,
834 ctx.tiles[6]);
835 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 2,
836 ctx.tiles[9]);
837 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, base_y + 2,
838 ctx.tiles[10]);
839 }
840 }
841
842 // Left/right borders
843 for (int yy = 0; yy < size_y + 1; ++yy) {
844 int base_y = ctx.object.y_ + (yy * 2);
845 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, base_y + 1,
846 ctx.tiles[4]);
847 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, base_y + 2,
848 ctx.tiles[8]);
849
850 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 1,
851 ctx.tiles[7]);
852 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, base_y + 2,
853 ctx.tiles[11]);
854 }
855
856 // Top/bottom borders
857 for (int xx = 0; xx < size_x + 1; ++xx) {
858 int base_x = ctx.object.x_ + (xx * 2);
859 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, ctx.object.y_,
860 ctx.tiles[1]);
861 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, ctx.object.y_,
862 ctx.tiles[2]);
863
864 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, bottom_y,
865 ctx.tiles[13]);
866 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, bottom_y,
867 ctx.tiles[14]);
868 }
869
870 // Corners
872 ctx.tiles[0]);
874 ctx.tiles[12]);
876 ctx.tiles[3]);
877 DrawRoutineUtils::WriteTile8(ctx.target_bg, right_x, bottom_y, ctx.tiles[15]);
878}
879
881 // ASM: RoomDraw_WaterOverlayA8x8_1to16 ($0195D6) / RoomDraw_WaterOverlayB8x8
882 // NOTE: In the original game, this is an HDMA control object that sets up
883 // the wavy water distortion effect. It doesn't draw tiles directly.
884 // For the editor, we draw the available tile data as a visual indicator.
885
886 int size_x = ((ctx.object.size_ >> 2) & 0x03);
887 int size_y = (ctx.object.size_ & 0x03);
888
889 int count_x = size_x + 2;
890 int count_y = size_y + 2;
891
892 if (ctx.tiles.empty())
893 return;
894 if (ctx.tiles.size() < 8) {
895 // Fallback for abbreviated tile payloads: still stamp a visible overlay.
896 for (int yy = 0; yy < count_y; ++yy) {
897 for (int xx = 0; xx < count_x; ++xx) {
898 int base_x = ctx.object.x_ + (xx * 4);
899 int base_y = ctx.object.y_ + (yy * 4);
900 const auto& tile =
901 ctx.tiles[static_cast<size_t>((xx + yy) % ctx.tiles.size())];
902 for (int y = 0; y < 4; ++y) {
903 for (int x = 0; x < 4; ++x) {
904 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
905 tile);
906 }
907 }
908 }
909 }
910 return;
911 }
912
913 for (int yy = 0; yy < count_y; ++yy) {
914 for (int xx = 0; xx < count_x; ++xx) {
915 int base_x = ctx.object.x_ + (xx * 4);
916 int base_y = ctx.object.y_ + (yy * 4);
917
918 for (int x = 0; x < 4; ++x) {
919 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y,
920 ctx.tiles[x]);
921 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 2,
922 ctx.tiles[x]);
923 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 1,
924 ctx.tiles[4 + x]);
925 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + 3,
926 ctx.tiles[4 + x]);
927 }
928 }
929 }
930}
931
932// ============================================================================
933// Stair Routines
934// ============================================================================
935
937 // ASM: RoomDraw_InterRoomFatStairsUp ($01A41B)
938 // Uses tile data at obj1088, draws 4x4 pattern
939 // In original game, registers position in $06B0 for transition handling
940 // For editor display, we just draw the visual representation
941
942 if (ctx.tiles.size() < 16)
943 return;
944
945 // Draw 4x4 stair pattern
946 for (int y = 0; y < 4; ++y) {
947 for (int x = 0; x < 4; ++x) {
948 size_t tile_idx = static_cast<size_t>(y * 4 + x);
950 ctx.object.y_ + y, ctx.tiles[tile_idx]);
951 }
952 }
953}
954
956 // ASM: RoomDraw_InterRoomFatStairsDownA ($01A458)
957 // Uses tile data at obj10A8
958 DrawInterRoomFatStairsUp(ctx); // Same visual structure
959}
960
962 // ASM: RoomDraw_InterRoomFatStairsDownB ($01A486)
963 // Uses tile data at obj10A8
964 DrawInterRoomFatStairsUp(ctx); // Same visual structure
965}
966
967void DrawSpiralStairs(const DrawContext& ctx, bool going_up, bool is_upper) {
968 // ASM: RoomDraw_SpiralStairsGoingUpUpper, etc.
969 // Calls RoomDraw_1x3N_rightwards with A=4 -> 4 columns x 3 rows = 12 tiles
970 // Tile order is COLUMN-MAJOR (down first, then right)
971 (void)going_up;
972 (void)is_upper;
973
974 if (ctx.tiles.size() < 12)
975 return;
976
977 // Draw 4x3 pattern in COLUMN-MAJOR order (matching ASM)
978 int tid = 0;
979 for (int x = 0; x < 4; ++x) {
980 for (int y = 0; y < 3; ++y) {
982 ctx.object.y_ + y, ctx.tiles[tid++]);
983 }
984 }
985}
986
987void DrawAutoStairs(const DrawContext& ctx) {
988 // ASM: RoomDraw_AutoStairs* routines
989 // Multi-layer or merged layer stair patterns
990 if (ctx.tiles.size() < 16)
991 return;
992
993 for (int y = 0; y < 4; ++y) {
994 for (int x = 0; x < 4; ++x) {
995 size_t tile_idx = static_cast<size_t>(y * 4 + x);
997 ctx.object.y_ + y, ctx.tiles[tile_idx]);
998 }
999 }
1000}
1001
1003 // ASM: RoomDraw_StraightInterroomStairs* routines
1004 // North/South, Up/Down variants
1005 if (ctx.tiles.size() < 16)
1006 return;
1007
1008 for (int y = 0; y < 4; ++y) {
1009 for (int x = 0; x < 4; ++x) {
1010 size_t tile_idx = static_cast<size_t>(y * 4 + x);
1012 ctx.object.y_ + y, ctx.tiles[tile_idx]);
1013 }
1014 }
1015}
1016
1017// ============================================================================
1018// Interactive Object Routines
1019// ============================================================================
1020
1021void DrawPrisonCell(const DrawContext& ctx) {
1022 // ASM: RoomDraw_PrisonCell ($019C44)
1023 // Draws prison cell bars to BOTH BG layers with horizontal flip for symmetry
1024 // The ASM writes to $7E2xxx (BG1) and also uses ORA #$4000 for horizontal flip
1025 // Pattern: 5 iterations drawing a complex bar pattern
1026
1027 if (ctx.tiles.size() < 6)
1028 return;
1029
1030 // Prison cell layout based on ASM analysis:
1031 // The routine draws 5 columns of bars, each with specific tile patterns
1032 // Tiles at positions: (x, y), (x+7, y) for outer bars
1033 // Middle bars with horizontal flip on one side
1034
1035 int base_x = ctx.object.x_;
1036 int base_y = ctx.object.y_;
1037
1038 // Draw the prison cell pattern - 5 vertical bar segments
1039 for (int col = 0; col < 5; ++col) {
1040 int x_offset = col;
1041
1042 // Each column has 4 rows of tiles
1043 for (int row = 0; row < 4; ++row) {
1044 size_t tile_idx = (row < static_cast<int>(ctx.tiles.size())) ? row : 0;
1045
1046 // Left side bar
1047 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x_offset,
1048 base_y + row, ctx.tiles[tile_idx]);
1049
1050 // Right side bar (mirrored horizontally)
1051 auto mirrored_tile = ctx.tiles[tile_idx];
1052 mirrored_tile.horizontal_mirror_ = !mirrored_tile.horizontal_mirror_;
1053 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 9 - x_offset,
1054 base_y + row, mirrored_tile);
1055 }
1056 }
1057
1058 // If we have a secondary BG buffer, draw the same pattern there
1059 // This ensures the prison bars appear on both background layers
1060 if (ctx.HasSecondaryBG()) {
1061 for (int col = 0; col < 5; ++col) {
1062 int x_offset = col;
1063
1064 for (int row = 0; row < 4; ++row) {
1065 size_t tile_idx = (row < static_cast<int>(ctx.tiles.size())) ? row : 0;
1066
1067 // Left side bar
1068 DrawRoutineUtils::WriteTile8(*ctx.secondary_bg, base_x + x_offset,
1069 base_y + row, ctx.tiles[tile_idx]);
1070
1071 // Right side bar (mirrored)
1072 auto mirrored_tile = ctx.tiles[tile_idx];
1073 mirrored_tile.horizontal_mirror_ = !mirrored_tile.horizontal_mirror_;
1074 DrawRoutineUtils::WriteTile8(*ctx.secondary_bg, base_x + 9 - x_offset,
1075 base_y + row, mirrored_tile);
1076 }
1077 }
1078 }
1079}
1080
1081void DrawBigKeyLock(const DrawContext& ctx) {
1082 // ASM: RoomDraw_BigKeyLock ($0198AE)
1083 // Checks room flags via RoomFlagMask to see if lock is already opened
1084 // For editor, we draw the closed state by default
1085
1086 bool is_opened = false;
1087 if (ctx.state) {
1088 // Check if this specific lock has been opened via door state
1089 is_opened = ctx.state->IsDoorOpen(ctx.room_id, 0); // Lock uses door slot 0
1090 }
1091
1092 if (is_opened) {
1093 // Draw open lock (if different tiles available)
1094 if (ctx.tiles.size() >= 8) {
1095 // Use second set of tiles for open state
1096 for (int y = 0; y < 2; ++y) {
1097 for (int x = 0; x < 2; ++x) {
1099 ctx.object.y_ + y,
1100 ctx.tiles[4 + y * 2 + x]);
1101 }
1102 }
1103 return;
1104 }
1105 }
1106
1107 // Draw closed lock (2x2 pattern)
1108 if (ctx.tiles.size() >= 4) {
1109 for (int y = 0; y < 2; ++y) {
1110 for (int x = 0; x < 2; ++x) {
1112 ctx.object.y_ + y, ctx.tiles[y * 2 + x]);
1113 }
1114 }
1115 }
1116}
1117
1119 // ASM: RoomDraw_BombableFloor ($019B7A)
1120 // Checks room flags to see if floor has been bombed
1121
1122 bool is_bombed = false;
1123 if (ctx.state) {
1124 is_bombed = ctx.state->IsFloorBombable(ctx.room_id);
1125 }
1126
1127 if (is_bombed) {
1128 // Draw hole (use second tile set if available)
1129 if (ctx.tiles.size() >= 8) {
1130 for (int y = 0; y < 2; ++y) {
1131 for (int x = 0; x < 2; ++x) {
1133 ctx.object.y_ + y,
1134 ctx.tiles[4 + y * 2 + x]);
1135 }
1136 }
1137 return;
1138 }
1139 }
1140
1141 // Draw intact floor (2x2 pattern)
1142 if (ctx.tiles.size() >= 4) {
1143 for (int y = 0; y < 2; ++y) {
1144 for (int x = 0; x < 2; ++x) {
1146 ctx.object.y_ + y, ctx.tiles[y * 2 + x]);
1147 }
1148 }
1149 }
1150}
1151
1152void DrawMovingWall(const DrawContext& ctx, bool is_west) {
1153 // ASM: RoomDraw_MovingWallWest ($019316), RoomDraw_MovingWallEast ($01935C)
1154 // Checks if wall has moved based on game state
1155 (void)is_west; // Direction affects which way wall moves
1156
1157 bool has_moved = false;
1158 if (ctx.state) {
1159 has_moved = ctx.state->IsWallMoved(ctx.room_id);
1160 }
1161
1162 // Draw wall in current position
1163 // Size determines wall length
1164 int size = (ctx.object.size_ & 0x0F) + 1;
1165
1166 if (ctx.tiles.size() < 4)
1167 return;
1168
1169 for (int s = 0; s < size; ++s) {
1170 int offset = has_moved ? 2 : 0; // Offset position if wall has moved
1171 int x = ctx.object.x_ + offset;
1172 int y = ctx.object.y_ + (s * 2);
1173
1174 // Draw 2x2 wall segment
1175 for (int dy = 0; dy < 2; ++dy) {
1176 for (int dx = 0; dx < 2; ++dx) {
1177 DrawRoutineUtils::WriteTile8(ctx.target_bg, x + dx, y + dy,
1178 ctx.tiles[dy * 2 + dx]);
1179 }
1180 }
1181 }
1182}
1183
1184// ============================================================================
1185// Water Face Variants
1186// ============================================================================
1187
1188namespace {
1189constexpr int kWaterFaceWidthTiles = 4;
1190
1191void DrawWaterFaceRows(const DrawContext& ctx, int row_count, int tile_offset) {
1192 if (row_count <= 0 || tile_offset < 0 || ctx.tiles.empty()) {
1193 return;
1194 }
1195
1196 if (tile_offset >= static_cast<int>(ctx.tiles.size())) {
1197 return;
1198 }
1199
1200 const int available_tiles = static_cast<int>(ctx.tiles.size()) - tile_offset;
1201 const int available_rows = available_tiles / kWaterFaceWidthTiles;
1202 const int rows_to_draw = std::min(row_count, available_rows);
1203
1204 for (int row = 0; row < rows_to_draw; ++row) {
1205 const int row_base = tile_offset + (row * kWaterFaceWidthTiles);
1206 for (int col = 0; col < kWaterFaceWidthTiles; ++col) {
1208 ctx.object.y_ + row,
1209 ctx.tiles[row_base + col]);
1210 }
1211 }
1212}
1213} // namespace
1214
1216 // ASM: RoomDraw_EmptyWaterFace ($019D29)
1217 //
1218 // usdasm behavior:
1219 // - Base state draws a 4x3 face using data at offset 0x1614.
1220 // - "Water active" branch draws a 4x5 variant using data at offset 0x162C.
1221 //
1222 // IMPORTANT: this uses dedicated water-face state, not door state. Tying
1223 // this branch to IsDoorOpen created cross-feature rendering regressions.
1224 const bool water_active =
1225 (ctx.state != nullptr) && ctx.state->IsWaterFaceActive(ctx.room_id);
1226
1227 const int row_count = water_active ? 5 : 3;
1228 const int tile_offset = water_active ? 12 : 0; // 0x162C - 0x1614 = 24 bytes
1229 DrawWaterFaceRows(ctx, row_count, tile_offset);
1230}
1231
1233 // ASM: RoomDraw_SpittingWaterFace ($019D64)
1234 // Draws a 4x5 face/spout shape.
1235 DrawWaterFaceRows(ctx, /*row_count=*/5, /*tile_offset=*/0);
1236}
1237
1239 // ASM: RoomDraw_DrenchingWaterFace ($019D83)
1240 // Draws a 4x7 continuous stream.
1241 DrawWaterFaceRows(ctx, /*row_count=*/7, /*tile_offset=*/0);
1242}
1243
1244// ============================================================================
1245// Chest Platform Multi-Part Routines
1246// ============================================================================
1247
1249 // ASM: RoomDraw_ClosedChestPlatform ($018CC7)
1250 // Complex structure: horizontal wall top, vertical walls sides
1251
1252 int size_x = (ctx.object.size_ & 0x0F) + 4; // Width is size + 4
1253 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
1254
1255 if (ctx.tiles.size() < 16)
1256 return;
1257
1258 // Draw top horizontal wall with corners
1259 for (int x = 0; x < size_x; ++x) {
1260 // Top row
1261 size_t tile_idx = (x == 0) ? 0 : ((x == size_x - 1) ? 2 : 1);
1263 ctx.object.y_, ctx.tiles[tile_idx]);
1264 }
1265
1266 // Draw vertical walls on sides
1267 for (int y = 1; y < size_y + 1; ++y) {
1268 // Left wall
1270 ctx.object.y_ + y, ctx.tiles[3]);
1271 // Right wall
1272 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_ + size_x - 1,
1273 ctx.object.y_ + y, ctx.tiles[4]);
1274 }
1275
1276 // Draw bottom horizontal wall with corners
1277 int bottom_y = ctx.object.y_ + size_y + 1;
1278 for (int x = 0; x < size_x; ++x) {
1279 size_t tile_idx = (x == 0) ? 5 : ((x == size_x - 1) ? 7 : 6);
1280 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_ + x, bottom_y,
1281 ctx.tiles[tile_idx]);
1282 }
1283}
1284
1286 // ASM: RoomDraw_ChestPlatformHorizontalWallWithCorners ($018D0D)
1287 int width = (ctx.object.size_ & 0x0F) + 1;
1288
1289 if (ctx.tiles.size() < 3)
1290 return;
1291
1292 for (int x = 0; x < width; ++x) {
1293 size_t tile_idx = (x == 0) ? 0 : ((x == width - 1) ? 2 : 1);
1295 ctx.object.y_, ctx.tiles[tile_idx]);
1296 }
1297}
1298
1300 // ASM: RoomDraw_ChestPlatformVerticalWall ($019E70)
1301 int height = (ctx.object.size_ & 0x0F) + 1;
1302
1303 if (ctx.tiles.empty())
1304 return;
1305
1306 for (int y = 0; y < height; ++y) {
1308 ctx.object.y_ + y, ctx.tiles[0]);
1309 }
1310}
1311
1312void RegisterSpecialRoutines(std::vector<DrawRoutineInfo>& registry) {
1313 // Note: Routine IDs are assigned based on the assembly routine table
1314 // These special routines handle chests, doors, and other non-standard objects
1315
1316 // Chest routine - uses a wrapper since it needs chest_index
1317 registry.push_back(DrawRoutineInfo{
1318 .id = 39, // DrawChest (special index)
1319 .name = "Chest",
1320 .function =
1321 [](const DrawContext& ctx) {
1322 // Default chest index 0 - actual index tracked externally
1323 DrawChest(ctx, 0);
1324 },
1325 .draws_to_both_bgs = false,
1326 .base_width = 2,
1327 .base_height = 2,
1328 .min_tiles = 4, // 2x2 block
1330 });
1331
1332 registry.push_back(DrawRoutineInfo{
1333 .id = 38, // DrawNothing
1334 .name = "Nothing",
1335 .function = DrawNothing,
1336 .draws_to_both_bgs = false,
1337 .base_width = 0,
1338 .base_height = 0,
1340 });
1341
1342 registry.push_back(DrawRoutineInfo{
1343 .id = 26, // DrawDoorSwitcherer
1344 .name = "DoorSwitcherer",
1345 .function = DrawDoorSwitcherer,
1346 .draws_to_both_bgs = false,
1347 .base_width = 1,
1348 .base_height = 1,
1349 .min_tiles = 1, // at least one tile for door graphic
1351 });
1352
1353 registry.push_back(DrawRoutineInfo{
1354 .id = 33, // DrawSomariaLine
1355 .name = "SomariaLine",
1356 .function = DrawSomariaLine,
1357 .draws_to_both_bgs = false,
1358 .base_width = 0, // Variable
1359 .base_height = 0,
1361 });
1362
1363 registry.push_back(DrawRoutineInfo{
1364 .id = 34, // DrawWaterFace
1365 .name = "WaterFace",
1366 .function = DrawWaterFace,
1367 .draws_to_both_bgs = false,
1368 .base_width = 2,
1369 .base_height = 2,
1370 .min_tiles = 4, // 2x2 block
1372 });
1373
1374 // ============================================================================
1375 // SuperSquare Routines (Phase 4) - IDs 56-64
1376 // ============================================================================
1377
1378 registry.push_back(DrawRoutineInfo{
1379 .id = 56, // Draw4x4BlocksIn4x4SuperSquare
1380 .name = "4x4BlocksIn4x4SuperSquare",
1382 .draws_to_both_bgs = false,
1383 .base_width = 0, // Variable: width = (((size >> 2) & 3) + 1) * 4
1384 .base_height = 0, // Variable: height = ((size & 3) + 1) * 4
1386 });
1387
1388 registry.push_back(DrawRoutineInfo{
1389 .id = 57, // Draw3x3FloorIn4x4SuperSquare
1390 .name = "3x3FloorIn4x4SuperSquare",
1391 .function = Draw3x3FloorIn4x4SuperSquare,
1392 .draws_to_both_bgs = false,
1393 .base_width = 0, // Variable
1394 .base_height = 0, // Variable
1396 });
1397
1398 registry.push_back(DrawRoutineInfo{
1399 .id = 58, // Draw4x4FloorIn4x4SuperSquare
1400 .name = "4x4FloorIn4x4SuperSquare",
1401 .function = Draw4x4FloorIn4x4SuperSquare,
1402 .draws_to_both_bgs = false,
1403 .base_width = 0, // Variable
1404 .base_height = 0, // Variable
1406 });
1407
1408 registry.push_back(DrawRoutineInfo{
1409 .id = 59, // Draw4x4FloorOneIn4x4SuperSquare
1410 .name = "4x4FloorOneIn4x4SuperSquare",
1412 .draws_to_both_bgs = false,
1413 .base_width = 0, // Variable
1414 .base_height = 0, // Variable
1416 });
1417
1418 registry.push_back(DrawRoutineInfo{
1419 .id = 60, // Draw4x4FloorTwoIn4x4SuperSquare
1420 .name = "4x4FloorTwoIn4x4SuperSquare",
1422 .draws_to_both_bgs = false,
1423 .base_width = 0, // Variable
1424 .base_height = 0, // Variable
1426 });
1427
1428 registry.push_back(DrawRoutineInfo{
1429 .id = 61, // DrawBigHole4x4_1to16
1430 .name = "BigHole4x4_1to16",
1431 .function = DrawBigHole4x4_1to16,
1432 .draws_to_both_bgs = false,
1433 .base_width = 0, // Variable
1434 .base_height = 0, // Variable
1436 });
1437
1438 registry.push_back(DrawRoutineInfo{
1439 .id = 62, // DrawSpike2x2In4x4SuperSquare
1440 .name = "Spike2x2In4x4SuperSquare",
1441 .function = DrawSpike2x2In4x4SuperSquare,
1442 .draws_to_both_bgs = false,
1443 .base_width = 0, // Variable
1444 .base_height = 0, // Variable
1446 });
1447
1448 registry.push_back(DrawRoutineInfo{
1449 .id = 63, // DrawTableRock4x4_1to16
1450 .name = "TableRock4x4_1to16",
1451 .function = DrawTableRock4x4_1to16,
1452 .draws_to_both_bgs = false,
1453 .base_width = 0, // Variable
1454 .base_height = 0, // Variable
1456 });
1457
1458 registry.push_back(DrawRoutineInfo{
1459 .id = 64, // DrawWaterOverlay8x8_1to16
1460 .name = "WaterOverlay8x8_1to16",
1461 .function = DrawWaterOverlay8x8_1to16,
1462 .draws_to_both_bgs = false,
1463 .base_width = 0, // Variable
1464 .base_height = 0, // Variable
1466 });
1467
1468 // Stair routines (IDs 83-88)
1469 registry.push_back(DrawRoutineInfo{
1470 .id = 83, // DrawInterRoomFatStairsUp
1471 .name = "InterRoomFatStairsUp",
1472 .function = DrawInterRoomFatStairsUp,
1473 .draws_to_both_bgs = false,
1474 .base_width = 4,
1475 .base_height = 4,
1476 .min_tiles = 16, // 4x4 stair pattern
1478 });
1479
1480 registry.push_back(DrawRoutineInfo{
1481 .id = 84, // DrawInterRoomFatStairsDownA
1482 .name = "InterRoomFatStairsDownA",
1483 .function = DrawInterRoomFatStairsDownA,
1484 .draws_to_both_bgs = false,
1485 .base_width = 4,
1486 .base_height = 4,
1487 .min_tiles = 16, // 4x4 stair pattern
1489 });
1490
1491 registry.push_back(DrawRoutineInfo{
1492 .id = 85, // DrawInterRoomFatStairsDownB
1493 .name = "InterRoomFatStairsDownB",
1494 .function = DrawInterRoomFatStairsDownB,
1495 .draws_to_both_bgs = false,
1496 .base_width = 4,
1497 .base_height = 4,
1498 .min_tiles = 16, // 4x4 stair pattern
1500 });
1501
1502 registry.push_back(DrawRoutineInfo{
1503 .id = 86, // DrawAutoStairs
1504 .name = "AutoStairs",
1505 .function = DrawAutoStairs,
1506 .draws_to_both_bgs = false,
1507 .base_width = 4,
1508 .base_height = 4,
1509 .min_tiles = 16, // 4x4 stair pattern
1511 });
1512
1513 registry.push_back(DrawRoutineInfo{
1514 .id = 87, // DrawStraightInterRoomStairs
1515 .name = "StraightInterRoomStairs",
1516 .function = DrawStraightInterRoomStairs,
1517 .draws_to_both_bgs = false,
1518 .base_width = 4,
1519 .base_height = 4,
1520 .min_tiles = 16, // 4x4 stair pattern
1522 });
1523
1524 // Spiral stairs variants (IDs 88-91)
1525 registry.push_back(DrawRoutineInfo{
1526 .id = 88, // DrawSpiralStairsGoingUpUpper
1527 .name = "SpiralStairsGoingUpUpper",
1528 .function =
1529 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, true, true); },
1530 .draws_to_both_bgs = false,
1531 .base_width = 4,
1532 .base_height = 3, // 4x3 pattern
1533 .min_tiles = 12, // 4x3 block
1535 });
1536
1537 registry.push_back(DrawRoutineInfo{
1538 .id = 89, // DrawSpiralStairsGoingDownUpper
1539 .name = "SpiralStairsGoingDownUpper",
1540 .function =
1541 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, false, true); },
1542 .draws_to_both_bgs = false,
1543 .base_width = 4,
1544 .base_height = 3, // 4x3 pattern
1545 .min_tiles = 12, // 4x3 block
1547 });
1548
1549 registry.push_back(DrawRoutineInfo{
1550 .id = 90, // DrawSpiralStairsGoingUpLower
1551 .name = "SpiralStairsGoingUpLower",
1552 .function =
1553 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, true, false); },
1554 .draws_to_both_bgs = false,
1555 .base_width = 4,
1556 .base_height = 3, // 4x3 pattern
1557 .min_tiles = 12, // 4x3 block
1559 });
1560
1561 registry.push_back(DrawRoutineInfo{
1562 .id = 91, // DrawSpiralStairsGoingDownLower
1563 .name = "SpiralStairsGoingDownLower",
1564 .function =
1565 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, false, false); },
1566 .draws_to_both_bgs = false,
1567 .base_width = 4,
1568 .base_height = 3, // 4x3 pattern
1569 .min_tiles = 12, // 4x3 block
1571 });
1572
1573 // Interactive object routines (IDs 92-95)
1574 registry.push_back(DrawRoutineInfo{
1575 .id = 92, // DrawBigKeyLock
1576 .name = "BigKeyLock",
1577 .function = DrawBigKeyLock,
1578 .draws_to_both_bgs = false,
1579 .base_width = 2,
1580 .base_height = 2,
1581 .min_tiles = 4, // 2x2 block
1583 });
1584
1585 registry.push_back(DrawRoutineInfo{
1586 .id = 93, // DrawBombableFloor
1587 .name = "BombableFloor",
1588 .function = DrawBombableFloor,
1589 .draws_to_both_bgs = false,
1590 .base_width = 2,
1591 .base_height = 2,
1592 .min_tiles = 4, // 2x2 block
1594 });
1595
1596 // Water face variants (IDs 94-96)
1597 registry.push_back(DrawRoutineInfo{
1598 .id = 94, // DrawEmptyWaterFace
1599 .name = "EmptyWaterFace",
1600 .function = DrawEmptyWaterFace,
1601 .draws_to_both_bgs = false,
1602 .base_width = 4,
1603 .base_height = 3,
1604 .min_tiles = 12, // base 4x3 variant
1606 });
1607
1608 registry.push_back(DrawRoutineInfo{
1609 .id = 95, // DrawSpittingWaterFace
1610 .name = "SpittingWaterFace",
1611 .function = DrawSpittingWaterFace,
1612 .draws_to_both_bgs = false,
1613 .base_width = 4,
1614 .base_height = 5,
1615 .min_tiles = 20, // 4x5 variant
1617 });
1618
1619 registry.push_back(DrawRoutineInfo{
1620 .id = 96, // DrawDrenchingWaterFace
1621 .name = "DrenchingWaterFace",
1622 .function = DrawDrenchingWaterFace,
1623 .draws_to_both_bgs = false,
1624 .base_width = 4,
1625 .base_height = 7,
1626 .min_tiles = 28, // 4x7 variant
1628 });
1629
1630 // Prison cell (Type 3 objects 0x20D, 0x217) draws to both BG layers.
1631 registry.push_back(DrawRoutineInfo{
1632 .id = 97, // DrawPrisonCell
1633 .name = "PrisonCell",
1634 .function = DrawPrisonCell,
1635 .draws_to_both_bgs = true,
1636 .base_width = 10, // Columns x..x+9
1637 .base_height = 4,
1639 });
1640
1641 registry.push_back(DrawRoutineInfo{
1642 .id = DrawRoutineIds::kBed4x5, // 98
1643 .name = "Bed4x5",
1644 .function = DrawBed4x5,
1645 .draws_to_both_bgs = false,
1646 .base_width = 4,
1647 .base_height = 5,
1649 });
1650
1651 registry.push_back(DrawRoutineInfo{
1653 .name = "Rightwards3x6",
1654 .function = DrawRightwards3x6,
1655 .draws_to_both_bgs = false,
1656 .base_width = 6,
1657 .base_height = 3,
1659 });
1660
1661 registry.push_back(DrawRoutineInfo{
1663 .name = "Utility6x3",
1664 .function = DrawUtility6x3,
1665 .draws_to_both_bgs = false,
1666 .base_width = 6,
1667 .base_height = 3,
1669 });
1670
1671 registry.push_back(DrawRoutineInfo{
1673 .name = "Utility3x5",
1674 .function = DrawUtility3x5,
1675 .draws_to_both_bgs = false,
1676 .base_width = 3,
1677 .base_height = 5,
1679 });
1680
1681 registry.push_back(DrawRoutineInfo{
1683 .name = "VerticalTurtleRockPipe",
1684 .function = DrawVerticalTurtleRockPipe,
1685 .draws_to_both_bgs = false,
1686 .base_width = 4,
1687 .base_height = 6,
1689 });
1690
1691 registry.push_back(DrawRoutineInfo{
1693 .name = "HorizontalTurtleRockPipe",
1694 .function = DrawHorizontalTurtleRockPipe,
1695 .draws_to_both_bgs = false,
1696 .base_width = 6,
1697 .base_height = 4,
1699 });
1700
1701 registry.push_back(DrawRoutineInfo{
1703 .name = "LightBeam",
1704 .function = DrawLightBeamOnFloor,
1705 .draws_to_both_bgs = false,
1706 .base_width = 4,
1707 .base_height = 10,
1709 });
1710
1711 registry.push_back(DrawRoutineInfo{
1713 .name = "BigLightBeam",
1714 .function = DrawBigLightBeamOnFloor,
1715 .draws_to_both_bgs = false,
1716 .base_width = 8,
1717 .base_height = 8,
1719 });
1720
1721 registry.push_back(DrawRoutineInfo{
1723 .name = "BossShell4x4",
1724 .function = DrawBossShell4x4,
1725 .draws_to_both_bgs = false,
1726 .base_width = 4,
1727 .base_height = 4,
1729 });
1730
1731 registry.push_back(DrawRoutineInfo{
1733 .name = "SolidWallDecor3x4",
1734 .function = DrawSolidWallDecor3x4,
1735 .draws_to_both_bgs = false,
1736 .base_width = 3,
1737 .base_height = 4,
1739 });
1740
1741 registry.push_back(DrawRoutineInfo{
1743 .name = "ArcheryGameTargetDoor",
1744 .function = DrawArcheryGameTargetDoor,
1745 .draws_to_both_bgs = false,
1746 .base_width = 3,
1747 .base_height = 6,
1749 });
1750
1751 registry.push_back(DrawRoutineInfo{
1753 .name = "GanonTriforceFloorDecor",
1754 .function = DrawGanonTriforceFloorDecor,
1755 .draws_to_both_bgs = false,
1756 .base_width = 8,
1757 .base_height = 8,
1759 });
1760
1761 registry.push_back(DrawRoutineInfo{
1763 .name = "Single2x2",
1764 .function = DrawSingle2x2,
1765 .draws_to_both_bgs = false,
1766 .base_width = 2,
1767 .base_height = 2,
1768 .min_tiles = 4,
1770 });
1771
1772 registry.push_back(DrawRoutineInfo{
1774 .name = "Single4x4",
1775 .function = DrawSingle4x4,
1776 .draws_to_both_bgs = false,
1777 .base_width = 4,
1778 .base_height = 4,
1779 .min_tiles = 16,
1781 });
1782
1783 registry.push_back(DrawRoutineInfo{
1785 .name = "Single4x3",
1786 .function = DrawSingle4x3,
1787 .draws_to_both_bgs = false,
1788 .base_width = 4,
1789 .base_height = 3,
1790 .min_tiles = 12,
1792 });
1793
1794 registry.push_back(DrawRoutineInfo{
1796 .name = "RupeeFloor",
1797 .function = DrawRupeeFloor,
1798 .draws_to_both_bgs = false,
1799 .base_width = 6,
1800 .base_height = 8,
1801 .min_tiles = 2,
1803 });
1804
1805 registry.push_back(DrawRoutineInfo{
1807 .name = "Actual4x4",
1808 .function = DrawActual4x4,
1809 .draws_to_both_bgs = false,
1810 .base_width = 4,
1811 .base_height = 4,
1812 .min_tiles = 16,
1814 });
1815
1816 registry.push_back(DrawRoutineInfo{
1818 .name = "Waterfall47",
1819 .function = DrawWaterfall47,
1820 .draws_to_both_bgs = false,
1821 .base_width = 0, // Variable with size
1822 .base_height = 5,
1824 });
1825
1826 registry.push_back(DrawRoutineInfo{
1828 .name = "Waterfall48",
1829 .function = DrawWaterfall48,
1830 .draws_to_both_bgs = false,
1831 .base_width = 0, // Variable with size
1832 .base_height = 3,
1834 });
1835
1836 // Chest platform routines - use canonical IDs from DrawRoutineIds
1837 registry.push_back(DrawRoutineInfo{
1839 .name = "ClosedChestPlatform",
1840 .function = DrawClosedChestPlatform,
1841 .draws_to_both_bgs = false,
1842 .base_width = 0, // Variable: width = (size & 0x0F) + 4
1843 .base_height = 0, // Variable: height = ((size >> 4) & 0x0F) + 1
1845 });
1846
1847 // Moving wall routines
1848 registry.push_back(DrawRoutineInfo{
1850 .name = "MovingWallWest",
1851 .function =
1852 [](const DrawContext& ctx) { DrawMovingWall(ctx, /*is_west=*/true); },
1853 .draws_to_both_bgs = false,
1854 .base_width = 4,
1855 .base_height = 8,
1856 .min_tiles = 4,
1858 });
1859
1860 registry.push_back(DrawRoutineInfo{
1862 .name = "MovingWallEast",
1863 .function =
1864 [](const DrawContext& ctx) {
1865 DrawMovingWall(ctx, /*is_west=*/false);
1866 },
1867 .draws_to_both_bgs = false,
1868 .base_width = 4,
1869 .base_height = 8,
1870 .min_tiles = 4,
1872 });
1873
1874 registry.push_back(DrawRoutineInfo{
1876 .name = "OpenChestPlatform",
1877 .function =
1878 [](const DrawContext& ctx) {
1879 // Open chest platform - draws multi-segment pattern
1880 // Size: width = (size & 0x0F) + 1, segments = ((size >> 4) & 0x0F) * 2 + 5
1881 int width = (ctx.object.size_ & 0x0F) + 1;
1882 int segments = ((ctx.object.size_ >> 4) & 0x0F) * 2 + 5;
1883 // For geometry purposes, just set reasonable bounds
1884 for (int s = 0; s < segments && s < 8; ++s) {
1885 for (int x = 0; x < width && x < 8; ++x) {
1886 if (ctx.tiles.size() > 0) {
1887 size_t idx = (s * width + x) % ctx.tiles.size();
1888 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_ + x,
1889 ctx.object.y_ + s,
1890 ctx.tiles[idx]);
1891 }
1892 }
1893 }
1894 },
1895 .draws_to_both_bgs = false,
1896 .base_width = 0, // Variable
1897 .base_height = 0, // Variable
1899 });
1900
1901 // Vertical rails with CORNER+MIDDLE+END pattern (ID 117) - objects 0x8A-0x8C
1902 // Matches horizontal rail 0x22 but in vertical orientation
1903 registry.push_back(DrawRoutineInfo{
1905 .name = "DownwardsHasEdge1x1_1to16_plus23",
1906 .function =
1907 [](const DrawContext& ctx) {
1908 // CORNER+MIDDLE+END pattern vertically
1909 int size = ctx.object.size_ & 0x0F;
1910 int count = size + 21;
1911 if (ctx.tiles.size() < 3)
1912 return;
1913
1914 int tile_y = ctx.object.y_;
1915 // USDASM $01:8EC9-$01:8ED4 suppresses the leading corner when the
1916 // current slot already contains the small vertical rail corner.
1918 ctx.target_bg, ctx.object.x_, tile_y, {0x00E3})) {
1919 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y,
1920 ctx.tiles[0]);
1921 }
1922 tile_y++;
1923 // Middle tiles
1924 for (int s = 0; s < count; s++) {
1925 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y,
1926 ctx.tiles[1]);
1927 tile_y++;
1928 }
1929 // End tile
1930 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y,
1931 ctx.tiles[2]);
1932 },
1933 .draws_to_both_bgs = false,
1934 .base_width = 1,
1935 .base_height = 23, // size + 23
1936 .min_tiles = 3, // corner + middle + end tiles
1938 });
1939
1940 // Custom Object routine (ID 130) - Oracle of Secrets objects 0x31, 0x32
1941 // These use external binary files instead of ROM tile data.
1942 // CustomDraw() handles feature-flag gating and binary file lookup.
1943 registry.push_back(DrawRoutineInfo{
1945 .name = "CustomObject",
1946 .function = CustomDraw,
1947 .draws_to_both_bgs = false,
1948 .base_width = 0, // Variable: depends on binary file content
1949 .base_height = 0, // Variable: depends on binary file content
1951 });
1952}
1953
1954} // namespace draw_routines
1955} // namespace zelda3
1956} // namespace yaze
static Flags & get()
Definition features.h:118
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 IsDoorOpen(int room_id, int door_index) const =0
#define LOG_DEBUG(category, format,...)
Definition log.h:103
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)
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 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)
const gfx::TileInfo & TileAtWrapped(std::span< const gfx::TileInfo > tiles, size_t index)
void Draw1x5Column(const DrawContext &ctx, int x_offset, size_t start_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 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 DrawLargeCanvasObject(const DrawContext &ctx, int width, int height)
Draw large canvas object with arbitrary dimensions.
void DrawChest(const DrawContext &ctx, int chest_index)
Draw chest object (big or small) with open/closed state support.
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 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 DrawChestPlatformVerticalWall(const DrawContext &ctx)
Draw chest platform vertical wall section.
void DrawClosedChestPlatform(const DrawContext &ctx)
Draw closed chest platform (Type 1 object 0xC1)
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 big floor light beam (8x8 footprint)
void DrawSpiralStairs(const DrawContext &ctx, bool going_up, bool is_upper)
Draw spiral stairs (Type 2 objects 0x138-0x13B)
void DrawSomariaLine(const DrawContext &ctx)
Draw Somaria line in various directions.
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 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 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 DrawMovingWall(const DrawContext &ctx, bool is_west)
Draw moving wall (Type 1 objects 0xCD, 0xCE)
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 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 (6x8 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 big key lock (Type 3 object 0x218)
void DrawPrisonCell(const DrawContext &ctx)
Draw prison cell with bars (Type 3 objects 0x20D, 0x217)
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.