diff --git a/src/app/core/features.h b/src/app/core/features.h index e10ef334..263ce672 100644 --- a/src/app/core/features.h +++ b/src/app/core/features.h @@ -65,7 +65,8 @@ class FeatureFlags { // Save overworld properties to the Rom. bool kSaveOverworldProperties = true; - // Load custom overworld data from the ROM and enable UI. + // Enable custom overworld features for vanilla ROMs or override detection. + // If ZSCustomOverworld ASM is already applied, features are auto-enabled. bool kLoadCustomOverworld = false; // Apply ZSCustomOverworld ASM patches when upgrading ROM versions. @@ -134,8 +135,19 @@ struct FlagsMenu { &FeatureFlags::get().overworld.kSaveOverworldItems); Checkbox("Save Overworld Properties", &FeatureFlags::get().overworld.kSaveOverworldProperties); - Checkbox("Load Custom Overworld", + Checkbox("Enable Custom Overworld Features", &FeatureFlags::get().overworld.kLoadCustomOverworld); + ImGui::SameLine(); + if (ImGui::Button("?")) { + ImGui::OpenPopup("CustomOverworldHelp"); + } + if (ImGui::BeginPopup("CustomOverworldHelp")) { + ImGui::Text("This flag enables ZSCustomOverworld features."); + ImGui::Text("If ZSCustomOverworld ASM is already applied to the ROM,"); + ImGui::Text("features are auto-enabled regardless of this flag."); + ImGui::Text("For vanilla ROMs, enable this to use custom features."); + ImGui::EndPopup(); + } Checkbox("Apply ZSCustomOverworld ASM", &FeatureFlags::get().overworld.kApplyZSCustomOverworldASM); } diff --git a/src/app/editor/overworld/map_properties.cc b/src/app/editor/overworld/map_properties.cc index 96488048..110774a0 100644 --- a/src/app/editor/overworld/map_properties.cc +++ b/src/app/editor/overworld/map_properties.cc @@ -155,6 +155,12 @@ void MapPropertiesSystem::DrawMapPropertiesPanel(int current_map, bool& show_map ImGui::EndTabItem(); } + // Music Tab + if (ImGui::BeginTabItem("Music")) { + DrawMusicTab(current_map); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); } } @@ -521,6 +527,43 @@ void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) { } HOVER_HINT("Enable Mosaic effect for the current map"); + // Add music editing controls + TableNextColumn(); ImGui::Text("Music (Beginning)"); + TableNextColumn(); + if (gui::InputHexByte("##Music0", + overworld_->mutable_overworld_map(current_map)->mutable_area_music(0), + kInputFieldSize)) { + RefreshMapProperties(); + } + HOVER_HINT("Music track for game beginning state"); + + TableNextColumn(); ImGui::Text("Music (Zelda)"); + TableNextColumn(); + if (gui::InputHexByte("##Music1", + overworld_->mutable_overworld_map(current_map)->mutable_area_music(1), + kInputFieldSize)) { + RefreshMapProperties(); + } + HOVER_HINT("Music track for Zelda rescued state"); + + TableNextColumn(); ImGui::Text("Music (Master Sword)"); + TableNextColumn(); + if (gui::InputHexByte("##Music2", + overworld_->mutable_overworld_map(current_map)->mutable_area_music(2), + kInputFieldSize)) { + RefreshMapProperties(); + } + HOVER_HINT("Music track for Master Sword obtained state"); + + TableNextColumn(); ImGui::Text("Music (Agahnim)"); + TableNextColumn(); + if (gui::InputHexByte("##Music3", + overworld_->mutable_overworld_map(current_map)->mutable_area_music(3), + kInputFieldSize)) { + RefreshMapProperties(); + } + HOVER_HINT("Music track for Agahnim defeated state"); + ImGui::EndTable(); } } @@ -667,6 +710,74 @@ void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) { } } +void MapPropertiesSystem::DrawMusicTab(int current_map) { + ImGui::Text("Music Settings for Different Game States:"); + Separator(); + + if (BeginTable("MusicSettings", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Game State", ImGuiTableColumnFlags_WidthFixed, 150); + ImGui::TableSetupColumn("Music Track ID", ImGuiTableColumnFlags_WidthStretch); + + const char* music_state_names[] = { + "Beginning (Pre-Zelda)", + "Zelda Rescued", + "Master Sword Obtained", + "Agahnim Defeated" + }; + + const char* music_descriptions[] = { + "Music before rescuing Zelda", + "Music after rescuing Zelda from Hyrule Castle", + "Music after obtaining the Master Sword", + "Music after defeating Agahnim (Dark World)" + }; + + for (int i = 0; i < 4; i++) { + TableNextColumn(); + ImGui::Text("%s", music_state_names[i]); + + TableNextColumn(); + if (gui::InputHexByte(absl::StrFormat("##Music%d", i).c_str(), + overworld_->mutable_overworld_map(current_map)->mutable_area_music(i), + kInputFieldSize)) { + RefreshMapProperties(); + + // Update the ROM directly since music is not automatically saved + int music_address = 0; + switch (i) { + case 0: music_address = zelda3::kOverworldMusicBeginning + current_map; break; + case 1: music_address = zelda3::kOverworldMusicZelda + current_map; break; + case 2: music_address = zelda3::kOverworldMusicMasterSword + current_map; break; + case 3: music_address = zelda3::kOverworldMusicAgahnim + current_map; break; + } + + if (music_address > 0) { + (*rom_)[music_address] = *overworld_->mutable_overworld_map(current_map)->mutable_area_music(i); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s", music_descriptions[i]); + } + } + + ImGui::EndTable(); + } + + Separator(); + ImGui::Text("Music tracks control the background music for different"); + ImGui::Text("game progression states on this overworld map."); + + // Show common music track IDs for reference + Separator(); + ImGui::Text("Common Music Track IDs:"); + ImGui::BulletText("0x02 - Overworld Theme"); + ImGui::BulletText("0x05 - Kakariko Village"); + ImGui::BulletText("0x07 - Lost Woods"); + ImGui::BulletText("0x09 - Dark World Theme"); + ImGui::BulletText("0x0F - Ganon's Tower"); + ImGui::BulletText("0x11 - Death Mountain"); +} + void MapPropertiesSystem::RefreshMapProperties() { // Implementation would refresh map properties } diff --git a/src/app/editor/overworld/map_properties.h b/src/app/editor/overworld/map_properties.h index c9e96dfa..0280ecbc 100644 --- a/src/app/editor/overworld/map_properties.h +++ b/src/app/editor/overworld/map_properties.h @@ -64,6 +64,7 @@ class MapPropertiesSystem { void DrawSpritePropertiesTab(int current_map); void DrawCustomFeaturesTab(int current_map); void DrawTileGraphicsTab(int current_map); + void DrawMusicTab(int current_map); // Utility methods void RefreshMapProperties(); diff --git a/src/app/editor/overworld/overworld_editor.cc b/src/app/editor/overworld/overworld_editor.cc index 1a157205..1e795ed7 100644 --- a/src/app/editor/overworld/overworld_editor.cc +++ b/src/app/editor/overworld/overworld_editor.cc @@ -802,6 +802,8 @@ void OverworldEditor::CheckForOverworldEdits() { // Calculate the index within the overall map structure int index_x = local_map_x * tiles_per_local_map + tile16_x; int index_y = local_map_y * tiles_per_local_map + tile16_y; + overworld_.set_current_world(current_world_); + overworld_.set_current_map(current_map_); int tile16_id = overworld_.GetTileFromPosition( ow_map_canvas_.selected_tiles()[i]); selected_world[index_x][index_y] = tile16_id; @@ -818,6 +820,8 @@ void OverworldEditor::CheckForSelectRectangle() { // Single tile case if (ow_map_canvas_.selected_tile_pos().x != -1) { + overworld_.set_current_world(current_world_); + overworld_.set_current_map(current_map_); current_tile16_ = overworld_.GetTileFromPosition(ow_map_canvas_.selected_tile_pos()); ow_map_canvas_.set_selected_tile_pos(ImVec2(-1, -1)); @@ -831,13 +835,18 @@ void OverworldEditor::CheckForSelectRectangle() { } if (ow_map_canvas_.selected_tiles().size() > 0) { + // Set the current world and map in overworld for proper tile lookup + overworld_.set_current_world(current_world_); + overworld_.set_current_map(current_map_); for (auto &each : ow_map_canvas_.selected_tiles()) { tile16_ids.push_back(overworld_.GetTileFromPosition(each)); } } } // Create a composite image of all the tile16s selected - ow_map_canvas_.DrawBitmapGroup(tile16_ids, tile16_blockset_, 0x10); + if (!tile16_ids.empty()) { + ow_map_canvas_.DrawBitmapGroup(tile16_ids, tile16_blockset_, 0x10, ow_map_canvas_.global_scale()); + } } absl::Status OverworldEditor::Copy() { @@ -847,6 +856,8 @@ absl::Status OverworldEditor::Copy() { !ow_map_canvas_.selected_tiles().empty()) { std::vector ids; ids.reserve(ow_map_canvas_.selected_tiles().size()); + overworld_.set_current_world(current_world_); + overworld_.set_current_map(current_map_); for (const auto &pos : ow_map_canvas_.selected_tiles()) { ids.push_back(overworld_.GetTileFromPosition(pos)); } @@ -972,8 +983,22 @@ absl::Status OverworldEditor::CheckForCurrentMap() { parent_map_y * kOverworldMapSize, large_map_size, large_map_size); } else { - const int current_map_x = current_highlighted_map % 8; - const int current_map_y = current_highlighted_map / 8; + // Calculate map coordinates accounting for world offset + int current_map_x, current_map_y; + if (current_world_ == 0) { + // Light World (0x00-0x3F) + current_map_x = current_highlighted_map % 8; + current_map_y = current_highlighted_map / 8; + } else if (current_world_ == 1) { + // Dark World (0x40-0x7F) + current_map_x = (current_highlighted_map - 0x40) % 8; + current_map_y = (current_highlighted_map - 0x40) / 8; + } else { + // Special World (0x80-0x9F) - use display coordinates based on current_world_ + // The special world maps are displayed in the same 8x8 grid as LW/DW + current_map_x = (current_highlighted_map - 0x80) % 8; + current_map_y = (current_highlighted_map - 0x80) / 8; + } ow_map_canvas_.DrawOutline(current_map_x * kOverworldMapSize, current_map_y * kOverworldMapSize, kOverworldMapSize, kOverworldMapSize); @@ -1012,7 +1037,12 @@ void OverworldEditor::CheckForMousePan() { void OverworldEditor::DrawOverworldCanvas() { if (all_gfx_loaded_) { - if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) { + // Use ASM version with flag as override to determine UI + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool use_custom_overworld = (asm_version != 0xFF) || + core::FeatureFlags::get().overworld.kLoadCustomOverworld; + + if (use_custom_overworld) { map_properties_system_->DrawSimplifiedMapSettings( current_world_, current_map_, current_map_lock_, show_map_properties_panel_, show_custom_bg_color_editor_, @@ -1434,6 +1464,7 @@ absl::Status OverworldEditor::Save() { } if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) { RETURN_IF_ERROR(overworld_.SaveMapProperties()); + RETURN_IF_ERROR(overworld_.SaveMusic()); } return absl::OkStatus(); } diff --git a/src/app/gui/canvas.cc b/src/app/gui/canvas.cc index cd9b2027..d9550f99 100644 --- a/src/app/gui/canvas.cc +++ b/src/app/gui/canvas.cc @@ -559,8 +559,22 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) { const float scaled_size = tile_size * scale; static bool dragging = false; constexpr int small_map_size = 0x200; - int superY = current_map / 8; - int superX = current_map % 8; + + // Calculate superX and superY accounting for world offset + int superY, superX; + if (current_map < 0x40) { + // Light World + superY = current_map / 8; + superX = current_map % 8; + } else if (current_map < 0x80) { + // Dark World + superY = (current_map - 0x40) / 8; + superX = (current_map - 0x40) % 8; + } else { + // Special World + superY = (current_map - 0x80) / 8; + superX = (current_map - 0x80) % 8; + } // Handle right click for single tile selection if (IsMouseClicked(ImGuiMouseButton_Right)) { @@ -758,6 +772,11 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, int i = 0; for (int y = 0; y < tiles_per_col + 1; ++y) { for (int x = 0; x < tiles_per_row + 1; ++x) { + // Check bounds to prevent access violations + if (i >= static_cast(group.size())) { + break; + } + int tile_id = group[i]; // Check if tile_id is within the range of tile16_individual_ @@ -770,10 +789,15 @@ void Canvas::DrawBitmapGroup(std::vector &group, gfx::Tilemap &tilemap, // Draw the tile bitmap at the calculated position gfx::RenderTile(tilemap, tile_id); - DrawBitmap(tilemap.tile_bitmaps[tile_id], tile_pos_x, tile_pos_y, scale, - 150.0f); - i++; + if (tilemap.tile_bitmaps.find(tile_id) != tilemap.tile_bitmaps.end()) { + DrawBitmap(tilemap.tile_bitmaps[tile_id], tile_pos_x, tile_pos_y, scale, 150); + } } + i++; + } + // Break outer loop if we've run out of tiles + if (i >= static_cast(group.size())) { + break; } } diff --git a/src/app/zelda3/overworld/overworld.cc b/src/app/zelda3/overworld/overworld.cc index 29d98b01..727b5c1a 100644 --- a/src/app/zelda3/overworld/overworld.cc +++ b/src/app/zelda3/overworld/overworld.cc @@ -120,8 +120,12 @@ absl::Status Overworld::AssembleMap32Tiles() { rom()->version_constants().kMap32TileTR, rom()->version_constants().kMap32TileBL, rom()->version_constants().kMap32TileBR}; - if (rom()->data()[kMap32ExpandedFlagPos] != 0x04 && - core::FeatureFlags::get().overworld.kLoadCustomOverworld) { + + // Use ASM version to determine expanded tile32 support, with flag as override + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool use_custom_overworld = (asm_version != 0xFF) || + core::FeatureFlags::get().overworld.kLoadCustomOverworld; + if (rom()->data()[kMap32ExpandedFlagPos] != 0x04 && use_custom_overworld) { map32address[0] = rom()->version_constants().kMap32TileTL; map32address[1] = kMap32TileTRExpanded; map32address[2] = kMap32TileBLExpanded; @@ -169,8 +173,12 @@ absl::Status Overworld::AssembleMap32Tiles() { absl::Status Overworld::AssembleMap16Tiles() { int tpos = kMap16Tiles; int num_tile16 = kNumTile16Individual; - if (rom()->data()[kMap16ExpandedFlagPos] != 0x0F && - core::FeatureFlags::get().overworld.kLoadCustomOverworld) { + + // Use ASM version to determine expanded tile16 support, with flag as override + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool use_custom_overworld = (asm_version != 0xFF) || + core::FeatureFlags::get().overworld.kLoadCustomOverworld; + if (rom()->data()[kMap16ExpandedFlagPos] != 0x0F && use_custom_overworld) { tpos = kMap16TilesExpanded; num_tile16 = NumberOfMap16Ex; expanded_tile16_ = true; @@ -313,12 +321,17 @@ absl::Status Overworld::LoadEntrances() { int ow_entrance_pos_ptr = kOverworldEntrancePos; int ow_entrance_id_ptr = kOverworldEntranceEntranceId; int num_entrances = 129; - if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8 && - core::FeatureFlags::get().overworld.kLoadCustomOverworld) { + + // Use ASM version to determine expanded entrance support, with flag as override + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool use_custom_overworld = (asm_version != 0xFF) || + core::FeatureFlags::get().overworld.kLoadCustomOverworld; + if (rom()->data()[kOverworldEntranceExpandedFlagPos] != 0xB8 && use_custom_overworld) { ow_entrance_map_ptr = kOverworldEntranceMapExpanded; ow_entrance_pos_ptr = kOverworldEntrancePosExpanded; ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded; expanded_entrances_ = true; + num_entrances = 256; // Expanded entrance count } for (int i = 0; i < num_entrances; i++) { @@ -416,16 +429,38 @@ absl::Status Overworld::LoadExits() { } absl::Status Overworld::LoadItems() { - ASSIGN_OR_RETURN(uint32_t pointer, - rom()->ReadLong(zelda3::kOverworldItemsAddress)); - uint32_t pointer_pc = SnesToPc(pointer); // 1BC2F9 -> 0DC2F9 - for (int i = 0; i < 128; i++) { - ASSIGN_OR_RETURN(uint16_t word_address, - rom()->ReadWord(pointer_pc + i * 2)); - uint32_t addr = (pointer & 0xFF0000) | word_address; // 1B F9 3C + // byte asmVersion = ROM.DATA[Constants.OverworldCustomASMHasBeenApplied]; + + // // Version 0x03 of the OW ASM added item support for the SW. + // int maxOW = asmVersion >= 0x03 && asmVersion != 0xFF ? Constants.NumberOfOWMaps : 0x80; + + // int pointerSNES = ROM.ReadLong(Constants.overworldItemsAddress); + // this.ItemPointerAddress = Utils.SnesToPc(pointerSNES); // 0x1BC2F9 -> 0x0DC2F9 + + // Version 0x03 of the OW ASM added item support for the SW. + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + + // Determine max number of overworld maps based on ASM version + int max_ow = (asm_version >= 0x03 && asm_version != 0xFF) ? kNumOverworldMaps : 0x80; + + ASSIGN_OR_RETURN(uint32_t pointer_snes, + rom()->ReadLong(zelda3::overworldItemsAddress)); + uint32_t item_pointer_address = SnesToPc(pointer_snes); // 0x1BC2F9 -> 0x0DC2F9 + + for (int i = 0; i < max_ow; i++) { + ASSIGN_OR_RETURN(uint8_t bank_byte, rom()->ReadByte(zelda3::overworldItemsAddressBank)); + int bank = bank_byte & 0x7F; + + ASSIGN_OR_RETURN(uint8_t addr_low, rom()->ReadByte(item_pointer_address + (i * 2))); + ASSIGN_OR_RETURN(uint8_t addr_high, rom()->ReadByte(item_pointer_address + (i * 2) + 1)); + + uint32_t addr = (bank << 16) + // 1B + (addr_high << 8) + // F9 + addr_low; // 3C addr = SnesToPc(addr); - if (overworld_maps_[i].is_large_map()) { + // Check if this is a large map and skip if not the parent + if (overworld_maps_[i].area_size() != zelda3::AreaSizeEnum::SmallArea) { if (overworld_maps_[i].parent() != (uint8_t)i) { continue; } @@ -442,13 +477,10 @@ absl::Status Overworld::LoadItems() { int p = (((b2 & 0x1F) << 8) + b1) >> 1; - int x = p % 64; + int x = p % 0x40; // Use 0x40 instead of 64 to match ZS int y = p >> 6; - int fakeID = i; - if (fakeID >= 64) { - fakeID -= 64; - } + int fakeID = i % 0x40; // Use modulo 0x40 to match ZS int sy = fakeID / 8; int sx = fakeID - (sy * 8); @@ -467,15 +499,35 @@ absl::Status Overworld::LoadItems() { absl::Status Overworld::LoadSprites() { std::vector> futures; - futures.emplace_back(std::async(std::launch::async, [this]() { - return LoadSpritesFromMap(kOverworldSpritesBeginning, 64, 0); - })); - futures.emplace_back(std::async(std::launch::async, [this]() { - return LoadSpritesFromMap(kOverworldSpritesZelda, 144, 1); - })); - futures.emplace_back(std::async(std::launch::async, [this]() { - return LoadSpritesFromMap(kOverworldSpritesAgahnim, 144, 2); - })); + + // Use ASM version to determine sprite table locations, with flag as override + uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied]; + bool use_custom_overworld = (asm_version != 0xFF) || + core::FeatureFlags::get().overworld.kLoadCustomOverworld; + + if (use_custom_overworld && asm_version >= 3 && asm_version != 0xFF) { + // v3: Use expanded sprite tables + futures.emplace_back(std::async(std::launch::async, [this]() { + return LoadSpritesFromMap(overworldSpritesBeginingExpanded, 64, 0); + })); + futures.emplace_back(std::async(std::launch::async, [this]() { + return LoadSpritesFromMap(overworldSpritesZeldaExpanded, 144, 1); + })); + futures.emplace_back(std::async(std::launch::async, [this]() { + return LoadSpritesFromMap(overworldSpritesAgahnimExpanded, 144, 2); + })); + } else { + // Vanilla/v2: Use original sprite tables + futures.emplace_back(std::async(std::launch::async, [this]() { + return LoadSpritesFromMap(kOverworldSpritesBeginning, 64, 0); + })); + futures.emplace_back(std::async(std::launch::async, [this]() { + return LoadSpritesFromMap(kOverworldSpritesZelda, 144, 1); + })); + futures.emplace_back(std::async(std::launch::async, [this]() { + return LoadSpritesFromMap(kOverworldSpritesAgahnim, 144, 2); + })); + } for (auto &future : futures) { future.wait(); @@ -532,6 +584,7 @@ absl::Status Overworld::Save(Rom *rom) { RETURN_IF_ERROR(SaveOverworldMaps()) RETURN_IF_ERROR(SaveEntrances()) RETURN_IF_ERROR(SaveExits()) + RETURN_IF_ERROR(SaveMusic()) RETURN_IF_ERROR(SaveAreaSizes()) return absl::OkStatus(); } @@ -1602,6 +1655,30 @@ absl::Status Overworld::SaveMapProperties() { return absl::OkStatus(); } +absl::Status Overworld::SaveMusic() { + util::logf("Saving Music Data"); + + // Save music data for Light World maps + for (int i = 0; i < kDarkWorldMapIdStart; i++) { + RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicBeginning + i, + overworld_maps_[i].area_music(0))); + RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicZelda + i, + overworld_maps_[i].area_music(1))); + RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicMasterSword + i, + overworld_maps_[i].area_music(2))); + RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicAgahnim + i, + overworld_maps_[i].area_music(3))); + } + + // Save music data for Dark World maps + for (int i = kDarkWorldMapIdStart; i < kSpecialWorldMapIdStart; i++) { + RETURN_IF_ERROR(rom()->WriteByte(kOverworldMusicDarkWorld + (i - kDarkWorldMapIdStart), + overworld_maps_[i].area_music(0))); + } + + return absl::OkStatus(); +} + absl::Status Overworld::SaveAreaSizes() { util::logf("Saving V3 Area Sizes"); diff --git a/src/app/zelda3/overworld/overworld.h b/src/app/zelda3/overworld/overworld.h index 3ea0cf82..622c8c69 100644 --- a/src/app/zelda3/overworld/overworld.h +++ b/src/app/zelda3/overworld/overworld.h @@ -87,6 +87,29 @@ constexpr int kMap32ExpandedFlagPos = 0x01772E; // 0x04 constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F constexpr int kOverworldEntranceExpandedFlagPos = 0x0DB895; // 0xB8 +constexpr int overworldSpritesBeginingExpanded = 0x141438; +constexpr int overworldSpritesZeldaExpanded = 0x141578; +constexpr int overworldSpritesAgahnimExpanded = 0x1416B8; +constexpr int overworldSpritesDataStartExpanded = 0x04C881; + +constexpr int overworldSpecialSpriteGFXGroupExpandedTemp = 0x0166E1; +constexpr int overworldSpecialSpritePaletteExpandedTemp = 0x016701; + +constexpr int ExpandedOverlaySpace = 0x120000; + +constexpr int overworldTilesType = 0x071459; +constexpr int overworldMessages = 0x03F51D; +constexpr int overworldMessagesExpanded = 0x1417F8; + +constexpr int overworldItemsPointers = 0x0DC2F9; +constexpr int overworldItemsAddress = 0x0DC8B9; // 1BC2F9 +constexpr int overworldItemsAddressBank = 0x0DC8BF; +constexpr int overworldItemsEndData = 0x0DC89C; // 0DC89E + +constexpr int overworldBombDoorItemLocationsNew = 0x012644; +constexpr int overworldItemsPointersNew = 0x012784; +constexpr int overworldItemsStartDataNew = 0x0DC2F9; + constexpr int kOverworldCompressedMapPos = 0x058000; constexpr int kOverworldCompressedOverflowPos = 0x137FFF; @@ -138,6 +161,7 @@ class Overworld { absl::Status SaveMap32Tiles(); absl::Status SaveMapProperties(); + absl::Status SaveMusic(); absl::Status SaveAreaSizes(); auto rom() const { return rom_; } @@ -212,6 +236,7 @@ class Overworld { } auto is_loaded() const { return is_loaded_; } void set_current_map(int i) { current_map_ = i; } + void set_current_world(int world) { current_world_ = world; } auto map_tiles() const { return map_tiles_; } auto mutable_map_tiles() { return &map_tiles_; } auto all_items() const { return all_items_; } diff --git a/src/app/zelda3/overworld/overworld_item.h b/src/app/zelda3/overworld/overworld_item.h index a1b53041..3a69fb44 100644 --- a/src/app/zelda3/overworld/overworld_item.h +++ b/src/app/zelda3/overworld/overworld_item.h @@ -19,6 +19,10 @@ constexpr int kOverworldItemsAddress = 0xDC8B9; // 1BC2F9 constexpr int kOverworldItemsBank = 0xDC8BF; constexpr int kOverworldItemsEndData = 0xDC89C; // 0DC89E +constexpr int kOverworldBombDoorItemLocationsNew = 0x012644; +constexpr int kOverworldItemsPointersNew = 0x012784; +constexpr int kOverworldItemsStartDataNew = 0x0DC2F9; + class OverworldItem : public GameEntity { public: OverworldItem() = default; diff --git a/src/app/zelda3/overworld/overworld_map.cc b/src/app/zelda3/overworld/overworld_map.cc index b1762a8d..1f52f7fb 100644 --- a/src/app/zelda3/overworld/overworld_map.cc +++ b/src/app/zelda3/overworld/overworld_map.cc @@ -18,16 +18,21 @@ OverworldMap::OverworldMap(int index, Rom *rom) : index_(index), parent_(index), rom_(rom) { LoadAreaInfo(); - if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) { - // If the custom overworld ASM has NOT already been applied, manually set - // the vanilla values. - uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; - if (asm_version == 0x00) { + // Use ASM version byte as source of truth, with flag as override + uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied]; + bool use_custom_overworld = (asm_version != 0xFF) || + core::FeatureFlags::get().overworld.kLoadCustomOverworld; + + if (use_custom_overworld) { + if (asm_version == 0x00 || asm_version == 0xFF) { + // No custom ASM applied but flag enabled - set up vanilla values manually LoadCustomOverworldData(); } else { + // Custom overworld ASM applied - set up custom tileset SetupCustomTileset(asm_version); } } + // For vanilla ROMs without flag, LoadAreaInfo already handles everything } absl::Status OverworldMap::BuildMap(int count, int game_state, int world, diff --git a/src/app/zelda3/overworld/overworld_map.h b/src/app/zelda3/overworld/overworld_map.h index 280db89d..a808df70 100644 --- a/src/app/zelda3/overworld/overworld_map.h +++ b/src/app/zelda3/overworld/overworld_map.h @@ -67,6 +67,9 @@ constexpr int kOverworldPalettesScreenToSetNew = 0x4C635; constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp = 0x0166E1; constexpr int kOverworldSpecialSpritePaletteExpandedTemp = 0x016701; +constexpr int transition_target_northExpanded = 0x1411B8; +constexpr int transition_target_westExpanded = 0x1412F8; + constexpr int kDarkWorldMapIdStart = 0x40; constexpr int kSpecialWorldMapIdStart = 0x80;