yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_map.cc
Go to the documentation of this file.
1#include "overworld_map.h"
2
3#include <cstddef>
4#include <cstdint>
5#include <unordered_map>
6#include <vector>
7
8#include "app/core/features.h"
10#include "app/gfx/snes_tile.h"
11#include "app/rom.h"
13
14namespace yaze {
15namespace zelda3 {
16
18 : index_(index), parent_(index), rom_(rom) {
20 // Load parent ID from ROM data if available (for custom ASM versions)
21 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
22 if (asm_version != 0xFF && asm_version >= 0x03) {
23 // For v3+, parent ID is stored in expanded table
25 }
26
27 if (asm_version != 0xFF) {
28 if (asm_version == 0x03) {
30 } else {
31 SetupCustomTileset(asm_version);
32 }
33 } else if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
34 // Pure vanilla ROM but flag enabled - set up custom data manually
36 }
37 // For pure vanilla ROMs, LoadAreaInfo already handles everything
38}
39
40absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
41 std::vector<gfx::Tile16>& tiles16,
42 OverworldBlockset& world_blockset) {
43 game_state_ = game_state;
44 world_ = world;
45 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
46
47 // For large maps in vanilla ROMs, we need to handle special world graphics
48 // This ensures proper rendering of special overworld areas like Zora's Domain
49 if (large_map_ && asm_version == 0xFF) {
50 if (parent_ != index_ && !initialized_) {
51 if (index_ >= kSpecialWorldMapIdStart && index_ <= 0x8A &&
52 index_ != 0x88) {
53 // Most special world areas use the special graphics group
57 } else if (index_ == 0x88) {
58 // Triforce room has special hardcoded values
59 area_graphics_ = 0x51;
60 area_palette_ = 0x00;
61 } else {
62 // Fallback to standard area graphics
65 }
66
67 initialized_ = true;
68 }
69 }
70
73 RETURN_IF_ERROR(BuildTiles16Gfx(tiles16, count))
76 RETURN_IF_ERROR(BuildBitmap(world_blockset))
77 built_ = true;
78 return absl::OkStatus();
79}
80
82 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
83
84 // ZSCustomOverworld ASM Version System:
85 // 0x00-0x02: Legacy versions with limited features
86 // 0x03: Current version with full area size expansion and custom data support
87 // 0xFF: Pure vanilla ROM (no ASM applied)
88
89 // Load message ID and area size based on ASM version
90 if (asm_version < 3 || asm_version == 0xFF) {
91 // v2 and vanilla: use original message table
92 message_id_ = (*rom_)[kOverworldMessageIds + (parent_ * 2)] |
93 ((*rom_)[kOverworldMessageIds + (parent_ * 2) + 1] << 8);
94
95 // Load area size for v2/vanilla
96 if (index_ < 0x80) {
97 // For LW and DW, check the screen size byte
98 // Note: v2 had a bug where large/small values were swapped
99 uint8_t size_byte = (*rom_)[kOverworldScreenSize + (index_ & 0x3F)];
100 switch (size_byte) {
101 case 0:
103 break;
104 case 1:
105 default:
107 break;
108 case 2:
110 break;
111 case 3:
113 break;
114 }
115 } else {
116 // For SW, use hardcoded values for v2 compatibility
117 // Zora's Domain areas (0x81, 0x82, 0x89, 0x8A) are large areas
118 area_size_ =
119 (index_ == 0x81 || index_ == 0x82 || index_ == 0x89 || index_ == 0x8A)
122 }
123 } else {
124 // v3: use expanded message table and area size table
125 // All area sizes are now stored in the expanded table, supporting all size types
127 (*rom_)[kOverworldMessagesExpanded + (parent_ * 2)] |
128 ((*rom_)[kOverworldMessagesExpanded + (parent_ * 2) + 1] << 8);
129 area_size_ =
130 static_cast<AreaSizeEnum>((*rom_)[kOverworldScreenSize + index_]);
131 }
132
133 // Update large_map_ based on area size
135
136 // Load area-specific data based on index range
138 // Light World (LW) areas
144
147
149 sprite_palette_[1] =
151 sprite_palette_[2] =
153
158
159 // For v2/vanilla, use original palette table
160 if (asm_version < 3 || asm_version == 0xFF) {
162 }
163 } else if (index_ < kSpecialWorldMapIdStart) {
164 // Dark World (DW) areas
171
174
175 sprite_palette_[0] =
177 sprite_palette_[1] =
179 sprite_palette_[2] =
181
182 area_music_[0] =
184
185 // For v2/vanilla, use original palette table
186 if (asm_version < 3 || asm_version == 0xFF) {
188 }
189 } else {
190 // Special World (SW) areas (index >= 0x80)
191 // Message ID already loaded above based on ASM version
192
193 // For v3, use expanded sprite tables with full customization support
194 if (asm_version >= 3 && asm_version != 0xFF) {
204
211 } else {
212 // For v2/vanilla, use original sprite tables
219
226 }
227
230
231 // For v2/vanilla, use original palette table and handle special cases
232 // These hardcoded cases are needed for vanilla compatibility
233 if (asm_version < 3 || asm_version == 0xFF) {
235
236 // Handle special world area cases based on ZScream documentation
237 if (index_ == 0x88 || index_ == 0x93) {
238 // Triforce room - special graphics and palette
239 area_graphics_ = 0x51;
240 area_palette_ = 0x00;
241 } else if (index_ == 0x80) {
242 // Master Sword area - use special graphics group
246 } else if (index_ == 0x81 || index_ == 0x82 || index_ == 0x89 ||
247 index_ == 0x8A) {
248 // Zora's Domain areas - use special sprite graphics and area graphics
249 // Note: These are the large area maps that were causing crashes
250 sprite_graphics_[0] = 0x0E;
251 sprite_graphics_[1] = 0x0E;
252 sprite_graphics_[2] = 0x0E;
253
257 } else if (index_ == 0x94) {
258 // Make this the same GFX as the true master sword area
260 (0x80 - kSpecialWorldMapIdStart)];
262 } else if (index_ == 0x95) {
263 // Make this the same GFX as the LW death mountain areas
264 area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x03];
265 area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x03];
266 } else if (index_ == 0x96) {
267 // Make this the same GFX as the pyramid areas
268 area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x5B];
269 area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x5B];
270 } else if (index_ == 0x9C) {
271 // Make this the same GFX as the DW death mountain areas
272 area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x43];
273 area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x43];
274 } else {
275 // Default case - use basic graphics
276 area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x00];
277 area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x00];
278 }
279 }
280 }
281}
282
284 // Set the main palette values based on ZScream logic
285 if (index_ < 0x40 || index_ == 0x95) { // LW
286 main_palette_ = 0;
287 } else if ((index_ >= 0x40 && index_ < 0x80) || index_ == 0x96) { // DW
288 main_palette_ = 1;
289 } else if (index_ >= 0x80 && index_ < 0xA0) { // SW
290 main_palette_ = 0;
291 }
292
293 if (index_ == 0x03 || index_ == 0x05 ||
294 index_ == 0x07) { // LW Death Mountain
295 main_palette_ = 2;
296 } else if (index_ == 0x43 || index_ == 0x45 ||
297 index_ == 0x47) { // DW Death Mountain
298 main_palette_ = 3;
299 } else if (index_ == 0x88 || index_ == 0x93) { // Triforce room
300 main_palette_ = 4;
301 }
302
303 // Set the mosaic values based on ZScream logic
304 switch (index_) {
305 case 0x00: // Leaving Skull Woods / Lost Woods
306 case 0x40:
307 mosaic_expanded_ = {false, true, false, true};
308 break;
309 case 0x02: // Going into Skull woods / Lost Woods west
310 case 0x0A:
311 case 0x42:
312 case 0x4A:
313 mosaic_expanded_ = {false, false, true, false};
314 break;
315 case 0x0F: // Going into Zora's Domain North
316 case 0x10: // Going into Skull Woods / Lost Woods North
317 case 0x11:
318 case 0x50:
319 case 0x51:
320 mosaic_expanded_ = {true, false, false, false};
321 break;
322 case 0x80: // Leaving Zora's Domain, the Master Sword area, and the
323 // Triforce area
324 case 0x81:
325 case 0x88:
326 mosaic_expanded_ = {false, true, false, false};
327 break;
328 default:
329 mosaic_expanded_ = {false, false, false, false};
330 break;
331 }
332
333 // Set up world index for GFX groups
334 int index_world = 0x20;
337 index_world = 0x21;
338 } else if (parent_ == 0x88 || parent_ == 0x93) { // Triforce room
339 index_world = 0x24;
340 }
341
342 const auto overworld_gfx_groups2 =
344
345 // Main Blocksets
346 for (int i = 0; i < 8; i++) {
347 custom_gfx_ids_[i] =
348 (uint8_t)(*rom_)[overworld_gfx_groups2 + (index_world * 8) + i];
349 }
350
351 const auto overworldgfxGroups =
353
354 // Replace the variable tiles with the variable ones
355 uint8_t temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4)];
356 if (temp != 0) {
357 custom_gfx_ids_[3] = temp;
358 } else {
359 custom_gfx_ids_[3] = 0xFF;
360 }
361
362 temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 1];
363 if (temp != 0) {
364 custom_gfx_ids_[4] = temp;
365 } else {
366 custom_gfx_ids_[4] = 0xFF;
367 }
368
369 temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 2];
370 if (temp != 0) {
371 custom_gfx_ids_[5] = temp;
372 } else {
373 custom_gfx_ids_[5] = 0xFF;
374 }
375
376 temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 3];
377 if (temp != 0) {
378 custom_gfx_ids_[6] = temp;
379 } else {
380 custom_gfx_ids_[6] = 0xFF;
381 }
382
383 // Set the animated GFX values
384 if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07 || index_ == 0x43 ||
385 index_ == 0x45 || index_ == 0x47 || index_ == 0x95) {
386 animated_gfx_ = 0x59;
387 } else {
388 animated_gfx_ = 0x5B;
389 }
390
391 // Set the subscreen overlay values
392 subscreen_overlay_ = 0x00FF;
393
394 if (index_ == 0x00 || index_ == 0x01 || index_ == 0x08 || index_ == 0x09 ||
395 index_ == 0x40 || index_ == 0x41 || index_ == 0x48 ||
396 index_ == 0x49) { // Add fog 2 to the lost woods and skull woods
397 subscreen_overlay_ = 0x009D;
398 } else if (index_ == 0x03 || index_ == 0x04 || index_ == 0x0B ||
399 index_ == 0x0C || index_ == 0x05 || index_ == 0x06 ||
400 index_ == 0x0D || index_ == 0x0E ||
401 index_ == 0x07) { // Add the sky BG to LW death mountain
402 subscreen_overlay_ = 0x0095;
403 } else if (index_ == 0x43 || index_ == 0x44 || index_ == 0x4B ||
404 index_ == 0x4C || index_ == 0x45 || index_ == 0x46 ||
405 index_ == 0x4D || index_ == 0x4E ||
406 index_ == 0x47) { // Add the lava to DW death mountain
407 subscreen_overlay_ = 0x009C;
408 } else if (index_ == 0x5B || index_ == 0x5C || index_ == 0x63 ||
409 index_ == 0x64) { // TODO: Might need this one too "index == 0x1B"
410 // but for now I don't think so
411 subscreen_overlay_ = 0x0096;
412 } else if (index_ == 0x80) { // Add fog 1 to the master sword area
413 subscreen_overlay_ = 0x0097;
414 } else if (index_ ==
415 0x88) { // Add the triforce room curtains to the triforce room
416 subscreen_overlay_ = 0x0093;
417 }
418}
419
420void OverworldMap::SetupCustomTileset(uint8_t asm_version) {
421 // Load custom palette and mosaic settings
423 mosaic_ = (*rom_)[OverworldCustomMosaicArray + index_] != 0x00;
424
425 uint8_t mosaicByte = (*rom_)[OverworldCustomMosaicArray + index_];
426 mosaic_expanded_ = {(mosaicByte & 0x08) != 0x00, (mosaicByte & 0x04) != 0x00,
427 (mosaicByte & 0x02) != 0x00, (mosaicByte & 0x01) != 0x00};
428
429 // Load area size for v3
430 if (asm_version >= 3 && asm_version != 0xFF) {
431 uint8_t size_byte = (*rom_)[kOverworldScreenSize + index_];
432 area_size_ = static_cast<AreaSizeEnum>(size_byte);
434 }
435
436 // Load custom GFX groups based on ASM version
437 if (asm_version >= 0x01 && asm_version != 0xFF) {
438 // Load from custom GFX group array
439 for (int i = 0; i < 8; i++) {
440 custom_gfx_ids_[i] =
441 (*rom_)[OverworldCustomTileGFXGroupArray + (index_ * 8) + i];
442 }
444 } else {
445 // Fallback to vanilla logic for ROMs without custom ASM
446 int index_world = 0x20;
449 index_world = 0x21;
450 } else if (parent_ == 0x88 || parent_ == 0x93) { // Triforce room
451 index_world = 0x24;
452 }
453
454 // Main Blocksets
455 for (int i = 0; i < 8; i++) {
456 custom_gfx_ids_[i] =
458 (index_world * 8) + i];
459 }
460
461 const auto overworldgfxGroups =
463
464 // Replace the variable tiles with the variable ones
465 // If the variable is 00 set it to 0xFF which is the new "don't load
466 // anything" value
467 uint8_t temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4)];
468 if (temp != 0x00) {
469 custom_gfx_ids_[3] = temp;
470 } else {
471 custom_gfx_ids_[3] = 0xFF;
472 }
473
474 temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 1];
475 if (temp != 0x00) {
476 custom_gfx_ids_[4] = temp;
477 } else {
478 custom_gfx_ids_[4] = 0xFF;
479 }
480
481 temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 2];
482 if (temp != 0x00) {
483 custom_gfx_ids_[5] = temp;
484 } else {
485 custom_gfx_ids_[5] = 0xFF;
486 }
487
488 temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4) + 3];
489 if (temp != 0x00) {
490 custom_gfx_ids_[6] = temp;
491 } else {
492 custom_gfx_ids_[6] = 0xFF;
493 }
494
495 // Set the animated GFX values
496 if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07 || index_ == 0x43 ||
497 index_ == 0x45 || index_ == 0x47) {
498 animated_gfx_ = 0x59;
499 } else {
500 animated_gfx_ = 0x5B;
501 }
502 }
503
504 // Load subscreen overlay
507}
508
511 main_gfx_id_ = 0x20;
512 } else if (parent_ >= kDarkWorldMapIdStart &&
514 main_gfx_id_ = 0x21;
515 } else if (parent_ >= kSpecialWorldMapIdStart) {
516 // Special world maps - use appropriate graphics ID based on the specific map
517 if (parent_ == 0x88) {
518 main_gfx_id_ = 0x24;
519 } else {
520 // Default special world graphics ID
521 main_gfx_id_ = 0x20;
522 }
523 }
524}
525
527 int static_graphics_base = 0x73;
528 static_graphics_[8] = static_graphics_base + 0x00;
529 static_graphics_[9] = static_graphics_base + 0x01;
530 static_graphics_[10] = static_graphics_base + 0x06;
531 static_graphics_[11] = static_graphics_base + 0x07;
532
533 for (int i = 0; i < 4; i++) {
534 static_graphics_[12 + i] =
536 (sprite_graphics_[game_state_] * 4) + i] +
537 static_graphics_base);
538 }
539}
540
542 for (int i = 0; i < 8; i++) {
545 (main_gfx_id_ * 8) + i];
546 }
547}
548
549// For animating water tiles on the overworld map.
550// We want to swap out static_graphics_[07] with the next sheet
551// Usually it is 5A, so we make it 5B instead.
552// There is a middle frame which contains tiles from the bottom half
553// of the 5A sheet, so this will need some special manipulation to make work
554// during the BuildBitmap step (or a new one specifically for animating).
556 if (static_graphics_[7] == 0x5B) {
557 static_graphics_[7] = 0x5A;
558 } else {
559 if (static_graphics_[7] == 0x59) {
560 static_graphics_[7] = 0x58;
561 }
562 static_graphics_[7] = 0x5B;
563 }
564}
565
567 for (int i = 0; i < 4; i++) {
568 uint8_t value = (*rom_)[rom_->version_constants().kOverworldGfxGroups1 +
569 (area_graphics_ * 4) + i];
570 if (value != 0) {
571 static_graphics_[3 + i] = value;
572 }
573 }
574}
575
576// TODO: Change the conditions for death mountain gfx
577// JaredBrian: This is how ZS did it, but in 3.0.4 I changed it to just check
578// for 03, 05, 07, and the DW ones as that's how it would appear in-game if
579// you were to make area 03 not a large area anymore for example, so you might
580// want to do the same.
582 static_graphics_[7] = (((parent_ >= 0x03 && parent_ <= 0x07) ||
583 (parent_ >= 0x0B && parent_ <= 0x0E)) ||
584 ((parent_ >= 0x43 && parent_ <= 0x47) ||
585 (parent_ >= 0x4B && parent_ <= 0x4E)))
586 ? 0x59
587 : 0x5B;
588}
589
597
598namespace palette_internal {
599
600absl::Status SetColorsPalette(Rom& rom, int index, gfx::SnesPalette& current,
603 gfx::SnesPalette hud, gfx::SnesColor bgrcolor,
605 // Palettes infos, color 0 of a palette is always transparent (the arrays
606 // contains 7 colors width wide) There is 16 color per line so 16*Y
607
608 // Left side of the palette - Main, Animated
609 std::array<gfx::SnesColor, 256> new_palette = {};
610
611 // Main Palette, Location 0,2 : 35 colors [7x5]
612 int k = 0;
613 for (int y = 2; y < 7; y++) {
614 for (int x = 1; x < 8; x++) {
615 new_palette[x + (16 * y)] = main[k];
616 k++;
617 }
618 }
619
620 // Animated Palette, Location 0,7 : 7colors
621 for (int x = 1; x < 8; x++) {
622 new_palette[(16 * 7) + (x)] = animated[(x - 1)];
623 }
624
625 // Right side of the palette - Aux1, Aux2
626
627 // Aux1 Palette, Location 8,2 : 21 colors [7x3]
628 k = 0;
629 for (int y = 2; y < 5; y++) {
630 for (int x = 9; x < 16; x++) {
631 new_palette[x + (16 * y)] = aux1[k];
632 k++;
633 }
634 }
635
636 // Aux2 Palette, Location 8,5 : 21 colors [7x3]
637 k = 0;
638 for (int y = 5; y < 8; y++) {
639 for (int x = 9; x < 16; x++) {
640 new_palette[x + (16 * y)] = aux2[k];
641 k++;
642 }
643 }
644
645 // Hud Palette, Location 0,0 : 32 colors [16x2]
646 for (int i = 0; i < 32; i++) {
647 new_palette[i] = hud[i];
648 }
649
650 // Hardcoded grass color (that might change to become invisible instead)
651 for (int i = 0; i < 8; i++) {
652 new_palette[(i * 16)] = bgrcolor;
653 new_palette[(i * 16) + 8] = bgrcolor;
654 }
655
656 // Sprite Palettes
657 k = 0;
658 for (int y = 8; y < 9; y++) {
659 for (int x = 1; x < 8; x++) {
660 new_palette[x + (16 * y)] = rom.palette_group().sprites_aux1[1][k];
661 k++;
662 }
663 }
664
665 // Sprite Palettes
666 k = 0;
667 for (int y = 8; y < 9; y++) {
668 for (int x = 9; x < 16; x++) {
669 new_palette[x + (16 * y)] = rom.palette_group().sprites_aux3[0][k];
670 k++;
671 }
672 }
673
674 // Sprite Palettes
675 k = 0;
676 for (int y = 9; y < 13; y++) {
677 for (int x = 1; x < 16; x++) {
678 new_palette[x + (16 * y)] = rom.palette_group().global_sprites[0][k];
679 k++;
680 }
681 }
682
683 // Sprite Palettes
684 k = 0;
685 for (int y = 13; y < 14; y++) {
686 for (int x = 1; x < 8; x++) {
687 new_palette[x + (16 * y)] = spr[k];
688 k++;
689 }
690 }
691
692 // Sprite Palettes
693 k = 0;
694 for (int y = 14; y < 15; y++) {
695 for (int x = 1; x < 8; x++) {
696 new_palette[x + (16 * y)] = spr2[k];
697 k++;
698 }
699 }
700
701 // Sprite Palettes
702 k = 0;
703 for (int y = 15; y < 16; y++) {
704 for (int x = 1; x < 16; x++) {
705 new_palette[x + (16 * y)] = rom.palette_group().armors[0][k];
706 k++;
707 }
708 }
709
710 for (int i = 0; i < 256; i++) {
711 current[i] = new_palette[i];
712 current[(i / 16) * 16].set_transparent(true);
713 }
714
715 current.set_size(256);
716 return absl::OkStatus();
717}
718} // namespace palette_internal
719
720absl::StatusOr<gfx::SnesPalette> OverworldMap::GetPalette(
721 const gfx::PaletteGroup& palette_group, int index, int previous_index,
722 int limit) {
723 if (index == 255) {
725 (previous_index * 4)];
726 }
727 if (index >= limit) {
728 index = limit - 1;
729 }
730 return palette_group[index];
731}
732
734 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
735
736 int previous_pal_id = 0;
737 int previous_spr_pal_id = 0;
738
739 if (index_ > 0) {
740 // Load previous palette ID based on ASM version
741 if (asm_version < 3 || asm_version == 0xFF) {
742 previous_pal_id = (*rom_)[kOverworldMapPaletteIds + parent_ - 1];
743 } else {
744 // v3 uses expanded palette table
745 previous_pal_id = (*rom_)[kOverworldPalettesScreenToSetNew + parent_ - 1];
746 }
747
748 previous_spr_pal_id = (*rom_)[kOverworldSpritePaletteIds + parent_ - 1];
749 }
750
751 area_palette_ = std::min((int)area_palette_, 0xA3);
752
753 uint8_t pal0 = 0;
754 uint8_t pal1 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
755 (area_palette_ * 4)];
756 uint8_t pal2 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
757 (area_palette_ * 4) + 1];
758 uint8_t pal3 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
759 (area_palette_ * 4) + 2];
760 uint8_t pal4 = (*rom_)[kOverworldSpritePaletteGroup +
762 uint8_t pal5 = (*rom_)[kOverworldSpritePaletteGroup +
763 (sprite_palette_[game_state_] * 2) + 1];
764
765 auto grass_pal_group = rom_->palette_group().grass;
766 auto bgr = grass_pal_group[0][0];
767
768 // Handle 0xFF palette references (use previous palette)
769 if (pal1 == 0xFF) {
771 (previous_pal_id * 4)];
772 }
773
774 if (pal2 == 0xFF) {
776 (previous_pal_id * 4) + 1];
777 }
778
779 if (pal3 == 0xFF) {
781 (previous_pal_id * 4) + 2];
782 }
783
784 auto ow_aux_pal_group = rom_->palette_group().overworld_aux;
786 GetPalette(ow_aux_pal_group, pal1, previous_pal_id, 20));
788 GetPalette(ow_aux_pal_group, pal2, previous_pal_id, 20));
789
790 // Set background color based on world type and area-specific settings
791 bool use_area_specific_bg =
793 if (use_area_specific_bg) {
794 // Use area-specific background color from custom array
798 << 8);
799 // Convert 15-bit SNES color to palette color
801 } else {
802 // Use default world-based background colors
804 bgr = grass_pal_group[0][0]; // LW
805 } else if (parent_ >= kDarkWorldMapIdStart &&
807 bgr = grass_pal_group[0][1]; // DW
808 } else if (parent_ >= 128 && parent_ < kNumOverworldMaps) {
809 bgr = grass_pal_group[0][2]; // SW
810 }
811 }
812
813 // Use main palette from the overworld map data (matches ZScream logic)
814 pal0 = main_palette_;
815
816 auto ow_main_pal_group = rom_->palette_group().overworld_main;
818 GetPalette(ow_main_pal_group, pal0, previous_pal_id, 255));
819 auto ow_animated_pal_group = rom_->palette_group().overworld_animated;
821 GetPalette(ow_animated_pal_group, std::min((int)pal3, 13),
822 previous_pal_id, 14));
823
824 auto hud_pal_group = rom_->palette_group().hud;
825 gfx::SnesPalette hud = hud_pal_group[0];
826
827 // Handle 0xFF sprite palette references (use previous sprite palette)
828 if (pal4 == 0xFF) {
829 pal4 = (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2)];
830 }
831
832 if (pal4 == 0xFF) {
833 pal4 = 0; // Fallback to 0 if still 0xFF
834 }
835
836 if (pal5 == 0xFF) {
837 pal5 =
838 (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2) + 1];
839 }
840
841 if (pal5 == 0xFF) {
842 pal5 = 0; // Fallback to 0 if still 0xFF
843 }
844
846 GetPalette(rom_->palette_group().sprites_aux3, pal4,
847 previous_spr_pal_id, 24));
849 GetPalette(rom_->palette_group().sprites_aux3, pal5,
850 previous_spr_pal_id, 24));
851
853 *rom_, parent_, current_palette_, main, animated, aux1, aux2, hud, bgr,
854 spr, spr2));
855
856 if (palettesets_.count(area_palette_) == 0) {
858 main, animated, aux1, aux2, bgr, hud, spr, spr2, current_palette_};
859 }
860
861 return absl::OkStatus();
862}
863
865 uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
866
867 // Load overlays based on ROM version
868 if (asm_version == 0xFF) {
869 // Vanilla ROM - load overlay from overlay pointers
870 return LoadVanillaOverlayData();
871 } else {
872 // Custom overworld ROM - use overlay from custom data
874 has_overlay_ = (overlay_id_ != 0x00FF);
875 overlay_data_.clear();
876 return absl::OkStatus();
877 }
878}
879
881
882 // Load vanilla overlay for this map (interactive overlays for revealing holes/changing elements)
883 int address = (kOverlayPointersBank << 16) +
884 ((*rom_)[kOverlayPointers + (index_ * 2) + 1] << 8) +
885 (*rom_)[kOverlayPointers + (index_ * 2)];
886
887 // Convert SNES address to PC address
888 address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF);
889
890 // Check if custom overlay code is present
891 if ((*rom_)[kOverlayData1] == 0x6B) {
892 // Use custom overlay data pointer
893 address = ((*rom_)[kOverlayData2 + 2 + (index_ * 3)] << 16) +
894 ((*rom_)[kOverlayData2 + 1 + (index_ * 3)] << 8) +
895 (*rom_)[kOverlayData2 + (index_ * 3)];
896 address = ((address & 0x7F0000) >> 1) | (address & 0x7FFF);
897 }
898
899 // Validate address
900 if (address >= rom_->size()) {
901 has_overlay_ = false;
902 overlay_id_ = 0;
903 overlay_data_.clear();
904 return absl::OkStatus();
905 }
906
907 // Parse overlay data (interactive overlays)
908 overlay_data_.clear();
909 uint8_t b = (*rom_)[address];
910
911 // Parse overlay commands until we hit END (0x60)
912 while (b != 0x60 && address < rom_->size()) {
913 overlay_data_.push_back(b);
914
915 // Handle different overlay commands
916 switch (b) {
917 case 0xA9: // LDA #$
918 if (address + 2 < rom_->size()) {
919 overlay_data_.push_back((*rom_)[address + 1]);
920 overlay_data_.push_back((*rom_)[address + 2]);
921 address += 3;
922 } else {
923 address++;
924 }
925 break;
926 case 0xA2: // LDX #$
927 if (address + 2 < rom_->size()) {
928 overlay_data_.push_back((*rom_)[address + 1]);
929 overlay_data_.push_back((*rom_)[address + 2]);
930 address += 3;
931 } else {
932 address++;
933 }
934 break;
935 case 0x8D: // STA $xxxx
936 if (address + 3 < rom_->size()) {
937 overlay_data_.push_back((*rom_)[address + 1]);
938 overlay_data_.push_back((*rom_)[address + 2]);
939 overlay_data_.push_back((*rom_)[address + 3]);
940 address += 4;
941 } else {
942 address++;
943 }
944 break;
945 case 0x9D: // STA $xxxx,x
946 if (address + 3 < rom_->size()) {
947 overlay_data_.push_back((*rom_)[address + 1]);
948 overlay_data_.push_back((*rom_)[address + 2]);
949 overlay_data_.push_back((*rom_)[address + 3]);
950 address += 4;
951 } else {
952 address++;
953 }
954 break;
955 case 0x8F: // STA $xxxxxx
956 if (address + 4 < rom_->size()) {
957 overlay_data_.push_back((*rom_)[address + 1]);
958 overlay_data_.push_back((*rom_)[address + 2]);
959 overlay_data_.push_back((*rom_)[address + 3]);
960 overlay_data_.push_back((*rom_)[address + 4]);
961 address += 5;
962 } else {
963 address++;
964 }
965 break;
966 case 0x1A: // INC A
967 address++;
968 break;
969 case 0x4C: // JMP
970 if (address + 3 < rom_->size()) {
971 overlay_data_.push_back((*rom_)[address + 1]);
972 overlay_data_.push_back((*rom_)[address + 2]);
973 overlay_data_.push_back((*rom_)[address + 3]);
974 address += 4;
975 } else {
976 address++;
977 }
978 break;
979 default:
980 address++;
981 break;
982 }
983
984 if (address < rom_->size()) {
985 b = (*rom_)[address];
986 } else {
987 break;
988 }
989 }
990
991 // Add the END command if we found it
992 if (b == 0x60) {
993 overlay_data_.push_back(0x60);
994 }
995
996 // Set overlay ID based on map index (simplified)
998 has_overlay_ = !overlay_data_.empty();
999
1000 return absl::OkStatus();
1001}
1002
1003void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
1004 int size, uint8_t* all_gfx) {
1005 // Ensure we don't go out of bounds
1006 int max_offset = static_graphics_offset * size + size;
1007 if (max_offset > rom_->graphics_buffer().size()) {
1008 // Fill with zeros if out of bounds
1009 for (int i = 0; i < size; i++) {
1010 current_gfx_[(index * size) + i] = 0x00;
1011 }
1012 return;
1013 }
1014
1015 for (int i = 0; i < size; i++) {
1016 auto byte = all_gfx[i + (static_graphics_offset * size)];
1017 switch (index) {
1018 case 0:
1019 case 3:
1020 case 4:
1021 case 5:
1022 byte += 0x88;
1023 break;
1024 }
1025 current_gfx_[(index * size) + i] = byte;
1026 }
1027}
1028
1030 if (current_gfx_.size() == 0)
1031 current_gfx_.resize(0x10000, 0x00);
1032
1033 // Process the 8 main graphics sheets (slots 0-7)
1034 for (int i = 0; i < 8; i++) {
1035 if (static_graphics_[i] != 0) {
1037 rom_->graphics_buffer().data());
1038 }
1039 }
1040
1041 // Process sprite graphics (slots 8-15)
1042 for (int i = 8; i < 16; i++) {
1043 if (static_graphics_[i] != 0) {
1045 rom_->graphics_buffer().data());
1046 }
1047 }
1048
1049 // Process animated graphics if available (slot 16)
1050 if (static_graphics_[16] != 0) {
1052 rom_->graphics_buffer().data());
1053 }
1054
1055 return absl::OkStatus();
1056}
1057
1058absl::Status OverworldMap::BuildTiles16Gfx(std::vector<gfx::Tile16>& tiles16,
1059 int count) {
1060 if (current_blockset_.size() == 0)
1061 current_blockset_.resize(0x100000, 0x00);
1062
1063 const int offsets[] = {0x00, 0x08, 0x400, 0x408};
1064 auto yy = 0;
1065 auto xx = 0;
1066
1067 for (auto i = 0; i < count; i++) {
1068 for (auto tile = 0; tile < 0x04; tile++) {
1069 gfx::TileInfo info = tiles16[i].tiles_info[tile];
1070 int offset = offsets[tile];
1071 for (auto y = 0; y < 0x08; ++y) {
1072 for (auto x = 0; x < 0x08; ++x) {
1073 int mx = x;
1074 int my = y;
1075
1076 if (info.horizontal_mirror_ != 0) {
1077 mx = 0x07 - x;
1078 }
1079
1080 if (info.vertical_mirror_ != 0) {
1081 my = 0x07 - y;
1082 }
1083
1084 int xpos = ((info.id_ % 0x10) * 0x08);
1085 int ypos = (((info.id_ / 0x10)) * 0x400);
1086 int source = ypos + xpos + (x + (y * 0x80));
1087
1088 auto destination = xx + yy + offset + (mx + (my * 0x80));
1090 (current_gfx_[source] & 0x0F) + (info.palette_ * 0x10);
1091 }
1092 }
1093 }
1094
1095 xx += 0x10;
1096 if (xx >= 0x80) {
1097 yy += 0x800;
1098 xx = 0;
1099 }
1100 }
1101
1102 return absl::OkStatus();
1103}
1104
1105absl::Status OverworldMap::BuildBitmap(OverworldBlockset& world_blockset) {
1106 if (bitmap_data_.size() != 0) {
1107 bitmap_data_.clear();
1108 }
1109 bitmap_data_.reserve(0x40000);
1110 for (int i = 0; i < 0x40000; i++) {
1111 bitmap_data_.push_back(0x00);
1112 }
1113
1114 int superY = ((index_ - (world_ * 0x40)) / 0x08);
1115 int superX = index_ - (world_ * 0x40) - (superY * 0x08);
1116
1117 for (int y = 0; y < 0x20; y++) {
1118 for (int x = 0; x < 0x20; x++) {
1119 auto xt = x + (superX * 0x20);
1120 auto yt = y + (superY * 0x20);
1121 gfx::CopyTile8bpp16((x * 0x10), (y * 0x10), world_blockset[xt][yt],
1123 }
1124 }
1125 return absl::OkStatus();
1126}
1127
1128} // namespace zelda3
1129} // namespace yaze
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
auto palette_group() const
Definition rom.h:213
auto size() const
Definition rom.h:202
zelda3_version_pointers version_constants() const
Definition rom.h:221
auto graphics_buffer() const
Definition rom.h:211
static Flags & get()
Definition features.h:79
static std::unordered_map< uint8_t, gfx::Paletteset > palettesets_
SNES Color container.
Definition snes_color.h:38
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void set_size(size_t size)
SNES 16-bit tile metadata container.
Definition snes_tile.h:50
std::vector< uint8_t > current_gfx_
std::vector< uint8_t > current_blockset_
std::array< bool, 4 > mosaic_expanded_
absl::Status LoadVanillaOverlayData()
std::vector< uint8_t > bitmap_data_
void SetupCustomTileset(uint8_t asm_version)
absl::StatusOr< gfx::SnesPalette > GetPalette(const gfx::PaletteGroup &group, int index, int previous_index, int limit)
absl::Status BuildTiles16Gfx(std::vector< gfx::Tile16 > &tiles16, int count)
absl::Status BuildMap(int count, int game_state, int world, std::vector< gfx::Tile16 > &tiles16, OverworldBlockset &world_blockset)
std::array< uint8_t, 3 > sprite_graphics_
absl::Status BuildBitmap(OverworldBlockset &world_blockset)
std::array< uint8_t, 3 > sprite_palette_
std::array< uint8_t, 4 > area_music_
std::array< uint8_t, 16 > static_graphics_
std::array< uint8_t, 8 > custom_gfx_ids_
std::vector< uint8_t > overlay_data_
gfx::SnesPalette current_palette_
void ProcessGraphicsBuffer(int index, int static_graphics_offset, int size, uint8_t *all_gfx)
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
void CopyTile8bpp16(int x, int y, int tile, std::vector< uint8_t > &bitmap, std::vector< uint8_t > &blockset)
Definition snes_tile.cc:366
absl::Status SetColorsPalette(Rom &rom, int index, gfx::SnesPalette &current, gfx::SnesPalette main, gfx::SnesPalette animated, gfx::SnesPalette aux1, gfx::SnesPalette aux2, gfx::SnesPalette hud, gfx::SnesColor bgrcolor, gfx::SnesPalette spr, gfx::SnesPalette spr2)
constexpr int kAreaGfxIdPtr
Definition overworld.h:40
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldSpritePaletteGroup
Definition overworld.h:32
constexpr int kOverworldSpriteset
Definition overworld.h:33
constexpr int kOverworldScreenSize
Definition overworld.h:66
constexpr int kOverlayData1
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomMosaicArray
constexpr int kNumOverworldMaps
Definition overworld.h:119
constexpr int OverworldCustomMainPaletteArray
constexpr int kOverworldSpecialSpritePaletteExpandedTemp
constexpr int kOverworldMapParentIdExpanded
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp
constexpr int kOverworldMusicBeginning
Definition overworld.h:43
constexpr int kOverworldMusicDarkWorld
Definition overworld.h:47
constexpr int kOverlayPointersBank
constexpr int kOverworldSpecialPalGroup
Definition overworld.h:35
constexpr int kOverworldSpritePaletteIds
Definition overworld.h:31
constexpr int OverworldCustomASMHasBeenApplied
constexpr int kOverworldMusicAgahnim
Definition overworld.h:46
constexpr int kOverworldPalettesScreenToSetNew
constexpr int kOverworldMessagesExpanded
constexpr int kOverworldMusicMasterSword
Definition overworld.h:45
constexpr int kOverworldMusicZelda
Definition overworld.h:44
constexpr int kOverworldMessageIds
Definition overworld.h:41
constexpr int kOverlayData2
constexpr int OverworldCustomAnimatedGFXArray
constexpr int kDarkWorldMapIdStart
std::vector< std::vector< uint16_t > > OverworldBlockset
Represents tile32 data for the overworld.
constexpr int OverworldCustomTileGFXGroupArray
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int kOverlayPointers
constexpr int kOverworldMapPaletteIds
Definition overworld.h:30
constexpr int kOverworldSpecialGfxGroup
Definition overworld.h:34
constexpr int OverworldCustomSubscreenOverlayArray
Main namespace for the application.
Room transition destination.
Definition zelda.h:447
Represents a group of palettes.
Represents a set of palettes used in a SNES graphics system.
uint32_t kOverworldMapPaletteGroup
Definition zelda.h:98
uint32_t kOverworldGfxGroups1
Definition zelda.h:94
uint32_t kSpriteBlocksetPointer
Definition zelda.h:109
uint32_t kOverworldGfxGroups2
Definition zelda.h:95