Add ZScreamCustomOverworld ASM v3 and integrate area size support

- Introduced ZScream Custom Overworld ASM to enhance customization of the ALTTP overworld, allowing users to modify various aspects such as palettes, backgrounds, and transitions.
- Updated OverworldEditor to include area size selection for v3 support, enabling users to choose between different area sizes (Small, Large, Wide, Tall).
- Enhanced OverworldMap to load area size information based on the applied ASM version, ensuring compatibility with expanded features.
- Implemented SaveAreaSizes method in Overworld to persist area size settings in the ROM, supporting the new customization capabilities.
- Refactored related classes and methods to accommodate the new area size logic and ensure seamless integration with existing functionalities.
This commit is contained in:
scawful
2025-09-24 15:36:29 -04:00
parent 3ab82a9567
commit 49b4f6d677
6 changed files with 6203 additions and 224 deletions

View File

@@ -243,9 +243,9 @@ void OverworldEditor::DrawToolset() {
}
}
constexpr std::array<const char *, 8> kMapSettingsColumnNames = {
"##WorldId", "##GfxId", "##PalId", "##SprGfxId",
"##5thCol", "##6thCol", "##7thCol", "##8thCol"};
constexpr std::array<const char *, 9> kMapSettingsColumnNames = {
"##WorldId", "##GfxId", "##PalId", "##SprGfxId", "##5thCol",
"##6thCol", "##7thCol", "##8thCol", "##AreaSize"};
void OverworldEditor::DrawOverworldMapSettings() {
if (BeginTable(kOWMapTable.data(), 8, kOWMapFlags, ImVec2(0, 0), -1)) {
@@ -318,7 +318,7 @@ void OverworldEditor::DrawOverworldMapSettings() {
}
void OverworldEditor::DrawCustomOverworldMapSettings() {
if (BeginTable(kOWMapTable.data(), 8, kOWMapFlags, ImVec2(0, 0), -1)) {
if (BeginTable(kOWMapTable.data(), 9, kOWMapFlags, ImVec2(0, 0), -1)) {
for (const auto &name : kMapSettingsColumnNames)
ImGui::TableSetupColumn(name);
@@ -395,6 +395,35 @@ void OverworldEditor::DrawCustomOverworldMapSettings() {
overworld_.mutable_overworld_map(current_map_)->mutable_mosaic());
HOVER_HINT("Enable Mosaic effect for the current map");
TableNextColumn();
// Add area size selection for v3 support
static uint8_t asm_version =
(*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version != 0xFF) {
if (BeginTable("AreaSizeTable", 2, kOWMapFlags, ImVec2(0, 0), -1)) {
ImGui::TableSetupColumn("Area Size");
ImGui::TableSetupColumn("Value");
TableNextColumn();
Text("Area Size");
TableNextColumn();
static const char *area_size_names[] = {"Small (1x1)", "Large (2x2)",
"Wide (2x1)", "Tall (1x2)"};
int current_area_size = static_cast<int>(
overworld_.overworld_map(current_map_)->area_size());
if (ImGui::Combo("##AreaSize", &current_area_size, area_size_names,
4)) {
overworld_.mutable_overworld_map(current_map_)
->SetAreaSize(
static_cast<zelda3::AreaSizeEnum>(current_area_size));
RefreshOverworldMap();
}
ImGui::EndTable();
}
}
ImGui::EndTable();
}
}
@@ -580,10 +609,14 @@ absl::Status OverworldEditor::Copy() {
// Determine width/height in tile16 based on selection bounds
const auto start = ow_map_canvas_.selected_points()[0];
const auto end = ow_map_canvas_.selected_points()[1];
const int start_x = static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
const int end_x = static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
const int start_y = static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
const int end_y = static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
const int start_x =
static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
const int end_x =
static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
const int start_y =
static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
const int end_y =
static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
const int width = end_x - start_x + 1;
const int height = end_y - start_y + 1;
@@ -618,8 +651,10 @@ absl::Status OverworldEditor::Paste() {
const ImVec2 anchor = ow_map_canvas_.drawn_tile_position();
// Compute anchor in tile16 grid within the current map
const int tile16_x = (static_cast<int>(anchor.x) % kOverworldMapSize) / kTile16Size;
const int tile16_y = (static_cast<int>(anchor.y) % kOverworldMapSize) / kTile16Size;
const int tile16_x =
(static_cast<int>(anchor.x) % kOverworldMapSize) / kTile16Size;
const int tile16_y =
(static_cast<int>(anchor.y) % kOverworldMapSize) / kTile16Size;
auto &selected_world =
(current_world_ == 0) ? overworld_.mutable_map_tiles()->light_world

View File

@@ -527,6 +527,7 @@ absl::Status Overworld::Save(Rom *rom) {
RETURN_IF_ERROR(SaveOverworldMaps())
RETURN_IF_ERROR(SaveEntrances())
RETURN_IF_ERROR(SaveExits())
RETURN_IF_ERROR(SaveAreaSizes())
return absl::OkStatus();
}
@@ -1596,5 +1597,29 @@ absl::Status Overworld::SaveMapProperties() {
return absl::OkStatus();
}
absl::Status Overworld::SaveAreaSizes() {
util::logf("Saving V3 Area Sizes");
// Check if this is a v3 ROM
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version < 3 || asm_version == 0xFF) {
return absl::OkStatus(); // Not a v3 ROM, nothing to do
}
// Save area sizes to the expanded table
for (int i = 0; i < kNumOverworldMaps; i++) {
uint8_t area_size_byte = static_cast<uint8_t>(overworld_maps_[i].area_size());
RETURN_IF_ERROR(rom()->WriteByte(kOverworldScreenSize + i, area_size_byte));
}
// Save message IDs to expanded table
for (int i = 0; i < kNumOverworldMaps; i++) {
uint16_t message_id = overworld_maps_[i].message_id();
RETURN_IF_ERROR(rom()->WriteShort(kOverworldMessagesExpanded + (i * 2), message_id));
}
return absl::OkStatus();
}
} // namespace zelda3
} // namespace yaze

View File

@@ -138,6 +138,7 @@ class Overworld {
absl::Status SaveMap32Tiles();
absl::Status SaveMapProperties();
absl::Status SaveAreaSizes();
auto rom() const { return rom_; }
auto mutable_rom() { return rom_; }

View File

@@ -6,10 +6,10 @@
#include <vector>
#include "app/core/features.h"
#include "app/gfx/snes_color.h"
#include "app/gfx/snes_tile.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "util/log.h"
namespace yaze {
namespace zelda3 {
@@ -64,102 +64,81 @@ absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
}
void OverworldMap::LoadAreaInfo() {
if (index_ != kSpecialWorldMapIdStart) {
if (index_ <= 128)
large_map_ = ((*rom_)[kOverworldMapSize + (index_ & 0x3F)] != 0);
else {
large_map_ =
index_ == 129 || index_ == 130 || index_ == 137 || index_ == 138;
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
// Load message ID and area size based on ASM version
if (asm_version < 3 || asm_version == 0xFF) {
// v2 and vanilla: use original message table
message_id_ = (*rom_)[kOverworldMessageIds + (parent_ * 2)] |
((*rom_)[kOverworldMessageIds + (parent_ * 2) + 1] << 8);
// Load area size for v2/vanilla
if (index_ < 0x80) {
// For LW and DW, check the screen size byte
uint8_t size_byte = (*rom_)[kOverworldScreenSize + (index_ & 0x3F)];
switch (size_byte) {
case 0:
area_size_ = AreaSizeEnum::LargeArea;
break;
case 1:
default:
area_size_ = AreaSizeEnum::SmallArea;
break;
case 2:
area_size_ = AreaSizeEnum::WideArea;
break;
case 3:
area_size_ = AreaSizeEnum::TallArea;
break;
}
} else {
// For SW, use hardcoded values for v2 compatibility
area_size_ =
(index_ == 0x81 || index_ == 0x82 || index_ == 0x89 || index_ == 0x8A)
? AreaSizeEnum::LargeArea
: AreaSizeEnum::SmallArea;
}
}
auto message_id = rom_->ReadWord(kOverworldMessageIds + (parent_ * 2));
if (message_id.ok()) {
message_id_ = message_id.value();
} else {
message_id_ = 0;
util::logf("Error reading message id for map %d", parent_);
// v3: use expanded message table and area size table
message_id_ =
(*rom_)[kOverworldMessagesExpanded + (parent_ * 2)] |
((*rom_)[kOverworldMessagesExpanded + (parent_ * 2) + 1] << 8);
area_size_ =
static_cast<AreaSizeEnum>((*rom_)[kOverworldScreenSize + index_]);
}
// Update large_map_ based on area size
large_map_ = (area_size_ == AreaSizeEnum::LargeArea);
// Load area-specific data based on index range
if (index_ < kDarkWorldMapIdStart) {
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
area_music_[0] = (*rom_)[kOverworldMusicBeginning + parent_];
area_music_[1] = (*rom_)[kOverworldMusicZelda + parent_];
area_music_[2] = (*rom_)[kOverworldMusicMasterSword + parent_];
area_music_[3] = (*rom_)[kOverworldMusicAgahnim + parent_];
// Light World (LW) areas
sprite_graphics_[0] = (*rom_)[kOverworldSpriteset + parent_];
sprite_graphics_[1] =
(*rom_)[kOverworldSpriteset + parent_ + kDarkWorldMapIdStart];
sprite_graphics_[2] =
(*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];
sprite_palette_[0] = (*rom_)[kOverworldSpritePaletteIds + parent_];
sprite_palette_[1] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kDarkWorldMapIdStart];
sprite_palette_[2] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
} else if (index_ < kSpecialWorldMapIdStart) {
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
area_music_[0] = (*rom_)[kOverworldMusicDarkWorld + (parent_ - 64)];
sprite_graphics_[0] =
(*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
sprite_graphics_[1] =
(*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
sprite_graphics_[2] =
(*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
area_music_[0] = (*rom_)[kOverworldMusicBeginning + parent_];
area_music_[1] = (*rom_)[kOverworldMusicZelda + parent_];
area_music_[2] = (*rom_)[kOverworldMusicMasterSword + parent_];
area_music_[3] = (*rom_)[kOverworldMusicAgahnim + parent_];
sprite_palette_[0] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
sprite_palette_[1] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
sprite_palette_[2] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
} else {
if (index_ == 0x94) {
parent_ = 0x80;
} else if (index_ == 0x95) {
parent_ = 0x03;
} else if (index_ == 0x96) {
parent_ = 0x5B; // pyramid bg use 0x5B map
} else if (index_ == 0x97) {
parent_ = 0x00; // pyramid bg use 0x5B map
} else if (index_ == 0x9C) {
parent_ = 0x43;
} else if (index_ == 0x9D) {
parent_ = 0x00;
} else if (index_ == 0x9E) {
parent_ = 0x00;
} else if (index_ == 0x9F) {
parent_ = 0x2C;
} else if (index_ == 0x88) {
parent_ = 0x88;
} else if (index_ == 129 || index_ == 130 || index_ == 137 ||
index_ == 138) {
parent_ = 129;
}
area_palette_ =
(*rom_)[kOverworldSpecialPalGroup + parent_ - kSpecialWorldMapIdStart];
if ((index_ >= kSpecialWorldMapIdStart && index_ <= 0x8A &&
index_ != 0x88) ||
index_ == 0x94) {
area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
(parent_ - kSpecialWorldMapIdStart)];
area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
} else if (index_ == 0x88) {
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else {
// pyramid bg use 0x5B map
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
// For v2/vanilla, use original palette table
if (asm_version < 3 || asm_version == 0xFF) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
}
} else if (index_ < kSpecialWorldMapIdStart) {
// Dark World (DW) areas
sprite_graphics_[0] =
(*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
sprite_graphics_[1] =
@@ -167,47 +146,169 @@ void OverworldMap::LoadAreaInfo() {
sprite_graphics_[2] =
(*rom_)[kOverworldSpriteset + parent_ + kSpecialWorldMapIdStart];
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];
sprite_palette_[0] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
sprite_palette_[1] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
sprite_palette_[2] =
(*rom_)[kOverworldSpritePaletteIds + parent_ + kSpecialWorldMapIdStart];
area_music_[0] =
(*rom_)[kOverworldMusicDarkWorld + (parent_ - kDarkWorldMapIdStart)];
// For v2/vanilla, use original palette table
if (asm_version < 3 || asm_version == 0xFF) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
}
} else {
// Special World (SW) areas
// Message ID already loaded above based on ASM version
// For v3, use expanded sprite tables
if (asm_version >= 3 && asm_version != 0xFF) {
sprite_graphics_[0] =
(*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
kSpecialWorldMapIdStart];
sprite_graphics_[1] =
(*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
kSpecialWorldMapIdStart];
sprite_graphics_[2] =
(*rom_)[kOverworldSpecialSpriteGfxGroupExpandedTemp + parent_ -
kSpecialWorldMapIdStart];
sprite_palette_[0] = (*rom_)[kOverworldSpecialSpritePaletteExpandedTemp +
parent_ - kSpecialWorldMapIdStart];
sprite_palette_[1] = (*rom_)[kOverworldSpecialSpritePaletteExpandedTemp +
parent_ - kSpecialWorldMapIdStart];
sprite_palette_[2] = (*rom_)[kOverworldSpecialSpritePaletteExpandedTemp +
parent_ - kSpecialWorldMapIdStart];
} else {
// For v2/vanilla, use original sprite tables
sprite_graphics_[0] = (*rom_)[kOverworldSpecialGfxGroup + parent_ -
kSpecialWorldMapIdStart];
sprite_graphics_[1] = (*rom_)[kOverworldSpecialGfxGroup + parent_ -
kSpecialWorldMapIdStart];
sprite_graphics_[2] = (*rom_)[kOverworldSpecialGfxGroup + parent_ -
kSpecialWorldMapIdStart];
sprite_palette_[0] = (*rom_)[kOverworldSpecialPalGroup + parent_ -
kSpecialWorldMapIdStart];
sprite_palette_[1] = (*rom_)[kOverworldSpecialPalGroup + parent_ -
kSpecialWorldMapIdStart];
sprite_palette_[2] = (*rom_)[kOverworldSpecialPalGroup + parent_ -
kSpecialWorldMapIdStart];
}
area_graphics_ = (*rom_)[kAreaGfxIdPtr + parent_];
area_palette_ = (*rom_)[kOverworldPalettesScreenToSetNew + parent_];
// For v2/vanilla, use original palette table and handle special cases
if (asm_version < 3 || asm_version == 0xFF) {
area_palette_ = (*rom_)[kOverworldMapPaletteIds + parent_];
// Handle special world area cases
if (index_ == 0x88 || index_ == 0x93) {
area_graphics_ = 0x51;
area_palette_ = 0x00;
} else if (index_ == 0x80) {
area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
(parent_ - kSpecialWorldMapIdStart)];
area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
} else if (index_ == 0x81 || index_ == 0x82 || index_ == 0x89 ||
index_ == 0x8A) {
// Zora's Domain areas - use special sprite graphics
sprite_graphics_[0] = 0x0E;
sprite_graphics_[1] = 0x0E;
sprite_graphics_[2] = 0x0E;
area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
(parent_ - kSpecialWorldMapIdStart)];
area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
} else if (index_ == 0x94) {
// Make this the same GFX as the true master sword area
area_graphics_ = (*rom_)[kOverworldSpecialGfxGroup +
(0x80 - kSpecialWorldMapIdStart)];
area_palette_ = (*rom_)[kOverworldSpecialPalGroup + 1];
} else if (index_ == 0x95) {
// Make this the same GFX as the LW death mountain areas
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x03];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x03];
} else if (index_ == 0x96) {
// Make this the same GFX as the pyramid areas
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x5B];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x5B];
} else if (index_ == 0x9C) {
// Make this the same GFX as the DW death mountain areas
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x43];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x43];
} else {
// Default case
area_graphics_ = (*rom_)[kAreaGfxIdPtr + 0x00];
area_palette_ = (*rom_)[kOverworldMapPaletteIds + 0x00];
}
}
}
}
void OverworldMap::LoadCustomOverworldData() {
// Set the main palette values.
if (index_ < kDarkWorldMapIdStart) {
area_palette_ = 0;
} else if (index_ >= kDarkWorldMapIdStart &&
index_ < kSpecialWorldMapIdStart) {
area_palette_ = 1;
} else if (index_ >= kSpecialWorldMapIdStart && index_ < 0xA0) {
area_palette_ = 0;
// Set the main palette values based on ZScream logic
if (index_ < 0x40 || index_ == 0x95) { // LW
main_palette_ = 0;
} else if ((index_ >= 0x40 && index_ < 0x80) || index_ == 0x96) { // DW
main_palette_ = 1;
} else if (index_ >= 0x80 && index_ < 0xA0) { // SW
main_palette_ = 0;
}
if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07) {
area_palette_ = 2;
} else if (index_ == 0x43 || index_ == 0x45 || index_ == 0x47) {
area_palette_ = 3;
} else if (index_ == 0x88) {
area_palette_ = 4;
if (index_ == 0x03 || index_ == 0x05 ||
index_ == 0x07) { // LW Death Mountain
main_palette_ = 2;
} else if (index_ == 0x43 || index_ == 0x45 ||
index_ == 0x47) { // DW Death Mountain
main_palette_ = 3;
} else if (index_ == 0x88 || index_ == 0x93) { // Triforce room
main_palette_ = 4;
}
// Set the mosaic values.
mosaic_ = index_ == 0x00 || index_ == kDarkWorldMapIdStart ||
index_ == kSpecialWorldMapIdStart || index_ == 0x81 ||
index_ == 0x88;
// Set the mosaic values based on ZScream logic
switch (index_) {
case 0x00: // Leaving Skull Woods / Lost Woods
case 0x40:
mosaic_expanded_ = {false, true, false, true};
break;
case 0x02: // Going into Skull woods / Lost Woods west
case 0x0A:
case 0x42:
case 0x4A:
mosaic_expanded_ = {false, false, true, false};
break;
case 0x0F: // Going into Zora's Domain North
case 0x10: // Going into Skull Woods / Lost Woods North
case 0x11:
case 0x50:
case 0x51:
mosaic_expanded_ = {true, false, false, false};
break;
case 0x80: // Leaving Zora's Domain, the Master Sword area, and the
// Triforce area
case 0x81:
case 0x88:
mosaic_expanded_ = {false, true, false, false};
break;
default:
mosaic_expanded_ = {false, false, false, false};
break;
}
// Set up world index for GFX groups
int index_world = 0x20;
if (parent_ >= kDarkWorldMapIdStart &&
parent_ < kSpecialWorldMapIdStart) // DW
{
parent_ < kSpecialWorldMapIdStart) { // DW
index_world = 0x21;
} else if (parent_ == 0x88) // Triforce room
{
} else if (parent_ == 0x88 || parent_ == 0x93) { // Triforce room
index_world = 0x24;
}
@@ -222,7 +323,8 @@ void OverworldMap::LoadCustomOverworldData() {
const auto overworldgfxGroups =
rom_->version_constants().kOverworldGfxGroups1;
// Replace the variable tiles with the variable ones.
// Replace the variable tiles with the variable ones
uint8_t temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4)];
if (temp != 0) {
custom_gfx_ids_[3] = temp;
@@ -251,121 +353,74 @@ void OverworldMap::LoadCustomOverworldData() {
custom_gfx_ids_[6] = 0xFF;
}
// Set the animated GFX values.
// Set the animated GFX values
if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07 || index_ == 0x43 ||
index_ == 0x45 || index_ == 0x47) {
index_ == 0x45 || index_ == 0x47 || index_ == 0x95) {
animated_gfx_ = 0x59;
} else {
animated_gfx_ = 0x5B;
}
// Set the subscreen overlay values.
// Set the subscreen overlay values
subscreen_overlay_ = 0x00FF;
if (index_ == 0x00 ||
index_ ==
kDarkWorldMapIdStart) // Add fog 2 to the lost woods and skull woods.
{
if (index_ == 0x00 || index_ == 0x01 || index_ == 0x08 || index_ == 0x09 ||
index_ == 0x40 || index_ == 0x41 || index_ == 0x48 ||
index_ == 0x49) { // Add fog 2 to the lost woods and skull woods
subscreen_overlay_ = 0x009D;
} else if (index_ == 0x03 || index_ == 0x05 ||
index_ == 0x07) // Add the sky BG to LW death mountain.
{
} else if (index_ == 0x03 || index_ == 0x04 || index_ == 0x0B ||
index_ == 0x0C || index_ == 0x05 || index_ == 0x06 ||
index_ == 0x0D || index_ == 0x0E ||
index_ == 0x07) { // Add the sky BG to LW death mountain
subscreen_overlay_ = 0x0095;
} else if (index_ == 0x43 || index_ == 0x45 ||
index_ == 0x47) // Add the lava to DW death mountain.
{
} else if (index_ == 0x43 || index_ == 0x44 || index_ == 0x4B ||
index_ == 0x4C || index_ == 0x45 || index_ == 0x46 ||
index_ == 0x4D || index_ == 0x4E ||
index_ == 0x47) { // Add the lava to DW death mountain
subscreen_overlay_ = 0x009C;
} else if (index_ == 0x5B) // TODO: Might need this one too "index == 0x1B"
// but for now I don't think so.
{
} else if (index_ == 0x5B || index_ == 0x5C || index_ == 0x63 ||
index_ == 0x64) { // TODO: Might need this one too "index == 0x1B"
// but for now I don't think so
subscreen_overlay_ = 0x0096;
} else if (index_ == 0x80) // Add fog 1 to the master sword area.
{
} else if (index_ == 0x80) { // Add fog 1 to the master sword area
subscreen_overlay_ = 0x0097;
} else if (index_ ==
0x88) // Add the triforce room curtains to the triforce room.
{
0x88) { // Add the triforce room curtains to the triforce room
subscreen_overlay_ = 0x0093;
}
// Set the main palette values.
if (index_ < 0x40) // LW
{
area_palette_ = 0;
} else if (index_ >= 0x40 && index_ < 0x80) // DW
{
area_palette_ = 1;
} else if (index_ >= 0x80 && index_ < 0xA0) // SW
{
area_palette_ = 0;
}
if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07) // LW Death Mountain
{
area_palette_ = 2;
} else if (index_ == 0x43 || index_ == 0x45 ||
index_ == 0x47) // DW Death Mountain
{
area_palette_ = 3;
} else if (index_ == 0x88) // Triforce room
{
area_palette_ = 4;
}
// Set the mosaic values.
switch (index_) {
case 0x00: // Leaving Skull Woods / Lost Woods
case 0x40:
mosaic_expanded_ = {false, true, false, true};
break;
case 0x02: // Going into Skull woods / Lost Woods west
case 0x0A:
case 0x42:
case 0x4A:
mosaic_expanded_ = {false, false, true, false};
break;
case 0x0F: // Going into Zora's Domain North
case 0x10: // Going into Skull Woods / Lost Woods North
case 0x11:
case 0x50:
case 0x51:
mosaic_expanded_ = {true, false, false, false};
break;
case 0x80: // Leaving Zora's Domain, the Master Sword area, and the
// Triforce area
case 0x81:
case 0x88:
mosaic_expanded_ = {false, true, false, false};
break;
}
}
void OverworldMap::SetupCustomTileset(uint8_t asm_version) {
area_palette_ = (*rom_)[OverworldCustomMainPaletteArray + index_];
// Load custom palette and mosaic settings
main_palette_ = (*rom_)[OverworldCustomMainPaletteArray + index_];
mosaic_ = (*rom_)[OverworldCustomMosaicArray + index_] != 0x00;
uint8_t mosaicByte = (*rom_)[OverworldCustomMosaicArray + index_];
mosaic_expanded_ = {(mosaicByte & 0x08) != 0x00, (mosaicByte & 0x04) != 0x00,
(mosaicByte & 0x02) != 0x00, (mosaicByte & 0x01) != 0x00};
// This is just to load the GFX groups for ROMs that have an older version
// of the Overworld ASM already applied.
// Load area size for v3
if (asm_version >= 3 && asm_version != 0xFF) {
uint8_t size_byte = (*rom_)[kOverworldScreenSize + index_];
area_size_ = static_cast<AreaSizeEnum>(size_byte);
large_map_ = (area_size_ == AreaSizeEnum::LargeArea);
}
// Load custom GFX groups based on ASM version
if (asm_version >= 0x01 && asm_version != 0xFF) {
// Load from custom GFX group array
for (int i = 0; i < 8; i++) {
custom_gfx_ids_[i] =
(*rom_)[OverworldCustomTileGFXGroupArray + (index_ * 8) + i];
}
animated_gfx_ = (*rom_)[OverworldCustomAnimatedGFXArray + index_];
} else {
// Fallback to vanilla logic for ROMs without custom ASM
int index_world = 0x20;
if (parent_ >= kDarkWorldMapIdStart &&
parent_ < kSpecialWorldMapIdStart) // DW
{
parent_ < kSpecialWorldMapIdStart) { // DW
index_world = 0x21;
} else if (parent_ == 0x88) // Triforce room
{
} else if (parent_ == 0x88 || parent_ == 0x93) { // Triforce room
index_world = 0x24;
}
@@ -379,9 +434,9 @@ void OverworldMap::SetupCustomTileset(uint8_t asm_version) {
const auto overworldgfxGroups =
rom_->version_constants().kOverworldGfxGroups1;
// Replace the variable tiles with the variable ones.
// Replace the variable tiles with the variable ones
// If the variable is 00 set it to 0xFF which is the new "don't load
// anything" value.
// anything" value
uint8_t temp = (*rom_)[overworldgfxGroups + (area_graphics_ * 4)];
if (temp != 0x00) {
custom_gfx_ids_[3] = temp;
@@ -410,7 +465,7 @@ void OverworldMap::SetupCustomTileset(uint8_t asm_version) {
custom_gfx_ids_[6] = 0xFF;
}
// Set the animated GFX values.
// Set the animated GFX values
if (index_ == 0x03 || index_ == 0x05 || index_ == 0x07 || index_ == 0x43 ||
index_ == 0x45 || index_ == 0x47) {
animated_gfx_ = 0x59;
@@ -419,6 +474,7 @@ void OverworldMap::SetupCustomTileset(uint8_t asm_version) {
}
}
// Load subscreen overlay
subscreen_overlay_ =
(*rom_)[OverworldCustomSubscreenOverlayArray + (index_ * 2)];
}
@@ -642,10 +698,22 @@ absl::StatusOr<gfx::SnesPalette> OverworldMap::GetPalette(
}
absl::Status OverworldMap::LoadPalette() {
int previous_pal_id =
index_ > 0 ? (*rom_)[kOverworldMapPaletteIds + parent_ - 1] : 0;
int previous_spr_pal_id =
index_ > 0 ? (*rom_)[kOverworldSpritePaletteIds + parent_ - 1] : 0;
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
int previous_pal_id = 0;
int previous_spr_pal_id = 0;
if (index_ > 0) {
// Load previous palette ID based on ASM version
if (asm_version < 3 || asm_version == 0xFF) {
previous_pal_id = (*rom_)[kOverworldMapPaletteIds + parent_ - 1];
} else {
// v3 uses expanded palette table
previous_pal_id = (*rom_)[kOverworldPalettesScreenToSetNew + parent_ - 1];
}
previous_spr_pal_id = (*rom_)[kOverworldSpritePaletteIds + parent_ - 1];
}
area_palette_ = std::min((int)area_palette_, 0xA3);
@@ -664,33 +732,50 @@ absl::Status OverworldMap::LoadPalette() {
auto grass_pal_group = rom_->palette_group().grass;
auto bgr = grass_pal_group[0][0];
// Handle 0xFF palette references (use previous palette)
if (pal1 == 0xFF) {
pal1 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4)];
}
if (pal2 == 0xFF) {
pal2 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4) + 1];
}
if (pal3 == 0xFF) {
pal3 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4) + 2];
}
auto ow_aux_pal_group = rom_->palette_group().overworld_aux;
ASSIGN_OR_RETURN(gfx::SnesPalette aux1,
GetPalette(ow_aux_pal_group, pal1, previous_pal_id, 20));
ASSIGN_OR_RETURN(gfx::SnesPalette aux2,
GetPalette(ow_aux_pal_group, pal2, previous_pal_id, 20));
// Additional handling of `pal3` and `parent_`
if (pal3 == 255) {
pal3 = (*rom_)[rom_->version_constants().kOverworldMapPaletteGroup +
(previous_pal_id * 4) + 2];
// Set background color based on world type and area-specific settings
bool use_area_specific_bg = (*rom_)[OverworldCustomAreaSpecificBGEnabled] != 0x00;
if (use_area_specific_bg) {
// Use area-specific background color from custom array
area_specific_bg_color_ = (*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2)] |
((*rom_)[OverworldCustomAreaSpecificBGPalette + (parent_ * 2) + 1] << 8);
// Convert 15-bit SNES color to palette color
bgr = gfx::SnesColor(area_specific_bg_color_);
} else {
// Use default world-based background colors
if (parent_ < kDarkWorldMapIdStart) {
bgr = grass_pal_group[0][0]; // LW
} else if (parent_ >= kDarkWorldMapIdStart &&
parent_ < kSpecialWorldMapIdStart) {
bgr = grass_pal_group[0][1]; // DW
} else if (parent_ >= 128 && parent_ < kNumOverworldMaps) {
bgr = grass_pal_group[0][2]; // SW
}
}
if (parent_ < kDarkWorldMapIdStart) {
pal0 = parent_ == 0x03 || parent_ == 0x05 || parent_ == 0x07 ? 2 : 0;
bgr = grass_pal_group[0][0];
} else if (parent_ >= kDarkWorldMapIdStart &&
parent_ < kSpecialWorldMapIdStart) {
pal0 = parent_ == 0x43 || parent_ == 0x45 || parent_ == 0x47 ? 3 : 1;
bgr = grass_pal_group[0][1];
} else if (parent_ >= 128 && parent_ < kNumOverworldMaps) {
pal0 = 0;
bgr = grass_pal_group[0][2];
}
if (parent_ == 0x88) {
pal0 = 4;
}
// Use main palette from the overworld map data (matches ZScream logic)
pal0 = main_palette_;
auto ow_main_pal_group = rom_->palette_group().overworld_main;
ASSIGN_OR_RETURN(gfx::SnesPalette main,
@@ -703,6 +788,23 @@ absl::Status OverworldMap::LoadPalette() {
auto hud_pal_group = rom_->palette_group().hud;
gfx::SnesPalette hud = hud_pal_group[0];
// Handle 0xFF sprite palette references (use previous sprite palette)
if (pal4 == 0xFF) {
pal4 = (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2)];
}
if (pal4 == 0xFF) {
pal4 = 0; // Fallback to 0 if still 0xFF
}
if (pal5 == 0xFF) {
pal5 = (*rom_)[kOverworldSpritePaletteGroup + (previous_spr_pal_id * 2) + 1];
}
if (pal5 == 0xFF) {
pal5 = 0; // Fallback to 0 if still 0xFF
}
ASSIGN_OR_RETURN(gfx::SnesPalette spr,
GetPalette(rom_->palette_group().sprites_aux3, pal4,
previous_spr_pal_id, 24));

View File

@@ -17,6 +17,7 @@ namespace zelda3 {
static constexpr int kTileOffsets[] = {0, 8, 4096, 4104};
// 1 byte, not 0 if enabled
// vanilla, v2, v3
constexpr int OverworldCustomASMHasBeenApplied = 0x140145;
// 2 bytes for each overworld area (0x140)
@@ -54,6 +55,24 @@ constexpr int OverworldCustomTileGFXGroupArray = 0x140480;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148;
// v3 expanded constants
constexpr int kOverworldMessagesExpanded = 0x1417F8;
constexpr int kOverworldMapParentIdExpanded = 0x140998;
constexpr int kOverworldTransitionPositionYExpanded = 0x140F38;
constexpr int kOverworldTransitionPositionXExpanded = 0x141078;
constexpr int kOverworldScreenTileMapChangeByScreen1Expanded = 0x140A38;
constexpr int kOverworldScreenTileMapChangeByScreen2Expanded = 0x140B78;
constexpr int kOverworldScreenTileMapChangeByScreen3Expanded = 0x140CB8;
constexpr int kOverworldScreenTileMapChangeByScreen4Expanded = 0x140DF8;
constexpr int kOverworldSpecialSpriteGFXGroup = 0x016811;
constexpr int kOverworldSpecialGFXGroup = 0x016821;
constexpr int kOverworldSpecialPALGroup = 0x016831;
constexpr int kOverworldSpecialSpritePalette = 0x016841;
constexpr int kOverworldPalettesScreenToSetNew = 0x4C635;
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp = 0x0166E1;
constexpr int kOverworldSpecialSpritePaletteExpandedTemp = 0x016701;
constexpr int kDarkWorldMapIdStart = 0x40;
constexpr int kSpecialWorldMapIdStart = 0x80;
@@ -71,6 +90,13 @@ typedef struct OverworldMapTiles {
OverworldBlockset special_world; // 32 maps
} OverworldMapTiles;
enum class AreaSizeEnum {
SmallArea = 0,
LargeArea = 1,
WideArea = 2,
TallArea = 3,
};
/**
* @brief Represents a single Overworld map screen.
*/
@@ -109,6 +135,15 @@ class OverworldMap : public gfx::GfxContext {
auto area_music(int i) const { return area_music_[i]; }
auto static_graphics(int i) const { return static_graphics_[i]; }
auto large_index() const { return large_index_; }
auto area_size() const { return area_size_; }
auto main_palette() const { return main_palette_; }
void set_main_palette(uint8_t palette) { main_palette_ = palette; }
auto area_specific_bg_color() const { return area_specific_bg_color_; }
void set_area_specific_bg_color(uint16_t color) {
area_specific_bg_color_ = color;
}
auto mutable_current_graphics() { return &current_gfx_; }
auto mutable_area_graphics() { return &area_graphics_; }
@@ -133,6 +168,7 @@ class OverworldMap : public gfx::GfxContext {
parent_ = parent_index;
large_index_ = quadrant;
large_map_ = true;
area_size_ = AreaSizeEnum::LargeArea;
}
void SetAsSmallMap(int index = -1) {
@@ -142,6 +178,12 @@ class OverworldMap : public gfx::GfxContext {
parent_ = index_;
large_index_ = 0;
large_map_ = false;
area_size_ = AreaSizeEnum::SmallArea;
}
void SetAreaSize(AreaSizeEnum size) {
area_size_ = size;
large_map_ = (size == AreaSizeEnum::LargeArea);
}
void Destroy() {
@@ -185,25 +227,29 @@ class OverworldMap : public gfx::GfxContext {
int index, int previous_index,
int limit);
Rom *rom_;
Rom* rom_;
bool built_ = false;
bool large_map_ = false;
bool initialized_ = false;
bool mosaic_ = false;
int index_ = 0; // Map index
int parent_ = 0; // Parent map index
int large_index_ = 0; // Quadrant ID [0-3]
int world_ = 0; // World ID [0-2]
int game_state_ = 0; // Game state [0-2]
int main_gfx_id_ = 0; // Main Gfx ID
int index_ = 0; // Map index
int parent_ = 0; // Parent map index
int large_index_ = 0; // Quadrant ID [0-3]
int world_ = 0; // World ID [0-2]
int game_state_ = 0; // Game state [0-2]
int main_gfx_id_ = 0; // Main Gfx ID
AreaSizeEnum area_size_ = AreaSizeEnum::SmallArea; // Area size for v3
uint16_t message_id_ = 0;
uint8_t area_graphics_ = 0;
uint8_t area_palette_ = 0;
uint8_t main_palette_ = 0; // Custom Overworld Main Palette ID
uint8_t animated_gfx_ = 0; // Custom Overworld Animated ID
uint16_t subscreen_overlay_ = 0; // Custom Overworld Subscreen Overlay ID
uint16_t area_specific_bg_color_ =
0; // Custom Overworld Area-Specific Background Color
std::array<uint8_t, 8> custom_gfx_ids_;
std::array<uint8_t, 3> sprite_graphics_;