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