Enhance overworld editor with music editing features and custom overworld support
- Added a new Music tab in the Map Properties panel for editing music tracks associated with different game states. - Implemented functionality to save music data for both Light and Dark World maps. - Updated feature flags to enable custom overworld features based on ASM version, improving flexibility for ROM modifications. - Enhanced UI elements with tooltips and popups for better user guidance on custom overworld settings.
This commit is contained in:
@@ -65,7 +65,8 @@ class FeatureFlags {
|
|||||||
// Save overworld properties to the Rom.
|
// Save overworld properties to the Rom.
|
||||||
bool kSaveOverworldProperties = true;
|
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;
|
bool kLoadCustomOverworld = false;
|
||||||
|
|
||||||
// Apply ZSCustomOverworld ASM patches when upgrading ROM versions.
|
// Apply ZSCustomOverworld ASM patches when upgrading ROM versions.
|
||||||
@@ -134,8 +135,19 @@ struct FlagsMenu {
|
|||||||
&FeatureFlags::get().overworld.kSaveOverworldItems);
|
&FeatureFlags::get().overworld.kSaveOverworldItems);
|
||||||
Checkbox("Save Overworld Properties",
|
Checkbox("Save Overworld Properties",
|
||||||
&FeatureFlags::get().overworld.kSaveOverworldProperties);
|
&FeatureFlags::get().overworld.kSaveOverworldProperties);
|
||||||
Checkbox("Load Custom Overworld",
|
Checkbox("Enable Custom Overworld Features",
|
||||||
&FeatureFlags::get().overworld.kLoadCustomOverworld);
|
&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",
|
Checkbox("Apply ZSCustomOverworld ASM",
|
||||||
&FeatureFlags::get().overworld.kApplyZSCustomOverworldASM);
|
&FeatureFlags::get().overworld.kApplyZSCustomOverworldASM);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,6 +155,12 @@ void MapPropertiesSystem::DrawMapPropertiesPanel(int current_map, bool& show_map
|
|||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Music Tab
|
||||||
|
if (ImGui::BeginTabItem("Music")) {
|
||||||
|
DrawMusicTab(current_map);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::EndTabBar();
|
ImGui::EndTabBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -521,6 +527,43 @@ void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) {
|
|||||||
}
|
}
|
||||||
HOVER_HINT("Enable Mosaic effect for the 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();
|
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() {
|
void MapPropertiesSystem::RefreshMapProperties() {
|
||||||
// Implementation would refresh map properties
|
// Implementation would refresh map properties
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class MapPropertiesSystem {
|
|||||||
void DrawSpritePropertiesTab(int current_map);
|
void DrawSpritePropertiesTab(int current_map);
|
||||||
void DrawCustomFeaturesTab(int current_map);
|
void DrawCustomFeaturesTab(int current_map);
|
||||||
void DrawTileGraphicsTab(int current_map);
|
void DrawTileGraphicsTab(int current_map);
|
||||||
|
void DrawMusicTab(int current_map);
|
||||||
|
|
||||||
// Utility methods
|
// Utility methods
|
||||||
void RefreshMapProperties();
|
void RefreshMapProperties();
|
||||||
|
|||||||
@@ -802,6 +802,8 @@ void OverworldEditor::CheckForOverworldEdits() {
|
|||||||
// Calculate the index within the overall map structure
|
// Calculate the index within the overall map structure
|
||||||
int index_x = local_map_x * tiles_per_local_map + tile16_x;
|
int index_x = local_map_x * tiles_per_local_map + tile16_x;
|
||||||
int index_y = local_map_y * tiles_per_local_map + tile16_y;
|
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(
|
int tile16_id = overworld_.GetTileFromPosition(
|
||||||
ow_map_canvas_.selected_tiles()[i]);
|
ow_map_canvas_.selected_tiles()[i]);
|
||||||
selected_world[index_x][index_y] = tile16_id;
|
selected_world[index_x][index_y] = tile16_id;
|
||||||
@@ -818,6 +820,8 @@ void OverworldEditor::CheckForSelectRectangle() {
|
|||||||
|
|
||||||
// Single tile case
|
// Single tile case
|
||||||
if (ow_map_canvas_.selected_tile_pos().x != -1) {
|
if (ow_map_canvas_.selected_tile_pos().x != -1) {
|
||||||
|
overworld_.set_current_world(current_world_);
|
||||||
|
overworld_.set_current_map(current_map_);
|
||||||
current_tile16_ =
|
current_tile16_ =
|
||||||
overworld_.GetTileFromPosition(ow_map_canvas_.selected_tile_pos());
|
overworld_.GetTileFromPosition(ow_map_canvas_.selected_tile_pos());
|
||||||
ow_map_canvas_.set_selected_tile_pos(ImVec2(-1, -1));
|
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) {
|
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()) {
|
for (auto &each : ow_map_canvas_.selected_tiles()) {
|
||||||
tile16_ids.push_back(overworld_.GetTileFromPosition(each));
|
tile16_ids.push_back(overworld_.GetTileFromPosition(each));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create a composite image of all the tile16s selected
|
// 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() {
|
absl::Status OverworldEditor::Copy() {
|
||||||
@@ -847,6 +856,8 @@ absl::Status OverworldEditor::Copy() {
|
|||||||
!ow_map_canvas_.selected_tiles().empty()) {
|
!ow_map_canvas_.selected_tiles().empty()) {
|
||||||
std::vector<int> ids;
|
std::vector<int> ids;
|
||||||
ids.reserve(ow_map_canvas_.selected_tiles().size());
|
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()) {
|
for (const auto &pos : ow_map_canvas_.selected_tiles()) {
|
||||||
ids.push_back(overworld_.GetTileFromPosition(pos));
|
ids.push_back(overworld_.GetTileFromPosition(pos));
|
||||||
}
|
}
|
||||||
@@ -972,8 +983,22 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
|||||||
parent_map_y * kOverworldMapSize, large_map_size,
|
parent_map_y * kOverworldMapSize, large_map_size,
|
||||||
large_map_size);
|
large_map_size);
|
||||||
} else {
|
} else {
|
||||||
const int current_map_x = current_highlighted_map % 8;
|
// Calculate map coordinates accounting for world offset
|
||||||
const int current_map_y = current_highlighted_map / 8;
|
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,
|
ow_map_canvas_.DrawOutline(current_map_x * kOverworldMapSize,
|
||||||
current_map_y * kOverworldMapSize,
|
current_map_y * kOverworldMapSize,
|
||||||
kOverworldMapSize, kOverworldMapSize);
|
kOverworldMapSize, kOverworldMapSize);
|
||||||
@@ -1012,7 +1037,12 @@ void OverworldEditor::CheckForMousePan() {
|
|||||||
|
|
||||||
void OverworldEditor::DrawOverworldCanvas() {
|
void OverworldEditor::DrawOverworldCanvas() {
|
||||||
if (all_gfx_loaded_) {
|
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(
|
map_properties_system_->DrawSimplifiedMapSettings(
|
||||||
current_world_, current_map_, current_map_lock_,
|
current_world_, current_map_, current_map_lock_,
|
||||||
show_map_properties_panel_, show_custom_bg_color_editor_,
|
show_map_properties_panel_, show_custom_bg_color_editor_,
|
||||||
@@ -1434,6 +1464,7 @@ absl::Status OverworldEditor::Save() {
|
|||||||
}
|
}
|
||||||
if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) {
|
if (core::FeatureFlags::get().overworld.kSaveOverworldProperties) {
|
||||||
RETURN_IF_ERROR(overworld_.SaveMapProperties());
|
RETURN_IF_ERROR(overworld_.SaveMapProperties());
|
||||||
|
RETURN_IF_ERROR(overworld_.SaveMusic());
|
||||||
}
|
}
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -559,8 +559,22 @@ void Canvas::DrawSelectRect(int current_map, int tile_size, float scale) {
|
|||||||
const float scaled_size = tile_size * scale;
|
const float scaled_size = tile_size * scale;
|
||||||
static bool dragging = false;
|
static bool dragging = false;
|
||||||
constexpr int small_map_size = 0x200;
|
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
|
// Handle right click for single tile selection
|
||||||
if (IsMouseClicked(ImGuiMouseButton_Right)) {
|
if (IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
@@ -758,6 +772,11 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
for (int y = 0; y < tiles_per_col + 1; ++y) {
|
for (int y = 0; y < tiles_per_col + 1; ++y) {
|
||||||
for (int x = 0; x < tiles_per_row + 1; ++x) {
|
for (int x = 0; x < tiles_per_row + 1; ++x) {
|
||||||
|
// Check bounds to prevent access violations
|
||||||
|
if (i >= static_cast<int>(group.size())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
int tile_id = group[i];
|
int tile_id = group[i];
|
||||||
|
|
||||||
// Check if tile_id is within the range of tile16_individual_
|
// Check if tile_id is within the range of tile16_individual_
|
||||||
@@ -770,10 +789,15 @@ void Canvas::DrawBitmapGroup(std::vector<int> &group, gfx::Tilemap &tilemap,
|
|||||||
|
|
||||||
// Draw the tile bitmap at the calculated position
|
// Draw the tile bitmap at the calculated position
|
||||||
gfx::RenderTile(tilemap, tile_id);
|
gfx::RenderTile(tilemap, tile_id);
|
||||||
DrawBitmap(tilemap.tile_bitmaps[tile_id], tile_pos_x, tile_pos_y, scale,
|
if (tilemap.tile_bitmaps.find(tile_id) != tilemap.tile_bitmaps.end()) {
|
||||||
150.0f);
|
DrawBitmap(tilemap.tile_bitmaps[tile_id], tile_pos_x, tile_pos_y, scale, 150);
|
||||||
i++;
|
}
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// Break outer loop if we've run out of tiles
|
||||||
|
if (i >= static_cast<int>(group.size())) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,8 +120,12 @@ absl::Status Overworld::AssembleMap32Tiles() {
|
|||||||
rom()->version_constants().kMap32TileTR,
|
rom()->version_constants().kMap32TileTR,
|
||||||
rom()->version_constants().kMap32TileBL,
|
rom()->version_constants().kMap32TileBL,
|
||||||
rom()->version_constants().kMap32TileBR};
|
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[0] = rom()->version_constants().kMap32TileTL;
|
||||||
map32address[1] = kMap32TileTRExpanded;
|
map32address[1] = kMap32TileTRExpanded;
|
||||||
map32address[2] = kMap32TileBLExpanded;
|
map32address[2] = kMap32TileBLExpanded;
|
||||||
@@ -169,8 +173,12 @@ absl::Status Overworld::AssembleMap32Tiles() {
|
|||||||
absl::Status Overworld::AssembleMap16Tiles() {
|
absl::Status Overworld::AssembleMap16Tiles() {
|
||||||
int tpos = kMap16Tiles;
|
int tpos = kMap16Tiles;
|
||||||
int num_tile16 = kNumTile16Individual;
|
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;
|
tpos = kMap16TilesExpanded;
|
||||||
num_tile16 = NumberOfMap16Ex;
|
num_tile16 = NumberOfMap16Ex;
|
||||||
expanded_tile16_ = true;
|
expanded_tile16_ = true;
|
||||||
@@ -313,12 +321,17 @@ absl::Status Overworld::LoadEntrances() {
|
|||||||
int ow_entrance_pos_ptr = kOverworldEntrancePos;
|
int ow_entrance_pos_ptr = kOverworldEntrancePos;
|
||||||
int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
|
int ow_entrance_id_ptr = kOverworldEntranceEntranceId;
|
||||||
int num_entrances = 129;
|
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_map_ptr = kOverworldEntranceMapExpanded;
|
||||||
ow_entrance_pos_ptr = kOverworldEntrancePosExpanded;
|
ow_entrance_pos_ptr = kOverworldEntrancePosExpanded;
|
||||||
ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded;
|
ow_entrance_id_ptr = kOverworldEntranceEntranceIdExpanded;
|
||||||
expanded_entrances_ = true;
|
expanded_entrances_ = true;
|
||||||
|
num_entrances = 256; // Expanded entrance count
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < num_entrances; i++) {
|
for (int i = 0; i < num_entrances; i++) {
|
||||||
@@ -416,16 +429,38 @@ absl::Status Overworld::LoadExits() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
absl::Status Overworld::LoadItems() {
|
absl::Status Overworld::LoadItems() {
|
||||||
ASSIGN_OR_RETURN(uint32_t pointer,
|
// byte asmVersion = ROM.DATA[Constants.OverworldCustomASMHasBeenApplied];
|
||||||
rom()->ReadLong(zelda3::kOverworldItemsAddress));
|
|
||||||
uint32_t pointer_pc = SnesToPc(pointer); // 1BC2F9 -> 0DC2F9
|
// // Version 0x03 of the OW ASM added item support for the SW.
|
||||||
for (int i = 0; i < 128; i++) {
|
// int maxOW = asmVersion >= 0x03 && asmVersion != 0xFF ? Constants.NumberOfOWMaps : 0x80;
|
||||||
ASSIGN_OR_RETURN(uint16_t word_address,
|
|
||||||
rom()->ReadWord(pointer_pc + i * 2));
|
// int pointerSNES = ROM.ReadLong(Constants.overworldItemsAddress);
|
||||||
uint32_t addr = (pointer & 0xFF0000) | word_address; // 1B F9 3C
|
// 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);
|
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) {
|
if (overworld_maps_[i].parent() != (uint8_t)i) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -442,13 +477,10 @@ absl::Status Overworld::LoadItems() {
|
|||||||
|
|
||||||
int p = (((b2 & 0x1F) << 8) + b1) >> 1;
|
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 y = p >> 6;
|
||||||
|
|
||||||
int fakeID = i;
|
int fakeID = i % 0x40; // Use modulo 0x40 to match ZS
|
||||||
if (fakeID >= 64) {
|
|
||||||
fakeID -= 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sy = fakeID / 8;
|
int sy = fakeID / 8;
|
||||||
int sx = fakeID - (sy * 8);
|
int sx = fakeID - (sy * 8);
|
||||||
@@ -467,15 +499,35 @@ absl::Status Overworld::LoadItems() {
|
|||||||
|
|
||||||
absl::Status Overworld::LoadSprites() {
|
absl::Status Overworld::LoadSprites() {
|
||||||
std::vector<std::future<absl::Status>> futures;
|
std::vector<std::future<absl::Status>> futures;
|
||||||
futures.emplace_back(std::async(std::launch::async, [this]() {
|
|
||||||
return LoadSpritesFromMap(kOverworldSpritesBeginning, 64, 0);
|
// Use ASM version to determine sprite table locations, with flag as override
|
||||||
}));
|
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||||
futures.emplace_back(std::async(std::launch::async, [this]() {
|
bool use_custom_overworld = (asm_version != 0xFF) ||
|
||||||
return LoadSpritesFromMap(kOverworldSpritesZelda, 144, 1);
|
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||||
}));
|
|
||||||
futures.emplace_back(std::async(std::launch::async, [this]() {
|
if (use_custom_overworld && asm_version >= 3 && asm_version != 0xFF) {
|
||||||
return LoadSpritesFromMap(kOverworldSpritesAgahnim, 144, 2);
|
// 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) {
|
for (auto &future : futures) {
|
||||||
future.wait();
|
future.wait();
|
||||||
@@ -532,6 +584,7 @@ absl::Status Overworld::Save(Rom *rom) {
|
|||||||
RETURN_IF_ERROR(SaveOverworldMaps())
|
RETURN_IF_ERROR(SaveOverworldMaps())
|
||||||
RETURN_IF_ERROR(SaveEntrances())
|
RETURN_IF_ERROR(SaveEntrances())
|
||||||
RETURN_IF_ERROR(SaveExits())
|
RETURN_IF_ERROR(SaveExits())
|
||||||
|
RETURN_IF_ERROR(SaveMusic())
|
||||||
RETURN_IF_ERROR(SaveAreaSizes())
|
RETURN_IF_ERROR(SaveAreaSizes())
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
@@ -1602,6 +1655,30 @@ absl::Status Overworld::SaveMapProperties() {
|
|||||||
return absl::OkStatus();
|
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() {
|
absl::Status Overworld::SaveAreaSizes() {
|
||||||
util::logf("Saving V3 Area Sizes");
|
util::logf("Saving V3 Area Sizes");
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,29 @@ constexpr int kMap32ExpandedFlagPos = 0x01772E; // 0x04
|
|||||||
constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F
|
constexpr int kMap16ExpandedFlagPos = 0x02FD28; // 0x0F
|
||||||
constexpr int kOverworldEntranceExpandedFlagPos = 0x0DB895; // 0xB8
|
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 kOverworldCompressedMapPos = 0x058000;
|
||||||
constexpr int kOverworldCompressedOverflowPos = 0x137FFF;
|
constexpr int kOverworldCompressedOverflowPos = 0x137FFF;
|
||||||
|
|
||||||
@@ -138,6 +161,7 @@ class Overworld {
|
|||||||
absl::Status SaveMap32Tiles();
|
absl::Status SaveMap32Tiles();
|
||||||
|
|
||||||
absl::Status SaveMapProperties();
|
absl::Status SaveMapProperties();
|
||||||
|
absl::Status SaveMusic();
|
||||||
absl::Status SaveAreaSizes();
|
absl::Status SaveAreaSizes();
|
||||||
|
|
||||||
auto rom() const { return rom_; }
|
auto rom() const { return rom_; }
|
||||||
@@ -212,6 +236,7 @@ class Overworld {
|
|||||||
}
|
}
|
||||||
auto is_loaded() const { return is_loaded_; }
|
auto is_loaded() const { return is_loaded_; }
|
||||||
void set_current_map(int i) { current_map_ = i; }
|
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 map_tiles() const { return map_tiles_; }
|
||||||
auto mutable_map_tiles() { return &map_tiles_; }
|
auto mutable_map_tiles() { return &map_tiles_; }
|
||||||
auto all_items() const { return all_items_; }
|
auto all_items() const { return all_items_; }
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ constexpr int kOverworldItemsAddress = 0xDC8B9; // 1BC2F9
|
|||||||
constexpr int kOverworldItemsBank = 0xDC8BF;
|
constexpr int kOverworldItemsBank = 0xDC8BF;
|
||||||
constexpr int kOverworldItemsEndData = 0xDC89C; // 0DC89E
|
constexpr int kOverworldItemsEndData = 0xDC89C; // 0DC89E
|
||||||
|
|
||||||
|
constexpr int kOverworldBombDoorItemLocationsNew = 0x012644;
|
||||||
|
constexpr int kOverworldItemsPointersNew = 0x012784;
|
||||||
|
constexpr int kOverworldItemsStartDataNew = 0x0DC2F9;
|
||||||
|
|
||||||
class OverworldItem : public GameEntity {
|
class OverworldItem : public GameEntity {
|
||||||
public:
|
public:
|
||||||
OverworldItem() = default;
|
OverworldItem() = default;
|
||||||
|
|||||||
@@ -18,16 +18,21 @@ OverworldMap::OverworldMap(int index, Rom *rom)
|
|||||||
: index_(index), parent_(index), rom_(rom) {
|
: index_(index), parent_(index), rom_(rom) {
|
||||||
LoadAreaInfo();
|
LoadAreaInfo();
|
||||||
|
|
||||||
if (core::FeatureFlags::get().overworld.kLoadCustomOverworld) {
|
// Use ASM version byte as source of truth, with flag as override
|
||||||
// If the custom overworld ASM has NOT already been applied, manually set
|
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
|
||||||
// the vanilla values.
|
bool use_custom_overworld = (asm_version != 0xFF) ||
|
||||||
uint8_t asm_version = (*rom_)[OverworldCustomASMHasBeenApplied];
|
core::FeatureFlags::get().overworld.kLoadCustomOverworld;
|
||||||
if (asm_version == 0x00) {
|
|
||||||
|
if (use_custom_overworld) {
|
||||||
|
if (asm_version == 0x00 || asm_version == 0xFF) {
|
||||||
|
// No custom ASM applied but flag enabled - set up vanilla values manually
|
||||||
LoadCustomOverworldData();
|
LoadCustomOverworldData();
|
||||||
} else {
|
} else {
|
||||||
|
// Custom overworld ASM applied - set up custom tileset
|
||||||
SetupCustomTileset(asm_version);
|
SetupCustomTileset(asm_version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// For vanilla ROMs without flag, LoadAreaInfo already handles everything
|
||||||
}
|
}
|
||||||
|
|
||||||
absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
|
absl::Status OverworldMap::BuildMap(int count, int game_state, int world,
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ constexpr int kOverworldPalettesScreenToSetNew = 0x4C635;
|
|||||||
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp = 0x0166E1;
|
constexpr int kOverworldSpecialSpriteGfxGroupExpandedTemp = 0x0166E1;
|
||||||
constexpr int kOverworldSpecialSpritePaletteExpandedTemp = 0x016701;
|
constexpr int kOverworldSpecialSpritePaletteExpandedTemp = 0x016701;
|
||||||
|
|
||||||
|
constexpr int transition_target_northExpanded = 0x1411B8;
|
||||||
|
constexpr int transition_target_westExpanded = 0x1412F8;
|
||||||
|
|
||||||
constexpr int kDarkWorldMapIdStart = 0x40;
|
constexpr int kDarkWorldMapIdStart = 0x40;
|
||||||
constexpr int kSpecialWorldMapIdStart = 0x80;
|
constexpr int kSpecialWorldMapIdStart = 0x80;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user