refactor: Enhance Map Properties System with Graphics Management and Refresh Logic
- Introduced OverworldGraphicsManager to handle graphics loading, refreshing, and texture management for the overworld editor. - Updated MapPropertiesSystem to integrate new graphics management features, ensuring proper refresh order for map properties and graphics. - Added methods for forcing graphics refresh and handling sibling map graphics, improving the responsiveness of the editor. - Enhanced UI elements in the MapPropertiesSystem for better user experience, including tooltips and organized layouts. - Updated CMake configuration to include new graphics manager files, ensuring proper integration into the build system.
This commit is contained in:
@@ -36,6 +36,7 @@ set(
|
|||||||
app/editor/graphics/gfx_group_editor.cc
|
app/editor/graphics/gfx_group_editor.cc
|
||||||
app/editor/overworld/entity.cc
|
app/editor/overworld/entity.cc
|
||||||
app/editor/overworld/overworld_entity_renderer.cc
|
app/editor/overworld/overworld_entity_renderer.cc
|
||||||
|
app/editor/overworld/overworld_graphics_manager.cc
|
||||||
app/editor/system/settings_editor.cc
|
app/editor/system/settings_editor.cc
|
||||||
app/editor/system/command_manager.cc
|
app/editor/system/command_manager.cc
|
||||||
app/editor/system/extension_manager.cc
|
app/editor/system/extension_manager.cc
|
||||||
|
|||||||
@@ -460,7 +460,22 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
|
|||||||
overworld_->mutable_overworld_map(current_map)
|
overworld_->mutable_overworld_map(current_map)
|
||||||
->mutable_area_graphics(),
|
->mutable_area_graphics(),
|
||||||
kHexByteInputWidth)) {
|
kHexByteInputWidth)) {
|
||||||
|
// CORRECT ORDER: Properties first, then graphics reload
|
||||||
|
|
||||||
|
// 1. Propagate properties to siblings FIRST (calls LoadAreaGraphics on siblings)
|
||||||
RefreshMapProperties();
|
RefreshMapProperties();
|
||||||
|
|
||||||
|
// 2. Force immediate refresh of current map
|
||||||
|
(*maps_bmp_)[current_map].set_modified(true);
|
||||||
|
overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
|
||||||
|
|
||||||
|
// 3. Refresh siblings immediately
|
||||||
|
RefreshSiblingMapGraphics(current_map);
|
||||||
|
|
||||||
|
// 4. Update tile selector
|
||||||
|
RefreshTile16Blockset();
|
||||||
|
|
||||||
|
// 5. Final refresh
|
||||||
RefreshOverworldMap();
|
RefreshOverworldMap();
|
||||||
}
|
}
|
||||||
HOVER_HINT("Main tileset graphics for this map area");
|
HOVER_HINT("Main tileset graphics for this map area");
|
||||||
@@ -472,6 +487,7 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
|
|||||||
overworld_->mutable_overworld_map(current_map)
|
overworld_->mutable_overworld_map(current_map)
|
||||||
->mutable_sprite_graphics(game_state),
|
->mutable_sprite_graphics(game_state),
|
||||||
kHexByteInputWidth)) {
|
kHexByteInputWidth)) {
|
||||||
|
ForceRefreshGraphics(current_map);
|
||||||
RefreshMapProperties();
|
RefreshMapProperties();
|
||||||
RefreshOverworldMap();
|
RefreshOverworldMap();
|
||||||
}
|
}
|
||||||
@@ -484,34 +500,48 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
|
|||||||
overworld_->mutable_overworld_map(current_map)
|
overworld_->mutable_overworld_map(current_map)
|
||||||
->mutable_animated_gfx(),
|
->mutable_animated_gfx(),
|
||||||
kHexByteInputWidth)) {
|
kHexByteInputWidth)) {
|
||||||
|
ForceRefreshGraphics(current_map);
|
||||||
RefreshMapProperties();
|
RefreshMapProperties();
|
||||||
|
RefreshTile16Blockset();
|
||||||
RefreshOverworldMap();
|
RefreshOverworldMap();
|
||||||
}
|
}
|
||||||
HOVER_HINT("Animated tile graphics (water, lava, etc.)");
|
HOVER_HINT("Animated tile graphics (water, lava, etc.)");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
// Custom Tile Graphics - Only available for v1+ ROMs
|
||||||
ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics");
|
if (asm_version >= 1 && asm_version != 0xFF) {
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics");
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
// Show the 8 custom graphics IDs in a 2-column layout for density
|
// Show the 8 custom graphics IDs in a 2-column layout for density
|
||||||
if (BeginTable("CustomTileGraphics", 2,
|
if (BeginTable("CustomTileGraphics", 2,
|
||||||
ImGuiTableFlags_SizingFixedFit)) {
|
ImGuiTableFlags_SizingFixedFit)) {
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
TableNextColumn();
|
TableNextColumn();
|
||||||
std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i);
|
std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i);
|
||||||
if (gui::InputHexByte(label.c_str(),
|
if (gui::InputHexByte(label.c_str(),
|
||||||
overworld_->mutable_overworld_map(current_map)
|
overworld_->mutable_overworld_map(current_map)
|
||||||
->mutable_custom_tileset(i),
|
->mutable_custom_tileset(i),
|
||||||
90.f)) {
|
90.f)) {
|
||||||
RefreshMapProperties();
|
ForceRefreshGraphics(current_map);
|
||||||
RefreshOverworldMap();
|
RefreshMapProperties();
|
||||||
}
|
RefreshTile16Blockset();
|
||||||
if (ImGui::IsItemHovered()) {
|
RefreshOverworldMap();
|
||||||
ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
ImGui::EndTable();
|
} else if (asm_version == 0xFF) {
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
|
||||||
|
ICON_MD_INFO " Custom Tile Graphics");
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"Custom tile graphics require ZSCustomOverworld v1+.\n"
|
||||||
|
"Upgrade your ROM to access 8 customizable graphics sheets.");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
|
ImGui::PopStyleVar(2); // Pop the 2 style variables we pushed
|
||||||
@@ -693,7 +723,12 @@ void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) {
|
|||||||
overworld_->mutable_overworld_map(current_map)
|
overworld_->mutable_overworld_map(current_map)
|
||||||
->mutable_area_graphics(),
|
->mutable_area_graphics(),
|
||||||
kInputFieldSize)) {
|
kInputFieldSize)) {
|
||||||
|
// CORRECT ORDER: Properties first, then graphics reload
|
||||||
RefreshMapProperties();
|
RefreshMapProperties();
|
||||||
|
(*maps_bmp_)[current_map].set_modified(true);
|
||||||
|
overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
|
||||||
|
RefreshSiblingMapGraphics(current_map);
|
||||||
|
RefreshTile16Blockset();
|
||||||
RefreshOverworldMap();
|
RefreshOverworldMap();
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
@@ -955,36 +990,55 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) {
|
void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) {
|
||||||
ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics (8 sheets)");
|
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||||
Separator();
|
|
||||||
|
|
||||||
if (BeginTable("TileGraphics", 2,
|
|
||||||
ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
|
|
||||||
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
|
|
||||||
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
TableNextColumn();
|
|
||||||
ImGui::Text(ICON_MD_LAYERS " Sheet %d", i);
|
|
||||||
TableNextColumn();
|
|
||||||
if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(),
|
|
||||||
overworld_->mutable_overworld_map(current_map)
|
|
||||||
->mutable_custom_tileset(i),
|
|
||||||
kInputFieldSize)) {
|
|
||||||
RefreshMapProperties();
|
|
||||||
RefreshOverworldMap();
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
Separator();
|
// Only show custom tile graphics for v1+ ROMs
|
||||||
ImGui::TextWrapped("These 8 sheets allow custom tile graphics per map. "
|
if (asm_version >= 1 && asm_version != 0xFF) {
|
||||||
"Each sheet references a graphics ID loaded into VRAM.");
|
ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics (8 sheets)");
|
||||||
|
Separator();
|
||||||
|
|
||||||
|
if (BeginTable("TileGraphics", 2,
|
||||||
|
ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
|
||||||
|
ImGui::TableSetupColumn("Property", ImGuiTableColumnFlags_WidthFixed, 180);
|
||||||
|
ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
TableNextColumn();
|
||||||
|
ImGui::Text(ICON_MD_LAYERS " Sheet %d", i);
|
||||||
|
TableNextColumn();
|
||||||
|
if (gui::InputHexByte(absl::StrFormat("##TileGfx%d", i).c_str(),
|
||||||
|
overworld_->mutable_overworld_map(current_map)
|
||||||
|
->mutable_custom_tileset(i),
|
||||||
|
kInputFieldSize)) {
|
||||||
|
overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
|
||||||
|
ForceRefreshGraphics(current_map);
|
||||||
|
RefreshSiblingMapGraphics(current_map);
|
||||||
|
RefreshMapProperties();
|
||||||
|
RefreshTile16Blockset();
|
||||||
|
RefreshOverworldMap();
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
Separator();
|
||||||
|
ImGui::TextWrapped("These 8 sheets allow custom tile graphics per map. "
|
||||||
|
"Each sheet references a graphics ID loaded into VRAM.");
|
||||||
|
} else {
|
||||||
|
// Vanilla ROM - show info message
|
||||||
|
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
|
||||||
|
ICON_MD_INFO " Custom Tile Graphics");
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::TextWrapped(
|
||||||
|
"Custom tile graphics are not available in vanilla ROMs.\n\n"
|
||||||
|
"To enable this feature, upgrade your ROM to ZSCustomOverworld v1+, "
|
||||||
|
"which provides 8 customizable graphics sheets per map for advanced "
|
||||||
|
"tileset customization.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MapPropertiesSystem::DrawMusicTab(int current_map) {
|
void MapPropertiesSystem::DrawMusicTab(int current_map) {
|
||||||
@@ -1090,6 +1144,70 @@ absl::Status MapPropertiesSystem::RefreshMapPalette() {
|
|||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
absl::Status MapPropertiesSystem::RefreshTile16Blockset() {
|
||||||
|
if (refresh_tile16_blockset_) {
|
||||||
|
return refresh_tile16_blockset_();
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapPropertiesSystem::ForceRefreshGraphics(int map_index) {
|
||||||
|
if (force_refresh_graphics_) {
|
||||||
|
force_refresh_graphics_(map_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapPropertiesSystem::RefreshSiblingMapGraphics(int map_index, bool include_self) {
|
||||||
|
if (!overworld_ || !maps_bmp_ || map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* map = overworld_->mutable_overworld_map(map_index);
|
||||||
|
if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) {
|
||||||
|
return; // No siblings for small areas
|
||||||
|
}
|
||||||
|
|
||||||
|
int parent_id = map->parent();
|
||||||
|
std::vector<int> siblings;
|
||||||
|
|
||||||
|
switch (map->area_size()) {
|
||||||
|
case zelda3::AreaSizeEnum::LargeArea:
|
||||||
|
siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
|
||||||
|
break;
|
||||||
|
case zelda3::AreaSizeEnum::WideArea:
|
||||||
|
siblings = {parent_id, parent_id + 1};
|
||||||
|
break;
|
||||||
|
case zelda3::AreaSizeEnum::TallArea:
|
||||||
|
siblings = {parent_id, parent_id + 8};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int sibling : siblings) {
|
||||||
|
if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
|
||||||
|
// Skip self unless include_self is true
|
||||||
|
if (sibling == map_index && !include_self) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as modified FIRST
|
||||||
|
(*maps_bmp_)[sibling].set_modified(true);
|
||||||
|
|
||||||
|
// Load graphics from ROM
|
||||||
|
overworld_->mutable_overworld_map(sibling)->LoadAreaGraphics();
|
||||||
|
|
||||||
|
// CRITICAL FIX: Force immediate refresh on the sibling
|
||||||
|
// This will trigger the callback to OverworldEditor's RefreshChildMapOnDemand
|
||||||
|
ForceRefreshGraphics(sibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After marking all siblings, trigger a refresh
|
||||||
|
// This ensures all marked maps get processed
|
||||||
|
RefreshOverworldMap();
|
||||||
|
}
|
||||||
|
|
||||||
void MapPropertiesSystem::DrawMosaicControls(int current_map) {
|
void MapPropertiesSystem::DrawMosaicControls(int current_map) {
|
||||||
static uint8_t asm_version =
|
static uint8_t asm_version =
|
||||||
(*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
(*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class MapPropertiesSystem {
|
|||||||
// Callback types for refresh operations
|
// Callback types for refresh operations
|
||||||
using RefreshCallback = std::function<void()>;
|
using RefreshCallback = std::function<void()>;
|
||||||
using RefreshPaletteCallback = std::function<absl::Status()>;
|
using RefreshPaletteCallback = std::function<absl::Status()>;
|
||||||
|
using ForceRefreshGraphicsCallback = std::function<void(int)>;
|
||||||
|
|
||||||
explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom,
|
explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom,
|
||||||
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp = nullptr,
|
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp = nullptr,
|
||||||
@@ -31,10 +32,14 @@ class MapPropertiesSystem {
|
|||||||
// Set callbacks for refresh operations
|
// Set callbacks for refresh operations
|
||||||
void SetRefreshCallbacks(RefreshCallback refresh_map_properties,
|
void SetRefreshCallbacks(RefreshCallback refresh_map_properties,
|
||||||
RefreshCallback refresh_overworld_map,
|
RefreshCallback refresh_overworld_map,
|
||||||
RefreshPaletteCallback refresh_map_palette) {
|
RefreshPaletteCallback refresh_map_palette,
|
||||||
|
RefreshPaletteCallback refresh_tile16_blockset = nullptr,
|
||||||
|
ForceRefreshGraphicsCallback force_refresh_graphics = nullptr) {
|
||||||
refresh_map_properties_ = std::move(refresh_map_properties);
|
refresh_map_properties_ = std::move(refresh_map_properties);
|
||||||
refresh_overworld_map_ = std::move(refresh_overworld_map);
|
refresh_overworld_map_ = std::move(refresh_overworld_map);
|
||||||
refresh_map_palette_ = std::move(refresh_map_palette);
|
refresh_map_palette_ = std::move(refresh_map_palette);
|
||||||
|
refresh_tile16_blockset_ = std::move(refresh_tile16_blockset);
|
||||||
|
force_refresh_graphics_ = std::move(force_refresh_graphics);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main interface methods
|
// Main interface methods
|
||||||
@@ -85,6 +90,11 @@ class MapPropertiesSystem {
|
|||||||
void RefreshMapProperties();
|
void RefreshMapProperties();
|
||||||
void RefreshOverworldMap();
|
void RefreshOverworldMap();
|
||||||
absl::Status RefreshMapPalette();
|
absl::Status RefreshMapPalette();
|
||||||
|
absl::Status RefreshTile16Blockset();
|
||||||
|
void ForceRefreshGraphics(int map_index);
|
||||||
|
|
||||||
|
// Helper to refresh sibling map graphics for multi-area maps
|
||||||
|
void RefreshSiblingMapGraphics(int map_index, bool include_self = false);
|
||||||
|
|
||||||
zelda3::Overworld* overworld_;
|
zelda3::Overworld* overworld_;
|
||||||
Rom* rom_;
|
Rom* rom_;
|
||||||
@@ -95,6 +105,8 @@ class MapPropertiesSystem {
|
|||||||
RefreshCallback refresh_map_properties_;
|
RefreshCallback refresh_map_properties_;
|
||||||
RefreshCallback refresh_overworld_map_;
|
RefreshCallback refresh_overworld_map_;
|
||||||
RefreshPaletteCallback refresh_map_palette_;
|
RefreshPaletteCallback refresh_map_palette_;
|
||||||
|
RefreshPaletteCallback refresh_tile16_blockset_;
|
||||||
|
ForceRefreshGraphicsCallback force_refresh_graphics_;
|
||||||
|
|
||||||
// Using centralized UI constants from ui_constants.h
|
// Using centralized UI constants from ui_constants.h
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ void OverworldEditor::Initialize() {
|
|||||||
map_properties_system_->SetRefreshCallbacks(
|
map_properties_system_->SetRefreshCallbacks(
|
||||||
[this]() { this->RefreshMapProperties(); },
|
[this]() { this->RefreshMapProperties(); },
|
||||||
[this]() { this->RefreshOverworldMap(); },
|
[this]() { this->RefreshOverworldMap(); },
|
||||||
[this]() -> absl::Status { return this->RefreshMapPalette(); }
|
[this]() -> absl::Status { return this->RefreshMapPalette(); },
|
||||||
|
[this]() -> absl::Status { return this->RefreshTile16Blockset(); },
|
||||||
|
[this](int map_index) { this->ForceRefreshGraphics(map_index); }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialize OverworldEditorManager for v3 features
|
// Initialize OverworldEditorManager for v3 features
|
||||||
@@ -343,18 +345,30 @@ void OverworldEditor::DrawToolset() {
|
|||||||
toolbar.AddSeparator();
|
toolbar.AddSeparator();
|
||||||
|
|
||||||
// Inline map properties with icon labels - use toolbar methods for consistency
|
// Inline map properties with icon labels - use toolbar methods for consistency
|
||||||
if (toolbar.AddProperty(ICON_MD_IMAGE, "##gfx",
|
if (toolbar.AddProperty(ICON_MD_IMAGE, " Gfx",
|
||||||
overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(),
|
overworld_.mutable_overworld_map(current_map_)->mutable_area_graphics(),
|
||||||
[this]() {
|
[this]() {
|
||||||
|
// CORRECT ORDER: Properties first, then graphics reload
|
||||||
|
|
||||||
|
// 1. Propagate properties to siblings FIRST (this also calls LoadAreaGraphics on siblings)
|
||||||
RefreshMapProperties();
|
RefreshMapProperties();
|
||||||
RefreshOverworldMap();
|
|
||||||
|
// 2. Force immediate refresh of current map and all siblings
|
||||||
|
maps_bmp_[current_map_].set_modified(true);
|
||||||
|
RefreshChildMapOnDemand(current_map_);
|
||||||
|
RefreshSiblingMapGraphics(current_map_);
|
||||||
|
|
||||||
|
// 3. Update tile selector
|
||||||
|
RefreshTile16Blockset();
|
||||||
})) {
|
})) {
|
||||||
// Property changed
|
// Property changed
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolbar.AddProperty(ICON_MD_PALETTE, "##pal",
|
if (toolbar.AddProperty(ICON_MD_PALETTE, " Pal",
|
||||||
overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(),
|
overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(),
|
||||||
[this]() {
|
[this]() {
|
||||||
|
// Palette changes also need to propagate to siblings
|
||||||
|
RefreshSiblingMapGraphics(current_map_);
|
||||||
RefreshMapProperties();
|
RefreshMapProperties();
|
||||||
status_ = RefreshMapPalette();
|
status_ = RefreshMapPalette();
|
||||||
RefreshOverworldMap();
|
RefreshOverworldMap();
|
||||||
@@ -406,13 +420,6 @@ void OverworldEditor::DrawToolset() {
|
|||||||
show_gfx_groups_ = !show_gfx_groups_;
|
show_gfx_groups_ = !show_gfx_groups_;
|
||||||
}
|
}
|
||||||
|
|
||||||
toolbar.AddSeparator();
|
|
||||||
|
|
||||||
// v3 Settings and Usage Statistics
|
|
||||||
toolbar.AddV3StatusBadge(asm_version, [this]() {
|
|
||||||
show_v3_settings_ = !show_v3_settings_;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (toolbar.AddUsageStatsButton("Open Usage Statistics")) {
|
if (toolbar.AddUsageStatsButton("Open Usage Statistics")) {
|
||||||
show_usage_stats_ = !show_usage_stats_;
|
show_usage_stats_ = !show_usage_stats_;
|
||||||
}
|
}
|
||||||
@@ -1639,13 +1646,11 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
|
|||||||
map_index);
|
map_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update texture on main thread
|
// CRITICAL FIX: Force COMPLETE texture recreation for immediate visibility
|
||||||
if (maps_bmp_[map_index].texture()) {
|
// UpdateBitmap() was still deferred - we need to force a full re-render
|
||||||
Renderer::Get().UpdateBitmap(&maps_bmp_[map_index]);
|
|
||||||
} else {
|
// Always recreate the texture to ensure immediate GPU update
|
||||||
// Create texture if it doesn't exist
|
Renderer::Get().RenderBitmap(&maps_bmp_[map_index]);
|
||||||
EnsureMapTexture(map_index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle multi-area maps (large, wide, tall) with safe coordination
|
// Handle multi-area maps (large, wide, tall) with safe coordination
|
||||||
@@ -1888,6 +1893,67 @@ absl::Status OverworldEditor::RefreshMapPalette() {
|
|||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OverworldEditor::ForceRefreshGraphics(int map_index) {
|
||||||
|
// Mark the bitmap as modified to force refresh on next update
|
||||||
|
if (map_index >= 0 && map_index < static_cast<int>(maps_bmp_.size())) {
|
||||||
|
maps_bmp_[map_index].set_modified(true);
|
||||||
|
|
||||||
|
// Clear blockset cache
|
||||||
|
current_blockset_ = 0xFF;
|
||||||
|
|
||||||
|
LOG_INFO("OverworldEditor", "ForceRefreshGraphics: Map %d marked for refresh", map_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldEditor::RefreshSiblingMapGraphics(int map_index, bool include_self) {
|
||||||
|
if (map_index < 0 || map_index >= static_cast<int>(maps_bmp_.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* map = overworld_.mutable_overworld_map(map_index);
|
||||||
|
if (map->area_size() == zelda3::AreaSizeEnum::SmallArea) {
|
||||||
|
return; // No siblings for small areas
|
||||||
|
}
|
||||||
|
|
||||||
|
int parent_id = map->parent();
|
||||||
|
std::vector<int> siblings;
|
||||||
|
|
||||||
|
switch (map->area_size()) {
|
||||||
|
case zelda3::AreaSizeEnum::LargeArea:
|
||||||
|
siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
|
||||||
|
break;
|
||||||
|
case zelda3::AreaSizeEnum::WideArea:
|
||||||
|
siblings = {parent_id, parent_id + 1};
|
||||||
|
break;
|
||||||
|
case zelda3::AreaSizeEnum::TallArea:
|
||||||
|
siblings = {parent_id, parent_id + 8};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int sibling : siblings) {
|
||||||
|
if (sibling >= 0 && sibling < 0xA0) {
|
||||||
|
// Skip self unless include_self is true
|
||||||
|
if (sibling == map_index && !include_self) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark as modified FIRST before loading
|
||||||
|
maps_bmp_[sibling].set_modified(true);
|
||||||
|
|
||||||
|
// Load graphics from ROM
|
||||||
|
overworld_.mutable_overworld_map(sibling)->LoadAreaGraphics();
|
||||||
|
|
||||||
|
// CRITICAL FIX: Bypass visibility check - force immediate refresh
|
||||||
|
// Call RefreshChildMapOnDemand() directly instead of RefreshOverworldMapOnDemand()
|
||||||
|
RefreshChildMapOnDemand(sibling);
|
||||||
|
|
||||||
|
LOG_INFO("OverworldEditor", "RefreshSiblingMapGraphics: Refreshed sibling map %d", sibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OverworldEditor::RefreshMapProperties() {
|
void OverworldEditor::RefreshMapProperties() {
|
||||||
const auto& current_ow_map = *overworld_.mutable_overworld_map(current_map_);
|
const auto& current_ow_map = *overworld_.mutable_overworld_map(current_map_);
|
||||||
|
|
||||||
@@ -1935,6 +2001,9 @@ void OverworldEditor::RefreshMapProperties() {
|
|||||||
map.set_sprite_palette(game_state_,
|
map.set_sprite_palette(game_state_,
|
||||||
current_ow_map.sprite_palette(game_state_));
|
current_ow_map.sprite_palette(game_state_));
|
||||||
map.set_message_id(current_ow_map.message_id());
|
map.set_message_id(current_ow_map.message_id());
|
||||||
|
|
||||||
|
// CRITICAL FIX: Reload graphics after changing properties
|
||||||
|
map.LoadAreaGraphics();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1954,6 +2023,9 @@ void OverworldEditor::RefreshMapProperties() {
|
|||||||
map.set_sprite_palette(game_state_,
|
map.set_sprite_palette(game_state_,
|
||||||
current_ow_map.sprite_palette(game_state_));
|
current_ow_map.sprite_palette(game_state_));
|
||||||
map.set_message_id(current_ow_map.message_id());
|
map.set_message_id(current_ow_map.message_id());
|
||||||
|
|
||||||
|
// CRITICAL FIX: Reload graphics after changing properties
|
||||||
|
map.LoadAreaGraphics();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,8 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
|
|||||||
absl::Status RefreshMapPalette();
|
absl::Status RefreshMapPalette();
|
||||||
void RefreshMapProperties();
|
void RefreshMapProperties();
|
||||||
absl::Status RefreshTile16Blockset();
|
absl::Status RefreshTile16Blockset();
|
||||||
|
void ForceRefreshGraphics(int map_index);
|
||||||
|
void RefreshSiblingMapGraphics(int map_index, bool include_self = false);
|
||||||
|
|
||||||
void DrawOverworldMaps();
|
void DrawOverworldMaps();
|
||||||
void DrawOverworldEdits();
|
void DrawOverworldEdits();
|
||||||
|
|||||||
714
src/app/editor/overworld/overworld_graphics_manager.cc
Normal file
714
src/app/editor/overworld/overworld_graphics_manager.cc
Normal file
@@ -0,0 +1,714 @@
|
|||||||
|
#include "overworld_graphics_manager.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "app/core/features.h"
|
||||||
|
#include "app/core/window.h"
|
||||||
|
#include "app/gfx/performance_profiler.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/macro.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using core::Renderer;
|
||||||
|
using zelda3::kNumOverworldMaps;
|
||||||
|
using zelda3::kOverworldMapSize;
|
||||||
|
|
||||||
|
constexpr int kTile16Size = 16;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Loading Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
absl::Status OverworldGraphicsManager::LoadGraphics() {
|
||||||
|
gfx::ScopedTimer timer("LoadGraphics");
|
||||||
|
|
||||||
|
LOG_INFO("OverworldGraphicsManager", "Loading overworld.");
|
||||||
|
// Load the Link to the Past overworld.
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer load_timer("Overworld::Load");
|
||||||
|
RETURN_IF_ERROR(overworld_->Load(rom_));
|
||||||
|
}
|
||||||
|
*palette_ = overworld_->current_area_palette();
|
||||||
|
|
||||||
|
LOG_INFO("OverworldGraphicsManager", "Loading overworld graphics (optimized).");
|
||||||
|
|
||||||
|
// Phase 1: Create bitmaps without textures for faster loading
|
||||||
|
// This avoids blocking the main thread with GPU texture creation
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics");
|
||||||
|
Renderer::Get().CreateBitmapWithoutTexture(0x80, kOverworldMapSize, 0x40,
|
||||||
|
overworld_->current_graphics(),
|
||||||
|
*current_gfx_bmp_, *palette_);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("OverworldGraphicsManager", "Loading overworld tileset (deferred textures).");
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
|
||||||
|
Renderer::Get().CreateBitmapWithoutTexture(
|
||||||
|
0x80, 0x2000, 0x08, overworld_->tile16_blockset_data(),
|
||||||
|
*tile16_blockset_bmp_, *palette_);
|
||||||
|
}
|
||||||
|
map_blockset_loaded_ = true;
|
||||||
|
|
||||||
|
// Copy the tile16 data into individual tiles.
|
||||||
|
auto tile16_blockset_data = overworld_->tile16_blockset_data();
|
||||||
|
LOG_INFO("OverworldGraphicsManager", "Loading overworld tile16 graphics.");
|
||||||
|
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer tilemap_timer("CreateTilemap");
|
||||||
|
*tile16_blockset_ =
|
||||||
|
gfx::CreateTilemap(tile16_blockset_data, 0x80, 0x2000, kTile16Size,
|
||||||
|
zelda3::kNumTile16Individual, *palette_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2: Create bitmaps only for essential maps initially
|
||||||
|
// Non-essential maps will be created on-demand when accessed
|
||||||
|
constexpr int kEssentialMapsPerWorld = 8;
|
||||||
|
constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
|
||||||
|
constexpr int kDarkWorldEssential =
|
||||||
|
zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld;
|
||||||
|
constexpr int kSpecialWorldEssential =
|
||||||
|
zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
|
||||||
|
|
||||||
|
LOG_INFO("OverworldGraphicsManager",
|
||||||
|
"Creating bitmaps for essential maps only (first %d maps per world)",
|
||||||
|
kEssentialMapsPerWorld);
|
||||||
|
|
||||||
|
std::vector<gfx::Bitmap*> maps_to_texture;
|
||||||
|
maps_to_texture.reserve(kEssentialMapsPerWorld *
|
||||||
|
3); // 8 maps per world * 3 worlds
|
||||||
|
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer maps_timer("CreateEssentialOverworldMaps");
|
||||||
|
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
|
||||||
|
bool is_essential = false;
|
||||||
|
|
||||||
|
// Check if this is an essential map
|
||||||
|
if (i < kLightWorldEssential) {
|
||||||
|
is_essential = true;
|
||||||
|
} else if (i >= zelda3::kDarkWorldMapIdStart && i < kDarkWorldEssential) {
|
||||||
|
is_essential = true;
|
||||||
|
} else if (i >= zelda3::kSpecialWorldMapIdStart &&
|
||||||
|
i < kSpecialWorldEssential) {
|
||||||
|
is_essential = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_essential) {
|
||||||
|
overworld_->set_current_map(i);
|
||||||
|
auto palette = overworld_->current_area_palette();
|
||||||
|
try {
|
||||||
|
// Create bitmap data and surface but defer texture creation
|
||||||
|
(*maps_bmp_)[i].Create(kOverworldMapSize, kOverworldMapSize, 0x80,
|
||||||
|
overworld_->current_map_bitmap_data());
|
||||||
|
(*maps_bmp_)[i].SetPalette(palette);
|
||||||
|
maps_to_texture.push_back(&(*maps_bmp_)[i]);
|
||||||
|
} catch (const std::bad_alloc& e) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager", "Error allocating map %d: %s",
|
||||||
|
i, e.what());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Non-essential maps will be created on-demand when accessed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 3: Create textures only for currently visible maps
|
||||||
|
// Only create textures for the first few maps initially
|
||||||
|
const int initial_texture_count =
|
||||||
|
std::min(4, static_cast<int>(maps_to_texture.size()));
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer initial_textures_timer("CreateInitialTextures");
|
||||||
|
for (int i = 0; i < initial_texture_count; ++i) {
|
||||||
|
Renderer::Get().RenderBitmap(maps_to_texture[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store remaining maps for lazy texture creation
|
||||||
|
deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count,
|
||||||
|
maps_to_texture.end());
|
||||||
|
|
||||||
|
if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
|
||||||
|
{
|
||||||
|
gfx::ScopedTimer sprites_timer("LoadSpriteGraphics");
|
||||||
|
RETURN_IF_ERROR(LoadSpriteGraphics());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
all_gfx_loaded_ = true;
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldGraphicsManager::LoadSpriteGraphics() {
|
||||||
|
// Render the sprites for each Overworld map
|
||||||
|
const int depth = 0x10;
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
for (auto const& sprite : *overworld_->mutable_sprites(i)) {
|
||||||
|
int width = sprite.width();
|
||||||
|
int height = sprite.height();
|
||||||
|
if (width == 0 || height == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sprite_previews_->size() < sprite.id()) {
|
||||||
|
sprite_previews_->resize(sprite.id() + 1);
|
||||||
|
}
|
||||||
|
(*sprite_previews_)[sprite.id()].Create(width, height, depth,
|
||||||
|
*sprite.preview_graphics());
|
||||||
|
(*sprite_previews_)[sprite.id()].SetPalette(*palette_);
|
||||||
|
Renderer::Get().RenderBitmap(&(*sprite_previews_)[sprite.id()]);
|
||||||
|
}
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Texture Management
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::ProcessDeferredTextures() {
|
||||||
|
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
|
||||||
|
|
||||||
|
// Always process deferred textures progressively, even if the list is "empty"
|
||||||
|
// This allows for continuous background loading
|
||||||
|
|
||||||
|
// PHASE 1: Priority loading for current world
|
||||||
|
const int high_priority_per_frame = 4; // Current world maps
|
||||||
|
const int low_priority_per_frame = 2; // Other world maps
|
||||||
|
const int refresh_per_frame = 2; // Modified map refreshes
|
||||||
|
int processed = 0;
|
||||||
|
|
||||||
|
// Process high-priority deferred textures (current world)
|
||||||
|
if (!deferred_map_textures_.empty()) {
|
||||||
|
auto it = deferred_map_textures_.begin();
|
||||||
|
while (it != deferred_map_textures_.end() && processed < high_priority_per_frame) {
|
||||||
|
if (*it && !(*it)->texture()) {
|
||||||
|
// Find map index for priority check
|
||||||
|
int map_index = -1;
|
||||||
|
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
|
||||||
|
if (&(*maps_bmp_)[i] == *it) {
|
||||||
|
map_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_current_world = false;
|
||||||
|
if (map_index >= 0) {
|
||||||
|
int map_world = map_index / 0x40; // 64 maps per world
|
||||||
|
is_current_world = (map_world == current_world_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_current_world) {
|
||||||
|
Renderer::Get().RenderBitmap(*it);
|
||||||
|
processed++;
|
||||||
|
it = deferred_map_textures_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 2: Background loading for other worlds (lower priority)
|
||||||
|
if (!deferred_map_textures_.empty() && processed < high_priority_per_frame) {
|
||||||
|
auto it = deferred_map_textures_.begin();
|
||||||
|
int low_priority_processed = 0;
|
||||||
|
while (it != deferred_map_textures_.end() && low_priority_processed < low_priority_per_frame) {
|
||||||
|
if (*it && !(*it)->texture()) {
|
||||||
|
Renderer::Get().RenderBitmap(*it);
|
||||||
|
low_priority_processed++;
|
||||||
|
processed++;
|
||||||
|
it = deferred_map_textures_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 3: Process modified maps that need refresh (highest priority)
|
||||||
|
int refresh_processed = 0;
|
||||||
|
for (int i = 0; i < zelda3::kNumOverworldMaps && refresh_processed < refresh_per_frame; ++i) {
|
||||||
|
if ((*maps_bmp_)[i].modified() && (*maps_bmp_)[i].is_active()) {
|
||||||
|
// Check if this map is in current world (high priority) or visible
|
||||||
|
bool is_current_world = (i / 0x40 == current_world_);
|
||||||
|
bool is_current_map = (i == current_map_);
|
||||||
|
|
||||||
|
if (is_current_map || is_current_world) {
|
||||||
|
RefreshOverworldMapOnDemand(i);
|
||||||
|
refresh_processed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PHASE 4: Background refresh for modified maps in other worlds (very low priority)
|
||||||
|
if (refresh_processed == 0) {
|
||||||
|
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
|
||||||
|
if ((*maps_bmp_)[i].modified() && (*maps_bmp_)[i].is_active()) {
|
||||||
|
bool is_current_world = (i / 0x40 == current_world_);
|
||||||
|
if (!is_current_world) {
|
||||||
|
// Just mark for later, don't refresh now to avoid lag
|
||||||
|
// These will be refreshed when the world is switched
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::EnsureMapTexture(int map_index) {
|
||||||
|
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the map is built first (on-demand loading)
|
||||||
|
auto status = overworld_->EnsureMapBuilt(map_index);
|
||||||
|
if (!status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager", "Failed to build map %d: %s", map_index,
|
||||||
|
status.message().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& bitmap = (*maps_bmp_)[map_index];
|
||||||
|
|
||||||
|
// If bitmap doesn't exist yet (non-essential map), create it now
|
||||||
|
if (!bitmap.is_active()) {
|
||||||
|
overworld_->set_current_map(map_index);
|
||||||
|
auto palette = overworld_->current_area_palette();
|
||||||
|
try {
|
||||||
|
bitmap.Create(kOverworldMapSize, kOverworldMapSize, 0x80,
|
||||||
|
overworld_->current_map_bitmap_data());
|
||||||
|
bitmap.SetPalette(palette);
|
||||||
|
} catch (const std::bad_alloc& e) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager", "Error allocating bitmap for map %d: %s",
|
||||||
|
map_index, e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bitmap.texture() && bitmap.is_active()) {
|
||||||
|
Renderer::Get().RenderBitmap(&bitmap);
|
||||||
|
|
||||||
|
// Remove from deferred list if it was there
|
||||||
|
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
|
||||||
|
auto it = std::find(deferred_map_textures_.begin(),
|
||||||
|
deferred_map_textures_.end(), &bitmap);
|
||||||
|
if (it != deferred_map_textures_.end()) {
|
||||||
|
deferred_map_textures_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Refresh Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::RefreshOverworldMap() {
|
||||||
|
// Use the new on-demand refresh system
|
||||||
|
RefreshOverworldMapOnDemand(current_map_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::RefreshOverworldMapOnDemand(int map_index) {
|
||||||
|
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the map is actually visible or being edited
|
||||||
|
bool is_current_map = (map_index == current_map_);
|
||||||
|
bool is_current_world = (map_index / 0x40 == current_world_);
|
||||||
|
|
||||||
|
// For non-current maps in non-current worlds, defer the refresh
|
||||||
|
if (!is_current_map && !is_current_world) {
|
||||||
|
// Mark for deferred refresh - will be processed when the map becomes visible
|
||||||
|
(*maps_bmp_)[map_index].set_modified(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For visible maps, do immediate refresh
|
||||||
|
RefreshChildMapOnDemand(map_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::RefreshChildMap(int map_index) {
|
||||||
|
overworld_->mutable_overworld_map(map_index)->LoadAreaGraphics();
|
||||||
|
auto status = overworld_->mutable_overworld_map(map_index)->BuildTileset();
|
||||||
|
PRINT_IF_ERROR(status);
|
||||||
|
status = overworld_->mutable_overworld_map(map_index)->BuildTiles16Gfx(
|
||||||
|
*overworld_->mutable_tiles16(), overworld_->tiles16().size());
|
||||||
|
PRINT_IF_ERROR(status);
|
||||||
|
status = overworld_->mutable_overworld_map(map_index)->BuildBitmap(
|
||||||
|
overworld_->GetMapTiles(current_world_));
|
||||||
|
(*maps_bmp_)[map_index].set_data(
|
||||||
|
overworld_->mutable_overworld_map(map_index)->bitmap_data());
|
||||||
|
(*maps_bmp_)[map_index].set_modified(true);
|
||||||
|
PRINT_IF_ERROR(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::RefreshChildMapOnDemand(int map_index) {
|
||||||
|
auto* map = overworld_->mutable_overworld_map(map_index);
|
||||||
|
|
||||||
|
// Check what actually needs to be refreshed
|
||||||
|
bool needs_graphics_rebuild = (*maps_bmp_)[map_index].modified();
|
||||||
|
|
||||||
|
if (needs_graphics_rebuild) {
|
||||||
|
// Only rebuild what's actually changed
|
||||||
|
map->LoadAreaGraphics();
|
||||||
|
|
||||||
|
// Rebuild tileset only if graphics changed
|
||||||
|
auto status = map->BuildTileset();
|
||||||
|
if (!status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager", "Failed to build tileset for map %d: %s",
|
||||||
|
map_index, status.message().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild tiles16 graphics
|
||||||
|
status = map->BuildTiles16Gfx(*overworld_->mutable_tiles16(),
|
||||||
|
overworld_->tiles16().size());
|
||||||
|
if (!status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager", "Failed to build tiles16 graphics for map %d: %s",
|
||||||
|
map_index, status.message().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild bitmap
|
||||||
|
status = map->BuildBitmap(overworld_->GetMapTiles(current_world_));
|
||||||
|
if (!status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager", "Failed to build bitmap for map %d: %s",
|
||||||
|
map_index, status.message().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update bitmap data
|
||||||
|
(*maps_bmp_)[map_index].set_data(map->bitmap_data());
|
||||||
|
(*maps_bmp_)[map_index].set_modified(false);
|
||||||
|
|
||||||
|
// Validate surface synchronization to help debug crashes
|
||||||
|
if (!(*maps_bmp_)[map_index].ValidateDataSurfaceSync()) {
|
||||||
|
LOG_WARN("OverworldGraphicsManager", "Warning: Surface synchronization issue detected for map %d",
|
||||||
|
map_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update texture on main thread
|
||||||
|
if ((*maps_bmp_)[map_index].texture()) {
|
||||||
|
Renderer::Get().UpdateBitmap(&(*maps_bmp_)[map_index]);
|
||||||
|
} else {
|
||||||
|
// Create texture if it doesn't exist
|
||||||
|
EnsureMapTexture(map_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle multi-area maps (large, wide, tall) with safe coordination
|
||||||
|
// Check if ZSCustomOverworld v3 is present
|
||||||
|
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||||
|
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
|
||||||
|
|
||||||
|
if (use_v3_area_sizes) {
|
||||||
|
// Use v3 multi-area coordination
|
||||||
|
RefreshMultiAreaMapsSafely(map_index, map);
|
||||||
|
} else {
|
||||||
|
// Legacy logic: only handle large maps for vanilla/v2
|
||||||
|
if (map->is_large_map()) {
|
||||||
|
RefreshMultiAreaMapsSafely(map_index, map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::RefreshMultiAreaMapsSafely(
|
||||||
|
int map_index, zelda3::OverworldMap* map) {
|
||||||
|
using zelda3::AreaSizeEnum;
|
||||||
|
|
||||||
|
// Skip if this is already a processed sibling to avoid double-processing
|
||||||
|
static std::set<int> currently_processing;
|
||||||
|
if (currently_processing.count(map_index)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto area_size = map->area_size();
|
||||||
|
if (area_size == AreaSizeEnum::SmallArea) {
|
||||||
|
return; // No siblings to coordinate
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)",
|
||||||
|
(area_size == AreaSizeEnum::LargeArea) ? "large"
|
||||||
|
: (area_size == AreaSizeEnum::WideArea) ? "wide"
|
||||||
|
: "tall",
|
||||||
|
map_index, map->parent());
|
||||||
|
|
||||||
|
// Determine all maps that are part of this multi-area structure
|
||||||
|
std::vector<int> sibling_maps;
|
||||||
|
int parent_id = map->parent();
|
||||||
|
|
||||||
|
// Use the same logic as ZScream for area coordination
|
||||||
|
switch (area_size) {
|
||||||
|
case AreaSizeEnum::LargeArea: {
|
||||||
|
// Large Area: 2x2 grid (4 maps total)
|
||||||
|
sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
|
||||||
|
LOG_DEBUG("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
|
||||||
|
parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AreaSizeEnum::WideArea: {
|
||||||
|
// Wide Area: 2x1 grid (2 maps total, horizontally adjacent)
|
||||||
|
sibling_maps = {parent_id, parent_id + 1};
|
||||||
|
LOG_DEBUG("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d",
|
||||||
|
parent_id, parent_id + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AreaSizeEnum::TallArea: {
|
||||||
|
// Tall Area: 1x2 grid (2 maps total, vertically adjacent)
|
||||||
|
sibling_maps = {parent_id, parent_id + 8};
|
||||||
|
LOG_DEBUG("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d",
|
||||||
|
parent_id, parent_id + 8);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG_WARN("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
|
||||||
|
static_cast<int>(area_size), map_index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all siblings as being processed to prevent recursion
|
||||||
|
for (int sibling : sibling_maps) {
|
||||||
|
currently_processing.insert(sibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only refresh siblings that are visible/current and need updating
|
||||||
|
for (int sibling : sibling_maps) {
|
||||||
|
if (sibling == map_index) {
|
||||||
|
continue; // Skip self (already processed above)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds check
|
||||||
|
if (sibling < 0 || sibling >= zelda3::kNumOverworldMaps) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only refresh if it's visible or current
|
||||||
|
bool is_current_map = (sibling == current_map_);
|
||||||
|
bool is_current_world = (sibling / 0x40 == current_world_);
|
||||||
|
bool needs_refresh = (*maps_bmp_)[sibling].modified();
|
||||||
|
|
||||||
|
if ((is_current_map || is_current_world) && needs_refresh) {
|
||||||
|
LOG_DEBUG("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d "
|
||||||
|
"(parent: %d)",
|
||||||
|
(area_size == AreaSizeEnum::LargeArea) ? "large"
|
||||||
|
: (area_size == AreaSizeEnum::WideArea) ? "wide"
|
||||||
|
: "tall",
|
||||||
|
sibling, parent_id);
|
||||||
|
|
||||||
|
// Direct refresh without calling RefreshChildMapOnDemand to avoid recursion
|
||||||
|
auto* sibling_map = overworld_->mutable_overworld_map(sibling);
|
||||||
|
if (sibling_map && (*maps_bmp_)[sibling].modified()) {
|
||||||
|
sibling_map->LoadAreaGraphics();
|
||||||
|
|
||||||
|
if (auto status = sibling_map->BuildTileset(); !status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: %s",
|
||||||
|
sibling, status.message().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = sibling_map->BuildTiles16Gfx(*overworld_->mutable_tiles16(),
|
||||||
|
overworld_->tiles16().size()); !status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Failed to build tiles16 graphics for sibling map %d: %s",
|
||||||
|
sibling, status.message().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = sibling_map->LoadPalette(); !status.ok()) {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Failed to load palette for sibling map %d: %s",
|
||||||
|
sibling, status.message().data());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto status = sibling_map->BuildBitmap(overworld_->GetMapTiles(current_world_)); status.ok()) {
|
||||||
|
(*maps_bmp_)[sibling].set_data(sibling_map->bitmap_data());
|
||||||
|
(*maps_bmp_)[sibling].SetPalette(overworld_->current_area_palette());
|
||||||
|
(*maps_bmp_)[sibling].set_modified(false);
|
||||||
|
|
||||||
|
// Update texture if it exists
|
||||||
|
if ((*maps_bmp_)[sibling].texture()) {
|
||||||
|
Renderer::Get().UpdateBitmap(&(*maps_bmp_)[sibling]);
|
||||||
|
} else {
|
||||||
|
EnsureMapTexture(sibling);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("OverworldGraphicsManager",
|
||||||
|
"RefreshMultiAreaMapsSafely: Failed to build bitmap for sibling map %d: %s",
|
||||||
|
sibling, status.message().data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!is_current_map && !is_current_world) {
|
||||||
|
// Mark non-visible siblings for deferred refresh
|
||||||
|
(*maps_bmp_)[sibling].set_modified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear processing set after completion
|
||||||
|
for (int sibling : sibling_maps) {
|
||||||
|
currently_processing.erase(sibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldGraphicsManager::RefreshMapPalette() {
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_->mutable_overworld_map(current_map_)->LoadPalette());
|
||||||
|
const auto current_map_palette = overworld_->current_area_palette();
|
||||||
|
|
||||||
|
// Check if ZSCustomOverworld v3 is present
|
||||||
|
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||||
|
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
|
||||||
|
|
||||||
|
if (use_v3_area_sizes) {
|
||||||
|
// Use v3 area size system
|
||||||
|
using zelda3::AreaSizeEnum;
|
||||||
|
auto area_size = overworld_->overworld_map(current_map_)->area_size();
|
||||||
|
|
||||||
|
if (area_size != AreaSizeEnum::SmallArea) {
|
||||||
|
// Get all sibling maps that need palette updates
|
||||||
|
std::vector<int> sibling_maps;
|
||||||
|
int parent_id = overworld_->overworld_map(current_map_)->parent();
|
||||||
|
|
||||||
|
switch (area_size) {
|
||||||
|
case AreaSizeEnum::LargeArea:
|
||||||
|
// 2x2 grid: parent, parent+1, parent+8, parent+9
|
||||||
|
sibling_maps = {parent_id, parent_id + 1, parent_id + 8,
|
||||||
|
parent_id + 9};
|
||||||
|
break;
|
||||||
|
case AreaSizeEnum::WideArea:
|
||||||
|
// 2x1 grid: parent, parent+1
|
||||||
|
sibling_maps = {parent_id, parent_id + 1};
|
||||||
|
break;
|
||||||
|
case AreaSizeEnum::TallArea:
|
||||||
|
// 1x2 grid: parent, parent+8
|
||||||
|
sibling_maps = {parent_id, parent_id + 8};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update palette for all siblings
|
||||||
|
for (int sibling_index : sibling_maps) {
|
||||||
|
if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_->mutable_overworld_map(sibling_index)->LoadPalette());
|
||||||
|
(*maps_bmp_)[sibling_index].SetPalette(current_map_palette);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Small area - only update current map
|
||||||
|
(*maps_bmp_)[current_map_].SetPalette(current_map_palette);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Legacy logic for vanilla and v2 ROMs
|
||||||
|
if (overworld_->overworld_map(current_map_)->is_large_map()) {
|
||||||
|
// We need to update the map and its siblings if it's a large map
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
int sibling_index =
|
||||||
|
overworld_->overworld_map(current_map_)->parent() + i;
|
||||||
|
if (i >= 2)
|
||||||
|
sibling_index += 6;
|
||||||
|
RETURN_IF_ERROR(
|
||||||
|
overworld_->mutable_overworld_map(sibling_index)->LoadPalette());
|
||||||
|
(*maps_bmp_)[sibling_index].SetPalette(current_map_palette);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(*maps_bmp_)[current_map_].SetPalette(current_map_palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
absl::Status OverworldGraphicsManager::RefreshTile16Blockset() {
|
||||||
|
LOG_DEBUG("OverworldGraphicsManager", "RefreshTile16Blockset called");
|
||||||
|
if (current_blockset_ ==
|
||||||
|
overworld_->overworld_map(current_map_)->area_graphics()) {
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
current_blockset_ = overworld_->overworld_map(current_map_)->area_graphics();
|
||||||
|
|
||||||
|
overworld_->set_current_map(current_map_);
|
||||||
|
*palette_ = overworld_->current_area_palette();
|
||||||
|
|
||||||
|
const auto tile16_data = overworld_->tile16_blockset_data();
|
||||||
|
|
||||||
|
gfx::UpdateTilemap(*tile16_blockset_, tile16_data);
|
||||||
|
tile16_blockset_->atlas.SetPalette(*palette_);
|
||||||
|
return absl::OkStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverworldGraphicsManager::ForceRefreshGraphics(int map_index) {
|
||||||
|
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("OverworldGraphicsManager",
|
||||||
|
"ForceRefreshGraphics: Forcing graphics reload for map %d", map_index);
|
||||||
|
|
||||||
|
// Mark bitmap as modified to force refresh
|
||||||
|
(*maps_bmp_)[map_index].set_modified(true);
|
||||||
|
|
||||||
|
// Clear the blockset cache to force tile16 reload
|
||||||
|
current_blockset_ = 0xFF;
|
||||||
|
|
||||||
|
// If this is the current map, also ensure sibling maps are refreshed for multi-area maps
|
||||||
|
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
|
||||||
|
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
|
||||||
|
|
||||||
|
auto* map = overworld_->mutable_overworld_map(map_index);
|
||||||
|
if (use_v3_area_sizes) {
|
||||||
|
using zelda3::AreaSizeEnum;
|
||||||
|
auto area_size = map->area_size();
|
||||||
|
|
||||||
|
if (area_size != AreaSizeEnum::SmallArea) {
|
||||||
|
std::vector<int> sibling_maps;
|
||||||
|
int parent_id = map->parent();
|
||||||
|
|
||||||
|
switch (area_size) {
|
||||||
|
case AreaSizeEnum::LargeArea:
|
||||||
|
sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
|
||||||
|
break;
|
||||||
|
case AreaSizeEnum::WideArea:
|
||||||
|
sibling_maps = {parent_id, parent_id + 1};
|
||||||
|
break;
|
||||||
|
case AreaSizeEnum::TallArea:
|
||||||
|
sibling_maps = {parent_id, parent_id + 8};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all sibling maps as needing refresh
|
||||||
|
for (int sibling : sibling_maps) {
|
||||||
|
if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
|
||||||
|
(*maps_bmp_)[sibling].set_modified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (map->is_large_map()) {
|
||||||
|
// Legacy large map handling
|
||||||
|
int parent_id = map->parent();
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
int sibling = parent_id + (i < 2 ? i : i + 6);
|
||||||
|
if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
|
||||||
|
(*maps_bmp_)[sibling].set_modified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
204
src/app/editor/overworld/overworld_graphics_manager.h
Normal file
204
src/app/editor/overworld/overworld_graphics_manager.h
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H
|
||||||
|
#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/status/status.h"
|
||||||
|
#include "app/gfx/bitmap.h"
|
||||||
|
#include "app/gfx/snes_palette.h"
|
||||||
|
#include "app/gfx/tilemap.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
#include "app/zelda3/overworld/overworld.h"
|
||||||
|
#include "app/zelda3/overworld/overworld_map.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace editor {
|
||||||
|
|
||||||
|
using Tilemap = gfx::Tilemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class OverworldGraphicsManager
|
||||||
|
* @brief Manages all graphics-related operations for the Overworld Editor
|
||||||
|
*
|
||||||
|
* This class handles:
|
||||||
|
* - Graphics loading and initialization
|
||||||
|
* - Sprite graphics loading
|
||||||
|
* - Deferred texture processing for smooth loading
|
||||||
|
* - Map texture management
|
||||||
|
* - Map refreshing (full and on-demand)
|
||||||
|
* - Palette refreshing
|
||||||
|
* - Tile16 blockset refreshing
|
||||||
|
* - Multi-area map coordination
|
||||||
|
*
|
||||||
|
* Separating graphics management from the main OverworldEditor improves:
|
||||||
|
* - Code organization and maintainability
|
||||||
|
* - Performance optimization opportunities
|
||||||
|
* - Testing and debugging
|
||||||
|
* - Clear separation of concerns
|
||||||
|
*/
|
||||||
|
class OverworldGraphicsManager {
|
||||||
|
public:
|
||||||
|
OverworldGraphicsManager(
|
||||||
|
zelda3::Overworld* overworld, Rom* rom,
|
||||||
|
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp,
|
||||||
|
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap* current_gfx_bmp,
|
||||||
|
std::vector<gfx::Bitmap>* sprite_previews, gfx::SnesPalette* palette,
|
||||||
|
Tilemap* tile16_blockset)
|
||||||
|
: overworld_(overworld),
|
||||||
|
rom_(rom),
|
||||||
|
maps_bmp_(maps_bmp),
|
||||||
|
tile16_blockset_bmp_(tile16_blockset_bmp),
|
||||||
|
current_gfx_bmp_(current_gfx_bmp),
|
||||||
|
sprite_previews_(sprite_previews),
|
||||||
|
palette_(palette),
|
||||||
|
tile16_blockset_(tile16_blockset) {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Loading Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load all overworld graphics (maps, tilesets, sprites)
|
||||||
|
*
|
||||||
|
* This uses a multi-phase loading strategy:
|
||||||
|
* - Phase 1: Create bitmaps without textures
|
||||||
|
* - Phase 2: Create bitmaps for essential maps only
|
||||||
|
* - Phase 3: Create textures for visible maps
|
||||||
|
* - Deferred loading for remaining maps
|
||||||
|
*/
|
||||||
|
absl::Status LoadGraphics();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Load sprite graphics for all overworld maps
|
||||||
|
*/
|
||||||
|
absl::Status LoadSpriteGraphics();
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Texture Management
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process deferred texture creation (called per frame)
|
||||||
|
*
|
||||||
|
* Creates textures gradually to avoid frame drops.
|
||||||
|
* Prioritizes textures for the current world and visible maps.
|
||||||
|
*/
|
||||||
|
void ProcessDeferredTextures();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ensure a specific map has a texture created
|
||||||
|
*
|
||||||
|
* @param map_index Index of the map to ensure texture for
|
||||||
|
*/
|
||||||
|
void EnsureMapTexture(int map_index);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Refresh Operations
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh the current overworld map
|
||||||
|
*/
|
||||||
|
void RefreshOverworldMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh a specific map on-demand (only if visible)
|
||||||
|
*
|
||||||
|
* @param map_index Index of the map to refresh
|
||||||
|
*/
|
||||||
|
void RefreshOverworldMapOnDemand(int map_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh a child map (legacy method)
|
||||||
|
*
|
||||||
|
* @param map_index Index of the map to refresh
|
||||||
|
*/
|
||||||
|
void RefreshChildMap(int map_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh a child map with selective updates
|
||||||
|
*
|
||||||
|
* @param map_index Index of the map to refresh
|
||||||
|
*/
|
||||||
|
void RefreshChildMapOnDemand(int map_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Safely refresh multi-area maps (large, wide, tall)
|
||||||
|
*
|
||||||
|
* Handles coordination of multi-area maps without recursion.
|
||||||
|
*
|
||||||
|
* @param map_index Index of the map to refresh
|
||||||
|
* @param map Pointer to the OverworldMap object
|
||||||
|
*/
|
||||||
|
void RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap* map);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh the palette for the current map
|
||||||
|
*
|
||||||
|
* Also handles palette updates for multi-area maps.
|
||||||
|
*/
|
||||||
|
absl::Status RefreshMapPalette();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Refresh the tile16 blockset
|
||||||
|
*
|
||||||
|
* This should be called whenever area graphics change.
|
||||||
|
*/
|
||||||
|
absl::Status RefreshTile16Blockset();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Force a graphics refresh for a specific map
|
||||||
|
*
|
||||||
|
* Marks the map's bitmap as modified and clears the blockset cache
|
||||||
|
* to force a full reload on next refresh. Use this when graphics
|
||||||
|
* properties change (area_graphics, animated_gfx, custom tilesets).
|
||||||
|
*
|
||||||
|
* @param map_index Index of the map to force refresh
|
||||||
|
*/
|
||||||
|
void ForceRefreshGraphics(int map_index);
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// State Management
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void set_current_map(int map_index) { current_map_ = map_index; }
|
||||||
|
void set_current_world(int world_index) { current_world_ = world_index; }
|
||||||
|
void set_current_blockset(uint8_t blockset) { current_blockset_ = blockset; }
|
||||||
|
|
||||||
|
int current_map() const { return current_map_; }
|
||||||
|
int current_world() const { return current_world_; }
|
||||||
|
uint8_t current_blockset() const { return current_blockset_; }
|
||||||
|
|
||||||
|
bool all_gfx_loaded() const { return all_gfx_loaded_; }
|
||||||
|
bool map_blockset_loaded() const { return map_blockset_loaded_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Core dependencies
|
||||||
|
zelda3::Overworld* overworld_;
|
||||||
|
Rom* rom_;
|
||||||
|
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp_;
|
||||||
|
gfx::Bitmap* tile16_blockset_bmp_;
|
||||||
|
gfx::Bitmap* current_gfx_bmp_;
|
||||||
|
std::vector<gfx::Bitmap>* sprite_previews_;
|
||||||
|
gfx::SnesPalette* palette_;
|
||||||
|
Tilemap* tile16_blockset_;
|
||||||
|
|
||||||
|
// State tracking
|
||||||
|
int current_map_ = 0;
|
||||||
|
int current_world_ = 0;
|
||||||
|
uint8_t current_blockset_ = 0;
|
||||||
|
bool all_gfx_loaded_ = false;
|
||||||
|
bool map_blockset_loaded_ = false;
|
||||||
|
|
||||||
|
// Deferred texture loading
|
||||||
|
std::vector<gfx::Bitmap*> deferred_map_textures_;
|
||||||
|
std::mutex deferred_textures_mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H
|
||||||
|
|
||||||
@@ -633,14 +633,6 @@ absl::Status Overworld::LoadExits() {
|
|||||||
uint16_t px = (uint16_t)((rom_data[OWExitXPlayer + (i * 2) + 1] << 8) +
|
uint16_t px = (uint16_t)((rom_data[OWExitXPlayer + (i * 2) + 1] << 8) +
|
||||||
rom_data[OWExitXPlayer + (i * 2)]);
|
rom_data[OWExitXPlayer + (i * 2)]);
|
||||||
|
|
||||||
// util::logf(
|
|
||||||
// "Exit: %d RoomID: %d MapID: %d VRAM: %d YScroll: %d XScroll: "
|
|
||||||
// "%d YPlayer: %d XPlayer: %d YCamera: %d XCamera: %d "
|
|
||||||
// "ScrollModY: %d ScrollModX: %d DoorType1: %d DoorType2: %d",
|
|
||||||
// i, exit_room_id, exit_map_id, exit_vram, exit_y_scroll, exit_x_scroll,
|
|
||||||
// py, px, exit_y_camera, exit_x_camera, exit_scroll_mod_y,
|
|
||||||
// exit_scroll_mod_x, exit_door_type_1, exit_door_type_2);
|
|
||||||
|
|
||||||
exits.emplace_back(exit_room_id, exit_map_id, exit_vram, exit_y_scroll,
|
exits.emplace_back(exit_room_id, exit_map_id, exit_vram, exit_y_scroll,
|
||||||
exit_x_scroll, py, px, exit_y_camera, exit_x_camera,
|
exit_x_scroll, py, px, exit_y_camera, exit_x_camera,
|
||||||
exit_scroll_mod_y, exit_scroll_mod_x, exit_door_type_1,
|
exit_scroll_mod_y, exit_scroll_mod_x, exit_door_type_1,
|
||||||
|
|||||||
@@ -194,10 +194,6 @@ class OverworldExit : public GameEntity {
|
|||||||
|
|
||||||
map_pos_ = (uint16_t)(((vram_y_scroll & 0xFFF0) << 3) |
|
map_pos_ = (uint16_t)(((vram_y_scroll & 0xFFF0) << 3) |
|
||||||
((vram_x_scroll & 0xFFF0) >> 3));
|
((vram_x_scroll & 0xFFF0) >> 3));
|
||||||
|
|
||||||
std::cout << "Exit: " << room_id_ << " MapId: " << std::hex << mapid
|
|
||||||
<< " X: " << static_cast<int>(area_x_)
|
|
||||||
<< " Y: " << static_cast<int>(area_y_) << std::endl;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -53,12 +53,6 @@ class OverworldItem : public GameEntity {
|
|||||||
|
|
||||||
game_x_ = static_cast<uint8_t>(std::abs(x_ - (map_x * 512)) / 16);
|
game_x_ = static_cast<uint8_t>(std::abs(x_ - (map_x * 512)) / 16);
|
||||||
game_y_ = static_cast<uint8_t>(std::abs(y_ - (map_y * 512)) / 16);
|
game_y_ = static_cast<uint8_t>(std::abs(y_ - (map_y * 512)) / 16);
|
||||||
|
|
||||||
std::cout << "Item: " << std::hex << std::setw(2) << std::setfill('0')
|
|
||||||
<< static_cast<int>(id_) << " MapId: " << std::hex << std::setw(2)
|
|
||||||
<< std::setfill('0') << static_cast<int>(room_map_id_)
|
|
||||||
<< " X: " << static_cast<int>(game_x_)
|
|
||||||
<< " Y: " << static_cast<int>(game_y_) << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool bg2_ = false;
|
bool bg2_ = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user