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
6
7namespace yaze {
8namespace zelda3 {
9namespace draw_routines {
10
11void DrawChest(const DrawContext& ctx, int chest_index) {
12 // Determine if chest is open
13 bool is_open = false;
14 if (ctx.state) {
15 is_open = ctx.state->IsChestOpen(ctx.room_id, chest_index);
16 }
17
18 // Draw logic
19 // Heuristic: If we have extra tiles loaded, assume they are for the open
20 // state. Standard chests are 2x2 (4 tiles). Big chests are 4x4 (16 tiles). If
21 // we have double the tiles, use the second half for open state.
22
23 if (is_open) {
24 if (ctx.tiles.size() >= 32) {
25 // Big chest open tiles (indices 16-31)
26 // Draw 4x4 pattern
27 for (int x = 0; x < 4; ++x) {
28 for (int y = 0; y < 4; ++y) {
30 ctx.object.y_ + y,
31 ctx.tiles[16 + x * 4 + y]);
32 }
33 }
34 return;
35 }
36 if (ctx.tiles.size() >= 8 && ctx.tiles.size() < 16) {
37 // Small chest open tiles (indices 4-7)
39 ctx.tiles[4]);
41 ctx.object.y_ + 1, ctx.tiles[5]);
43 ctx.object.y_, ctx.tiles[6]);
45 ctx.object.y_ + 1, ctx.tiles[7]);
46 return;
47 }
48 // If no extra tiles, fall through to draw closed chest (better than
49 // nothing)
50 }
51
52 // Fallback to standard 4x4 or 2x2 drawing based on tile count
53 if (ctx.tiles.size() >= 16) {
54 // Draw 4x4 pattern in COLUMN-MAJOR order
55 for (int x = 0; x < 4; ++x) {
56 for (int y = 0; y < 4; ++y) {
58 ctx.object.y_ + y, ctx.tiles[x * 4 + y]);
59 }
60 }
61 } else if (ctx.tiles.size() >= 4) {
62 // Draw 2x2 pattern in COLUMN-MAJOR order
64 ctx.tiles[0]);
66 ctx.object.y_ + 1, ctx.tiles[1]);
68 ctx.object.y_, ctx.tiles[2]);
70 ctx.object.y_ + 1, ctx.tiles[3]);
71 }
72}
73
74void DrawNothing([[maybe_unused]] const DrawContext& ctx) {
75 // Intentionally empty - represents invisible logic objects or placeholders
76 // ASM: RoomDraw_Nothing_A ($0190F2), RoomDraw_Nothing_B ($01932E), etc.
77 // These routines typically just RTS.
78}
79
80void CustomDraw(const DrawContext& ctx) {
81 // Pattern: Custom draw routine (objects 0x31-0x32)
82 // For now, fall back to simple 1x1
83 if (ctx.tiles.size() >= 1) {
84 // Use first 8x8 tile from span
86 ctx.tiles[0]);
87 }
88}
89
91 // Pattern: Door switcher (object 0x35)
92 // Special door logic
93 // Check state to decide graphic
94 int tile_index = 0;
95 if (ctx.state && ctx.state->IsDoorSwitchActive(ctx.room_id)) {
96 // Use active tile if available (assuming 2nd tile is active state)
97 if (ctx.tiles.size() >= 2) {
98 tile_index = 1;
99 }
100 }
101
102 if (ctx.tiles.size() > static_cast<size_t>(tile_index)) {
104 ctx.tiles[tile_index]);
105 }
106}
107
108void DrawSomariaLine(const DrawContext& ctx) {
109 // Pattern: Somaria Line (objects 0x203-0x20F, 0x214)
110 // Draws a line of tiles based on direction encoded in object ID
111 // Direction mapping based on ZScream reference:
112 // 0x03: Horizontal right
113 // 0x04: Vertical down
114 // 0x05: Diagonal down-right
115 // 0x06: Diagonal down-left
116 // 0x07-0x09: Variations
117 // 0x0A-0x0C: More variations
118 // 0x0E-0x0F: Additional patterns
119 // 0x14: Another line type
120
121 if (ctx.tiles.empty()) return;
122
123 int length = (ctx.object.size_ & 0x0F) + 1;
124 int obj_subid = ctx.object.id_ & 0x0F; // Low nibble determines direction
125
126 // Determine direction based on object sub-ID
127 int dx = 1, dy = 0; // Default: horizontal right
128 switch (obj_subid) {
129 case 0x03:
130 dx = 1;
131 dy = 0;
132 break; // Horizontal right
133 case 0x04:
134 dx = 0;
135 dy = 1;
136 break; // Vertical down
137 case 0x05:
138 dx = 1;
139 dy = 1;
140 break; // Diagonal down-right
141 case 0x06:
142 dx = -1;
143 dy = 1;
144 break; // Diagonal down-left
145 case 0x07:
146 dx = 1;
147 dy = 0;
148 break; // Horizontal (variant)
149 case 0x08:
150 dx = 0;
151 dy = 1;
152 break; // Vertical (variant)
153 case 0x09:
154 dx = 1;
155 dy = 1;
156 break; // Diagonal (variant)
157 case 0x0A:
158 dx = 1;
159 dy = 0;
160 break; // Horizontal
161 case 0x0B:
162 dx = 0;
163 dy = 1;
164 break; // Vertical
165 case 0x0C:
166 dx = 1;
167 dy = 1;
168 break; // Diagonal
169 case 0x0E:
170 dx = 1;
171 dy = 0;
172 break; // Horizontal
173 case 0x0F:
174 dx = 0;
175 dy = 1;
176 break; // Vertical
177 default:
178 dx = 1;
179 dy = 0;
180 break; // Default horizontal
181 }
182
183 // Draw tiles along the path using first tile (Somaria uses single tile)
184 for (int i = 0; i < length; ++i) {
185 size_t tile_idx = i % ctx.tiles.size(); // Cycle through tiles if multiple
187 ctx.object.y_ + (i * dy), ctx.tiles[tile_idx]);
188 }
189}
190
191void DrawWaterFace(const DrawContext& ctx) {
192 // Pattern: Water Face (Type 3 objects 0xF80-0xF82)
193 // Draws a 2x2 face in COLUMN-MAJOR order
194 // TODO: Implement state check from RoomDraw_EmptyWaterFace ($019D29)
195 // Checks Room ID ($AF), Room State ($7EF000), Door Flags ($0403) to switch
196 // graphic
197 if (ctx.tiles.size() >= 4) {
199 ctx.tiles[0]); // col 0, row 0
201 ctx.object.y_ + 1,
202 ctx.tiles[1]); // col 0, row 1
204 ctx.object.y_,
205 ctx.tiles[2]); // col 1, row 0
207 ctx.object.y_ + 1,
208 ctx.tiles[3]); // col 1, row 1
209 }
210}
211
212void DrawLargeCanvasObject(const DrawContext& ctx, int width, int height) {
213 // Generic large object drawer
214 if (ctx.tiles.size() >= static_cast<size_t>(width * height)) {
215 for (int y = 0; y < height; ++y) {
216 for (int x = 0; x < width; ++x) {
218 ctx.object.y_ + y,
219 ctx.tiles[y * width + x]);
220 }
221 }
222 }
223}
224
225// ============================================================================
226// SuperSquare Routines (Phase 4)
227// ============================================================================
228// A "SuperSquare" is a 16x16 tile area (4 rows of 4 tiles each).
229// These routines draw floor patterns that repeat in super square units.
230
232 // ASM: RoomDraw_4x4BlocksIn4x4SuperSquare ($018B94)
233 // Draws solid 4x4 blocks using a single tile, repeated in a grid pattern.
234 // Size determines number of super squares in X and Y directions.
235 int size_x = (ctx.object.size_ & 0x0F) + 1;
236 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
237
238 if (ctx.tiles.empty()) return;
239
240 // Use first tile for all blocks
241 const auto& tile = ctx.tiles[0];
242
243 for (int sy = 0; sy < size_y; ++sy) {
244 for (int sx = 0; sx < size_x; ++sx) {
245 // Each super square is 4x4 tiles
246 int base_x = ctx.object.x_ + (sx * 4);
247 int base_y = ctx.object.y_ + (sy * 4);
248
249 // Draw 4x4 block with same tile
250 for (int y = 0; y < 4; ++y) {
251 for (int x = 0; x < 4; ++x) {
252 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
253 tile);
254 }
255 }
256 }
257 }
258}
259
261 // ASM: RoomDraw_3x3FloorIn4x4SuperSquare ($018D8A)
262 // Draws 3x3 floor patterns within super square units.
263 int size_x = (ctx.object.size_ & 0x0F) + 1;
264 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
265
266 if (ctx.tiles.size() < 9) return; // Need at least 9 tiles for 3x3
267
268 for (int sy = 0; sy < size_y; ++sy) {
269 for (int sx = 0; sx < size_x; ++sx) {
270 // Each super square positions a 3x3 pattern
271 int base_x = ctx.object.x_ + (sx * 4);
272 int base_y = ctx.object.y_ + (sy * 4);
273
274 // Draw 3x3 pattern (centered or offset in 4x4 space)
275 for (int y = 0; y < 3; ++y) {
276 for (int x = 0; x < 3; ++x) {
277 size_t tile_idx = static_cast<size_t>(y * 3 + x);
278 if (tile_idx < ctx.tiles.size()) {
279 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
280 ctx.tiles[tile_idx]);
281 }
282 }
283 }
284 }
285 }
286}
287
289 // ASM: RoomDraw_4x4FloorIn4x4SuperSquare ($018FA5)
290 // Most common floor pattern - draws 4x4 floor tiles in super square units.
291 // Uses RoomDraw_A_Many32x32Blocks internally in assembly.
292 int size_x = (ctx.object.size_ & 0x0F) + 1;
293 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
294
295 if (ctx.tiles.size() < 16) return; // Need 16 tiles for 4x4
296
297 for (int sy = 0; sy < size_y; ++sy) {
298 for (int sx = 0; sx < size_x; ++sx) {
299 int base_x = ctx.object.x_ + (sx * 4);
300 int base_y = ctx.object.y_ + (sy * 4);
301
302 // Draw 4x4 pattern
303 for (int y = 0; y < 4; ++y) {
304 for (int x = 0; x < 4; ++x) {
305 size_t tile_idx = static_cast<size_t>(y * 4 + x);
306 if (tile_idx < ctx.tiles.size()) {
307 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
308 ctx.tiles[tile_idx]);
309 }
310 }
311 }
312 }
313 }
314}
315
317 // ASM: RoomDraw_4x4FloorOneIn4x4SuperSquare ($018FA2)
318 // Single 4x4 floor pattern (starts at different tile offset in assembly).
319 // For our purposes, same as 4x4FloorIn4x4SuperSquare with offset tiles.
320 int size_x = (ctx.object.size_ & 0x0F) + 1;
321 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
322
323 // Use tiles starting at offset (assembly uses $046A index)
324 size_t tile_offset = 0;
325 if (ctx.tiles.size() >= 32) tile_offset = 16; // Use second set if available
326
327 if (ctx.tiles.size() < tile_offset + 16) {
328 // Fall back to standard 4x4
330 return;
331 }
332
333 for (int sy = 0; sy < size_y; ++sy) {
334 for (int sx = 0; sx < size_x; ++sx) {
335 int base_x = ctx.object.x_ + (sx * 4);
336 int base_y = ctx.object.y_ + (sy * 4);
337
338 for (int y = 0; y < 4; ++y) {
339 for (int x = 0; x < 4; ++x) {
340 size_t tile_idx = tile_offset + static_cast<size_t>(y * 4 + x);
341 if (tile_idx < ctx.tiles.size()) {
342 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
343 ctx.tiles[tile_idx]);
344 }
345 }
346 }
347 }
348 }
349}
350
352 // ASM: RoomDraw_4x4FloorTwoIn4x4SuperSquare ($018F9D)
353 // Two 4x4 floor patterns (uses $0490 offset in assembly).
354 int size_x = (ctx.object.size_ & 0x0F) + 1;
355 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
356
357 // Use tiles starting at different offset
358 size_t tile_offset = 0;
359 if (ctx.tiles.size() >= 48) tile_offset = 32;
360
361 if (ctx.tiles.size() < tile_offset + 16) {
363 return;
364 }
365
366 for (int sy = 0; sy < size_y; ++sy) {
367 for (int sx = 0; sx < size_x; ++sx) {
368 int base_x = ctx.object.x_ + (sx * 4);
369 int base_y = ctx.object.y_ + (sy * 4);
370
371 for (int y = 0; y < 4; ++y) {
372 for (int x = 0; x < 4; ++x) {
373 size_t tile_idx = tile_offset + static_cast<size_t>(y * 4 + x);
374 if (tile_idx < ctx.tiles.size()) {
375 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
376 ctx.tiles[tile_idx]);
377 }
378 }
379 }
380 }
381 }
382}
383
385 // ASM: Object 0xA4 - Big hole pattern
386 // Draws a 4x4 hole pattern that repeats based on size
387 int count = (ctx.object.size_ & 0x0F) + 1;
388
389 if (ctx.tiles.size() < 16) return;
390
391 for (int s = 0; s < count; ++s) {
392 int base_x = ctx.object.x_;
393 int base_y = ctx.object.y_ + (s * 4);
394
395 // Draw 4x4 pattern
396 for (int y = 0; y < 4; ++y) {
397 for (int x = 0; x < 4; ++x) {
398 size_t tile_idx = static_cast<size_t>(y * 4 + x);
399 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
400 ctx.tiles[tile_idx]);
401 }
402 }
403 }
404}
405
407 // ASM: RoomDraw_Spike2x2In4x4SuperSquare ($019708)
408 // Draws 2x2 spike patterns within super square units
409 int size_x = (ctx.object.size_ & 0x0F) + 1;
410 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
411
412 if (ctx.tiles.size() < 4) return;
413
414 for (int sy = 0; sy < size_y; ++sy) {
415 for (int sx = 0; sx < size_x; ++sx) {
416 int base_x = ctx.object.x_ + (sx * 4);
417 int base_y = ctx.object.y_ + (sy * 4);
418
419 // Draw 2x2 spike pattern (centered in 4x4 area)
420 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 1,
421 ctx.tiles[0]);
422 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, base_y + 1,
423 ctx.tiles[1]);
424 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 1, base_y + 2,
425 ctx.tiles[2]);
426 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 2, base_y + 2,
427 ctx.tiles[3]);
428 }
429 }
430}
431
433 // ASM: Object 0xDD - Table rock pattern
434 // 4x4 rock pattern that repeats
435 int count = (ctx.object.size_ & 0x0F) + 1;
436
437 if (ctx.tiles.size() < 16) return;
438
439 for (int s = 0; s < count; ++s) {
440 int base_x = ctx.object.x_ + (s * 4);
441 int base_y = ctx.object.y_;
442
443 for (int y = 0; y < 4; ++y) {
444 for (int x = 0; x < 4; ++x) {
445 size_t tile_idx = static_cast<size_t>(y * 4 + x);
446 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x, base_y + y,
447 ctx.tiles[tile_idx]);
448 }
449 }
450 }
451}
452
454 // ASM: RoomDraw_WaterOverlayA8x8_1to16 ($0195D6) / RoomDraw_WaterOverlayB8x8
455 // NOTE: In the original game, this is an HDMA control object that sets up
456 // the wavy water distortion effect. It doesn't draw tiles directly.
457 // For the editor, we draw the available tile data as a visual indicator.
458
459 int size_x = (ctx.object.size_ & 0x0F) + 1;
460 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
461
462 // Handle case where we have fewer tiles than expected
463 // Water objects load 8 tiles, we'll use what we have
464 if (ctx.tiles.empty()) return;
465
466 size_t num_tiles = ctx.tiles.size();
467
468 for (int sy = 0; sy < size_y; ++sy) {
469 for (int sx = 0; sx < size_x; ++sx) {
470 int base_x = ctx.object.x_ + (sx * 8);
471 int base_y = ctx.object.y_ + (sy * 8);
472
473 // Draw 8x8 area using available tiles with wrapping
474 for (int y = 0; y < 8; ++y) {
475 for (int x = 0; x < 8; ++x) {
476 // Wrap tile index to available tiles
477 size_t tile_idx = static_cast<size_t>((y * 8 + x) % num_tiles);
479 base_x + x,
480 base_y + y,
481 ctx.tiles[tile_idx]);
482 }
483 }
484 }
485 }
486}
487
488// ============================================================================
489// Stair Routines
490// ============================================================================
491
493 // ASM: RoomDraw_InterRoomFatStairsUp ($01A41B)
494 // Uses tile data at obj1088, draws 4x4 pattern
495 // In original game, registers position in $06B0 for transition handling
496 // For editor display, we just draw the visual representation
497
498 if (ctx.tiles.size() < 16) return;
499
500 // Draw 4x4 stair pattern
501 for (int y = 0; y < 4; ++y) {
502 for (int x = 0; x < 4; ++x) {
503 size_t tile_idx = static_cast<size_t>(y * 4 + x);
505 ctx.object.y_ + y, ctx.tiles[tile_idx]);
506 }
507 }
508}
509
511 // ASM: RoomDraw_InterRoomFatStairsDownA ($01A458)
512 // Uses tile data at obj10A8
513 DrawInterRoomFatStairsUp(ctx); // Same visual structure
514}
515
517 // ASM: RoomDraw_InterRoomFatStairsDownB ($01A486)
518 // Uses tile data at obj10A8
519 DrawInterRoomFatStairsUp(ctx); // Same visual structure
520}
521
522void DrawSpiralStairs(const DrawContext& ctx, bool going_up, bool is_upper) {
523 // ASM: RoomDraw_SpiralStairsGoingUpUpper, etc.
524 // Calls RoomDraw_1x3N_rightwards with A=4 -> 4 columns x 3 rows = 12 tiles
525 // Tile order is COLUMN-MAJOR (down first, then right)
526 (void)going_up;
527 (void)is_upper;
528
529 if (ctx.tiles.size() < 12) return;
530
531 // Draw 4x3 pattern in COLUMN-MAJOR order (matching ASM)
532 int tid = 0;
533 for (int x = 0; x < 4; ++x) {
534 for (int y = 0; y < 3; ++y) {
536 ctx.object.y_ + y, ctx.tiles[tid++]);
537 }
538 }
539}
540
541void DrawAutoStairs(const DrawContext& ctx) {
542 // ASM: RoomDraw_AutoStairs* routines
543 // Multi-layer or merged layer stair patterns
544 if (ctx.tiles.size() < 16) return;
545
546 for (int y = 0; y < 4; ++y) {
547 for (int x = 0; x < 4; ++x) {
548 size_t tile_idx = static_cast<size_t>(y * 4 + x);
550 ctx.object.y_ + y, ctx.tiles[tile_idx]);
551 }
552 }
553}
554
556 // ASM: RoomDraw_StraightInterroomStairs* routines
557 // North/South, Up/Down variants
558 if (ctx.tiles.size() < 16) return;
559
560 for (int y = 0; y < 4; ++y) {
561 for (int x = 0; x < 4; ++x) {
562 size_t tile_idx = static_cast<size_t>(y * 4 + x);
564 ctx.object.y_ + y, ctx.tiles[tile_idx]);
565 }
566 }
567}
568
569// ============================================================================
570// Interactive Object Routines
571// ============================================================================
572
573void DrawPrisonCell(const DrawContext& ctx) {
574 // ASM: RoomDraw_PrisonCell ($019C44)
575 // Draws prison cell bars to BOTH BG layers with horizontal flip for symmetry
576 // The ASM writes to $7E2xxx (BG1) and also uses ORA #$4000 for horizontal flip
577 // Pattern: 5 iterations drawing a complex bar pattern
578
579 if (ctx.tiles.size() < 6) return;
580
581 // Prison cell layout based on ASM analysis:
582 // The routine draws 5 columns of bars, each with specific tile patterns
583 // Tiles at positions: (x, y), (x+7, y) for outer bars
584 // Middle bars with horizontal flip on one side
585
586 int base_x = ctx.object.x_;
587 int base_y = ctx.object.y_;
588
589 // Draw the prison cell pattern - 5 vertical bar segments
590 for (int col = 0; col < 5; ++col) {
591 int x_offset = col;
592
593 // Each column has 4 rows of tiles
594 for (int row = 0; row < 4; ++row) {
595 size_t tile_idx = (row < static_cast<int>(ctx.tiles.size())) ? row : 0;
596
597 // Left side bar
598 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + x_offset,
599 base_y + row, ctx.tiles[tile_idx]);
600
601 // Right side bar (mirrored horizontally)
602 auto mirrored_tile = ctx.tiles[tile_idx];
603 mirrored_tile.horizontal_mirror_ = !mirrored_tile.horizontal_mirror_;
604 DrawRoutineUtils::WriteTile8(ctx.target_bg, base_x + 9 - x_offset,
605 base_y + row, mirrored_tile);
606 }
607 }
608
609 // If we have a secondary BG buffer, draw the same pattern there
610 // This ensures the prison bars appear on both background layers
611 if (ctx.HasSecondaryBG()) {
612 for (int col = 0; col < 5; ++col) {
613 int x_offset = col;
614
615 for (int row = 0; row < 4; ++row) {
616 size_t tile_idx = (row < static_cast<int>(ctx.tiles.size())) ? row : 0;
617
618 // Left side bar
619 DrawRoutineUtils::WriteTile8(*ctx.secondary_bg, base_x + x_offset,
620 base_y + row, ctx.tiles[tile_idx]);
621
622 // Right side bar (mirrored)
623 auto mirrored_tile = ctx.tiles[tile_idx];
624 mirrored_tile.horizontal_mirror_ = !mirrored_tile.horizontal_mirror_;
625 DrawRoutineUtils::WriteTile8(*ctx.secondary_bg, base_x + 9 - x_offset,
626 base_y + row, mirrored_tile);
627 }
628 }
629 }
630}
631
632void DrawBigKeyLock(const DrawContext& ctx) {
633 // ASM: RoomDraw_BigKeyLock ($0198AE)
634 // Checks room flags via RoomFlagMask to see if lock is already opened
635 // For editor, we draw the closed state by default
636
637 bool is_opened = false;
638 if (ctx.state) {
639 // Check if this specific lock has been opened via door state
640 is_opened = ctx.state->IsDoorOpen(ctx.room_id, 0); // Lock uses door slot 0
641 }
642
643 if (is_opened) {
644 // Draw open lock (if different tiles available)
645 if (ctx.tiles.size() >= 8) {
646 // Use second set of tiles for open state
647 for (int y = 0; y < 2; ++y) {
648 for (int x = 0; x < 2; ++x) {
650 ctx.object.y_ + y,
651 ctx.tiles[4 + y * 2 + x]);
652 }
653 }
654 return;
655 }
656 }
657
658 // Draw closed lock (2x2 pattern)
659 if (ctx.tiles.size() >= 4) {
660 for (int y = 0; y < 2; ++y) {
661 for (int x = 0; x < 2; ++x) {
663 ctx.object.y_ + y, ctx.tiles[y * 2 + x]);
664 }
665 }
666 }
667}
668
670 // ASM: RoomDraw_BombableFloor ($019B7A)
671 // Checks room flags to see if floor has been bombed
672
673 bool is_bombed = false;
674 if (ctx.state) {
675 is_bombed = ctx.state->IsFloorBombable(ctx.room_id);
676 }
677
678 if (is_bombed) {
679 // Draw hole (use second tile set if available)
680 if (ctx.tiles.size() >= 8) {
681 for (int y = 0; y < 2; ++y) {
682 for (int x = 0; x < 2; ++x) {
684 ctx.object.y_ + y,
685 ctx.tiles[4 + y * 2 + x]);
686 }
687 }
688 return;
689 }
690 }
691
692 // Draw intact floor (2x2 pattern)
693 if (ctx.tiles.size() >= 4) {
694 for (int y = 0; y < 2; ++y) {
695 for (int x = 0; x < 2; ++x) {
697 ctx.object.y_ + y, ctx.tiles[y * 2 + x]);
698 }
699 }
700 }
701}
702
703void DrawMovingWall(const DrawContext& ctx, bool is_west) {
704 // ASM: RoomDraw_MovingWallWest ($019316), RoomDraw_MovingWallEast ($01935C)
705 // Checks if wall has moved based on game state
706 (void)is_west; // Direction affects which way wall moves
707
708 bool has_moved = false;
709 if (ctx.state) {
710 has_moved = ctx.state->IsWallMoved(ctx.room_id);
711 }
712
713 // Draw wall in current position
714 // Size determines wall length
715 int size = (ctx.object.size_ & 0x0F) + 1;
716
717 if (ctx.tiles.size() < 4) return;
718
719 for (int s = 0; s < size; ++s) {
720 int offset = has_moved ? 2 : 0; // Offset position if wall has moved
721 int x = ctx.object.x_ + offset;
722 int y = ctx.object.y_ + (s * 2);
723
724 // Draw 2x2 wall segment
725 for (int dy = 0; dy < 2; ++dy) {
726 for (int dx = 0; dx < 2; ++dx) {
727 DrawRoutineUtils::WriteTile8(ctx.target_bg, x + dx, y + dy,
728 ctx.tiles[dy * 2 + dx]);
729 }
730 }
731 }
732}
733
734// ============================================================================
735// Water Face Variants
736// ============================================================================
737
739 // ASM: RoomDraw_EmptyWaterFace ($019D29)
740 // No water spout, just the face
741 DrawWaterFace(ctx);
742}
743
745 // ASM: RoomDraw_SpittingWaterFace ($019D64)
746 // Face with periodic water spout
747 DrawWaterFace(ctx);
748}
749
751 // ASM: RoomDraw_DrenchingWaterFace ($019D83)
752 // Face with continuous water stream
753 DrawWaterFace(ctx);
754}
755
756// ============================================================================
757// Chest Platform Multi-Part Routines
758// ============================================================================
759
761 // ASM: RoomDraw_ClosedChestPlatform ($018CC7)
762 // Complex structure: horizontal wall top, vertical walls sides
763
764 int size_x = (ctx.object.size_ & 0x0F) + 4; // Width is size + 4
765 int size_y = ((ctx.object.size_ >> 4) & 0x0F) + 1;
766
767 if (ctx.tiles.size() < 16) return;
768
769 // Draw top horizontal wall with corners
770 for (int x = 0; x < size_x; ++x) {
771 // Top row
772 size_t tile_idx = (x == 0) ? 0 : ((x == size_x - 1) ? 2 : 1);
774 ctx.object.y_, ctx.tiles[tile_idx]);
775 }
776
777 // Draw vertical walls on sides
778 for (int y = 1; y < size_y + 1; ++y) {
779 // Left wall
781 ctx.object.y_ + y, ctx.tiles[3]);
782 // Right wall
783 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_ + size_x - 1,
784 ctx.object.y_ + y, ctx.tiles[4]);
785 }
786
787 // Draw bottom horizontal wall with corners
788 int bottom_y = ctx.object.y_ + size_y + 1;
789 for (int x = 0; x < size_x; ++x) {
790 size_t tile_idx = (x == 0) ? 5 : ((x == size_x - 1) ? 7 : 6);
791 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_ + x, bottom_y,
792 ctx.tiles[tile_idx]);
793 }
794}
795
797 // ASM: RoomDraw_ChestPlatformHorizontalWallWithCorners ($018D0D)
798 int width = (ctx.object.size_ & 0x0F) + 1;
799
800 if (ctx.tiles.size() < 3) return;
801
802 for (int x = 0; x < width; ++x) {
803 size_t tile_idx = (x == 0) ? 0 : ((x == width - 1) ? 2 : 1);
805 ctx.object.y_, ctx.tiles[tile_idx]);
806 }
807}
808
810 // ASM: RoomDraw_ChestPlatformVerticalWall ($019E70)
811 int height = (ctx.object.size_ & 0x0F) + 1;
812
813 if (ctx.tiles.empty()) return;
814
815 for (int y = 0; y < height; ++y) {
817 ctx.object.y_ + y, ctx.tiles[0]);
818 }
819}
820
821void RegisterSpecialRoutines(std::vector<DrawRoutineInfo>& registry) {
822 // Note: Routine IDs are assigned based on the assembly routine table
823 // These special routines handle chests, doors, and other non-standard objects
824
825 // Chest routine - uses a wrapper since it needs chest_index
826 registry.push_back(DrawRoutineInfo{
827 .id = 39, // DrawChest (special index)
828 .name = "Chest",
829 .function =
830 [](const DrawContext& ctx) {
831 // Default chest index 0 - actual index tracked externally
832 DrawChest(ctx, 0);
833 },
834 .draws_to_both_bgs = false,
835 .base_width = 2,
836 .base_height = 2,
838 });
839
840 registry.push_back(DrawRoutineInfo{
841 .id = 38, // DrawNothing
842 .name = "Nothing",
843 .function = DrawNothing,
844 .draws_to_both_bgs = false,
845 .base_width = 0,
846 .base_height = 0,
848 });
849
850 registry.push_back(DrawRoutineInfo{
851 .id = 26, // DrawDoorSwitcherer
852 .name = "DoorSwitcherer",
853 .function = DrawDoorSwitcherer,
854 .draws_to_both_bgs = false,
855 .base_width = 1,
856 .base_height = 1,
858 });
859
860 registry.push_back(DrawRoutineInfo{
861 .id = 33, // DrawSomariaLine
862 .name = "SomariaLine",
863 .function = DrawSomariaLine,
864 .draws_to_both_bgs = false,
865 .base_width = 0, // Variable
866 .base_height = 0,
868 });
869
870 registry.push_back(DrawRoutineInfo{
871 .id = 34, // DrawWaterFace
872 .name = "WaterFace",
873 .function = DrawWaterFace,
874 .draws_to_both_bgs = false,
875 .base_width = 2,
876 .base_height = 2,
878 });
879
880 // Stair routines (IDs 83-88)
881 registry.push_back(DrawRoutineInfo{
882 .id = 83, // DrawInterRoomFatStairsUp
883 .name = "InterRoomFatStairsUp",
884 .function = DrawInterRoomFatStairsUp,
885 .draws_to_both_bgs = false,
886 .base_width = 4,
887 .base_height = 4,
889 });
890
891 registry.push_back(DrawRoutineInfo{
892 .id = 84, // DrawInterRoomFatStairsDownA
893 .name = "InterRoomFatStairsDownA",
894 .function = DrawInterRoomFatStairsDownA,
895 .draws_to_both_bgs = false,
896 .base_width = 4,
897 .base_height = 4,
899 });
900
901 registry.push_back(DrawRoutineInfo{
902 .id = 85, // DrawInterRoomFatStairsDownB
903 .name = "InterRoomFatStairsDownB",
904 .function = DrawInterRoomFatStairsDownB,
905 .draws_to_both_bgs = false,
906 .base_width = 4,
907 .base_height = 4,
909 });
910
911 registry.push_back(DrawRoutineInfo{
912 .id = 86, // DrawAutoStairs
913 .name = "AutoStairs",
914 .function = DrawAutoStairs,
915 .draws_to_both_bgs = false,
916 .base_width = 4,
917 .base_height = 4,
919 });
920
921 registry.push_back(DrawRoutineInfo{
922 .id = 87, // DrawStraightInterRoomStairs
923 .name = "StraightInterRoomStairs",
924 .function = DrawStraightInterRoomStairs,
925 .draws_to_both_bgs = false,
926 .base_width = 4,
927 .base_height = 4,
929 });
930
931 // Spiral stairs variants (IDs 88-91)
932 registry.push_back(DrawRoutineInfo{
933 .id = 88, // DrawSpiralStairsGoingUpUpper
934 .name = "SpiralStairsGoingUpUpper",
935 .function =
936 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, true, true); },
937 .draws_to_both_bgs = false,
938 .base_width = 4,
939 .base_height = 3, // 4x3 pattern
941 });
942
943 registry.push_back(DrawRoutineInfo{
944 .id = 89, // DrawSpiralStairsGoingDownUpper
945 .name = "SpiralStairsGoingDownUpper",
946 .function =
947 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, false, true); },
948 .draws_to_both_bgs = false,
949 .base_width = 4,
950 .base_height = 3, // 4x3 pattern
952 });
953
954 registry.push_back(DrawRoutineInfo{
955 .id = 90, // DrawSpiralStairsGoingUpLower
956 .name = "SpiralStairsGoingUpLower",
957 .function =
958 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, true, false); },
959 .draws_to_both_bgs = false,
960 .base_width = 4,
961 .base_height = 3, // 4x3 pattern
963 });
964
965 registry.push_back(DrawRoutineInfo{
966 .id = 91, // DrawSpiralStairsGoingDownLower
967 .name = "SpiralStairsGoingDownLower",
968 .function =
969 [](const DrawContext& ctx) { DrawSpiralStairs(ctx, false, false); },
970 .draws_to_both_bgs = false,
971 .base_width = 4,
972 .base_height = 3, // 4x3 pattern
974 });
975
976 // Interactive object routines (IDs 92-95)
977 registry.push_back(DrawRoutineInfo{
978 .id = 92, // DrawBigKeyLock
979 .name = "BigKeyLock",
980 .function = DrawBigKeyLock,
981 .draws_to_both_bgs = false,
982 .base_width = 2,
983 .base_height = 2,
985 });
986
987 registry.push_back(DrawRoutineInfo{
988 .id = 93, // DrawBombableFloor
989 .name = "BombableFloor",
990 .function = DrawBombableFloor,
991 .draws_to_both_bgs = false,
992 .base_width = 2,
993 .base_height = 2,
995 });
996
997 // Water face variants (IDs 94-96)
998 registry.push_back(DrawRoutineInfo{
999 .id = 94, // DrawEmptyWaterFace
1000 .name = "EmptyWaterFace",
1001 .function = DrawEmptyWaterFace,
1002 .draws_to_both_bgs = false,
1003 .base_width = 2,
1004 .base_height = 2,
1006 });
1007
1008 registry.push_back(DrawRoutineInfo{
1009 .id = 95, // DrawSpittingWaterFace
1010 .name = "SpittingWaterFace",
1011 .function = DrawSpittingWaterFace,
1012 .draws_to_both_bgs = false,
1013 .base_width = 2,
1014 .base_height = 2,
1016 });
1017
1018 registry.push_back(DrawRoutineInfo{
1019 .id = 96, // DrawDrenchingWaterFace
1020 .name = "DrenchingWaterFace",
1021 .function = DrawDrenchingWaterFace,
1022 .draws_to_both_bgs = false,
1023 .base_width = 2,
1024 .base_height = 2,
1026 });
1027
1028 // Chest platform routines - use canonical IDs from DrawRoutineIds
1029 registry.push_back(DrawRoutineInfo{
1031 .name = "ClosedChestPlatform",
1032 .function = DrawClosedChestPlatform,
1033 .draws_to_both_bgs = false,
1034 .base_width = 0, // Variable: width = (size & 0x0F) + 4
1035 .base_height = 0, // Variable: height = ((size >> 4) & 0x0F) + 1
1037 });
1038
1039 // Moving wall routines
1040 registry.push_back(DrawRoutineInfo{
1042 .name = "MovingWallWest",
1043 .function = [](const DrawContext& ctx) {
1044 // Placeholder - actual logic in ObjectDrawer
1045 DrawNothing(ctx);
1046 },
1047 .draws_to_both_bgs = false,
1048 .base_width = 4,
1049 .base_height = 8,
1051 });
1052
1053 registry.push_back(DrawRoutineInfo{
1055 .name = "MovingWallEast",
1056 .function = [](const DrawContext& ctx) {
1057 // Placeholder - actual logic in ObjectDrawer
1058 DrawNothing(ctx);
1059 },
1060 .draws_to_both_bgs = false,
1061 .base_width = 4,
1062 .base_height = 8,
1064 });
1065
1066 registry.push_back(DrawRoutineInfo{
1068 .name = "OpenChestPlatform",
1069 .function = [](const DrawContext& ctx) {
1070 // Open chest platform - draws multi-segment pattern
1071 // Size: width = (size & 0x0F) + 1, segments = ((size >> 4) & 0x0F) * 2 + 5
1072 int width = (ctx.object.size_ & 0x0F) + 1;
1073 int segments = ((ctx.object.size_ >> 4) & 0x0F) * 2 + 5;
1074 // For geometry purposes, just set reasonable bounds
1075 for (int s = 0; s < segments && s < 8; ++s) {
1076 for (int x = 0; x < width && x < 8; ++x) {
1077 if (ctx.tiles.size() > 0) {
1078 size_t idx = (s * width + x) % ctx.tiles.size();
1079 DrawRoutineUtils::WriteTile8(ctx.target_bg,
1080 ctx.object.x_ + x, ctx.object.y_ + s, ctx.tiles[idx]);
1081 }
1082 }
1083 }
1084 },
1085 .draws_to_both_bgs = false,
1086 .base_width = 0, // Variable
1087 .base_height = 0, // Variable
1089 });
1090
1091 // Vertical rails with CORNER+MIDDLE+END pattern (ID 117) - objects 0x8A-0x8C
1092 // Matches horizontal rail 0x22 but in vertical orientation
1093 registry.push_back(DrawRoutineInfo{
1095 .name = "DownwardsHasEdge1x1_1to16_plus23",
1096 .function = [](const DrawContext& ctx) {
1097 // CORNER+MIDDLE+END pattern vertically
1098 int size = ctx.object.size_ & 0x0F;
1099 int count = (size + 1) * 2;
1100 if (ctx.tiles.size() < 3) return;
1101
1102 int tile_y = ctx.object.y_;
1103 // Corner
1104 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y, ctx.tiles[0]);
1105 tile_y++;
1106 // Middle tiles
1107 for (int s = 0; s < count; s++) {
1108 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y, ctx.tiles[1]);
1109 tile_y++;
1110 }
1111 // End tile
1112 DrawRoutineUtils::WriteTile8(ctx.target_bg, ctx.object.x_, tile_y, ctx.tiles[2]);
1113 },
1114 .draws_to_both_bgs = false,
1115 .base_width = 1,
1116 .base_height = 0, // Variable: count + 2
1118 });
1119
1120 // Custom Object routine (ID 130) - Oracle of Secrets objects 0x31, 0x32
1121 // These use external binary files instead of ROM tile data.
1122 registry.push_back(DrawRoutineInfo{
1124 .name = "CustomObject",
1125 .function = [](const DrawContext& ctx) {
1126 // Custom objects use external binary files
1127 // Geometry: dimensions depend on the binary file content
1128 // For now, assume a 4x4 tile pattern as reasonable default
1129 for (int row = 0; row < 4 && row < 4; ++row) {
1130 for (int col = 0; col < 4 && col < 4; ++col) {
1131 if (static_cast<size_t>(row * 4 + col) < ctx.tiles.size()) {
1132 DrawRoutineUtils::WriteTile8(ctx.target_bg,
1133 ctx.object.x_ + col, ctx.object.y_ + row,
1134 ctx.tiles[row * 4 + col]);
1135 }
1136 }
1137 }
1138 },
1139 .draws_to_both_bgs = false,
1140 .base_width = 4, // Default: 4x4 tiles
1141 .base_height = 4,
1143 });
1144}
1145
1146} // namespace draw_routines
1147} // namespace zelda3
1148} // namespace yaze
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
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 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 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 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 DrawWaterFace(const DrawContext &ctx)
Draw water face pattern (2x2)
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 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 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 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 DrawStraightInterRoomStairs(const DrawContext &ctx)
Draw straight inter-room stairs (Type 3 objects 0x21E-0x229)
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 DrawChestPlatformHorizontalWall(const DrawContext &ctx)
Draw chest platform horizontal wall section.
void DrawWaterOverlay8x8_1to16(const DrawContext &ctx)
Draw water overlay 8x8 pattern.
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 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.