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:
scawful
2025-10-05 19:07:06 -04:00
parent 9835eb2ab1
commit 405dece70a
10 changed files with 1190 additions and 85 deletions

View File

@@ -36,6 +36,7 @@ set(
app/editor/graphics/gfx_group_editor.cc
app/editor/overworld/entity.cc
app/editor/overworld/overworld_entity_renderer.cc
app/editor/overworld/overworld_graphics_manager.cc
app/editor/system/settings_editor.cc
app/editor/system/command_manager.cc
app/editor/system/extension_manager.cc

View File

@@ -460,7 +460,22 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
overworld_->mutable_overworld_map(current_map)
->mutable_area_graphics(),
kHexByteInputWidth)) {
// CORRECT ORDER: Properties first, then graphics reload
// 1. Propagate properties to siblings FIRST (calls LoadAreaGraphics on siblings)
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();
}
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)
->mutable_sprite_graphics(game_state),
kHexByteInputWidth)) {
ForceRefreshGraphics(current_map);
RefreshMapProperties();
RefreshOverworldMap();
}
@@ -484,34 +500,48 @@ void MapPropertiesSystem::DrawGraphicsPopup(int current_map, int game_state) {
overworld_->mutable_overworld_map(current_map)
->mutable_animated_gfx(),
kHexByteInputWidth)) {
ForceRefreshGraphics(current_map);
RefreshMapProperties();
RefreshTile16Blockset();
RefreshOverworldMap();
}
HOVER_HINT("Animated tile graphics (water, lava, etc.)");
}
ImGui::Separator();
ImGui::Text(ICON_MD_GRID_VIEW " Custom Tile Graphics");
ImGui::Separator();
// Custom Tile Graphics - Only available for v1+ ROMs
if (asm_version >= 1 && asm_version != 0xFF) {
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
if (BeginTable("CustomTileGraphics", 2,
ImGuiTableFlags_SizingFixedFit)) {
for (int i = 0; i < 8; i++) {
TableNextColumn();
std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i);
if (gui::InputHexByte(label.c_str(),
overworld_->mutable_overworld_map(current_map)
->mutable_custom_tileset(i),
90.f)) {
RefreshMapProperties();
RefreshOverworldMap();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
// Show the 8 custom graphics IDs in a 2-column layout for density
if (BeginTable("CustomTileGraphics", 2,
ImGuiTableFlags_SizingFixedFit)) {
for (int i = 0; i < 8; i++) {
TableNextColumn();
std::string label = absl::StrFormat(ICON_MD_LAYERS " Sheet %d", i);
if (gui::InputHexByte(label.c_str(),
overworld_->mutable_overworld_map(current_map)
->mutable_custom_tileset(i),
90.f)) {
ForceRefreshGraphics(current_map);
RefreshMapProperties();
RefreshTile16Blockset();
RefreshOverworldMap();
}
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
@@ -693,7 +723,12 @@ void MapPropertiesSystem::DrawBasicPropertiesTab(int current_map) {
overworld_->mutable_overworld_map(current_map)
->mutable_area_graphics(),
kInputFieldSize)) {
// CORRECT ORDER: Properties first, then graphics reload
RefreshMapProperties();
(*maps_bmp_)[current_map].set_modified(true);
overworld_->mutable_overworld_map(current_map)->LoadAreaGraphics();
RefreshSiblingMapGraphics(current_map);
RefreshTile16Blockset();
RefreshOverworldMap();
}
if (ImGui::IsItemHovered()) {
@@ -955,36 +990,55 @@ void MapPropertiesSystem::DrawCustomFeaturesTab(int current_map) {
}
void MapPropertiesSystem::DrawTileGraphicsTab(int current_map) {
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)) {
RefreshMapProperties();
RefreshOverworldMap();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Custom graphics sheet %d (0x00-0xFF)", i);
}
}
ImGui::EndTable();
}
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
Separator();
ImGui::TextWrapped("These 8 sheets allow custom tile graphics per map. "
"Each sheet references a graphics ID loaded into VRAM.");
// Only show custom tile graphics for v1+ ROMs
if (asm_version >= 1 && asm_version != 0xFF) {
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) {
@@ -1090,6 +1144,70 @@ absl::Status MapPropertiesSystem::RefreshMapPalette() {
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) {
static uint8_t asm_version =
(*rom_)[zelda3::OverworldCustomASMHasBeenApplied];

View File

@@ -22,6 +22,7 @@ class MapPropertiesSystem {
// Callback types for refresh operations
using RefreshCallback = std::function<void()>;
using RefreshPaletteCallback = std::function<absl::Status()>;
using ForceRefreshGraphicsCallback = std::function<void(int)>;
explicit MapPropertiesSystem(zelda3::Overworld* overworld, Rom* rom,
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp = nullptr,
@@ -31,10 +32,14 @@ class MapPropertiesSystem {
// Set callbacks for refresh operations
void SetRefreshCallbacks(RefreshCallback refresh_map_properties,
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_overworld_map_ = std::move(refresh_overworld_map);
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
@@ -85,6 +90,11 @@ class MapPropertiesSystem {
void RefreshMapProperties();
void RefreshOverworldMap();
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_;
Rom* rom_;
@@ -95,6 +105,8 @@ class MapPropertiesSystem {
RefreshCallback refresh_map_properties_;
RefreshCallback refresh_overworld_map_;
RefreshPaletteCallback refresh_map_palette_;
RefreshPaletteCallback refresh_tile16_blockset_;
ForceRefreshGraphicsCallback force_refresh_graphics_;
// Using centralized UI constants from ui_constants.h
};

View File

@@ -54,7 +54,9 @@ void OverworldEditor::Initialize() {
map_properties_system_->SetRefreshCallbacks(
[this]() { this->RefreshMapProperties(); },
[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
@@ -343,18 +345,30 @@ void OverworldEditor::DrawToolset() {
toolbar.AddSeparator();
// 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(),
[this]() {
// CORRECT ORDER: Properties first, then graphics reload
// 1. Propagate properties to siblings FIRST (this also calls LoadAreaGraphics on siblings)
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
}
if (toolbar.AddProperty(ICON_MD_PALETTE, "##pal",
if (toolbar.AddProperty(ICON_MD_PALETTE, " Pal",
overworld_.mutable_overworld_map(current_map_)->mutable_area_palette(),
[this]() {
// Palette changes also need to propagate to siblings
RefreshSiblingMapGraphics(current_map_);
RefreshMapProperties();
status_ = RefreshMapPalette();
RefreshOverworldMap();
@@ -406,13 +420,6 @@ void OverworldEditor::DrawToolset() {
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")) {
show_usage_stats_ = !show_usage_stats_;
}
@@ -1639,13 +1646,11 @@ void OverworldEditor::RefreshChildMapOnDemand(int map_index) {
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);
}
// CRITICAL FIX: Force COMPLETE texture recreation for immediate visibility
// UpdateBitmap() was still deferred - we need to force a full re-render
// Always recreate the texture to ensure immediate GPU update
Renderer::Get().RenderBitmap(&maps_bmp_[map_index]);
}
// Handle multi-area maps (large, wide, tall) with safe coordination
@@ -1888,6 +1893,67 @@ absl::Status OverworldEditor::RefreshMapPalette() {
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() {
const auto& current_ow_map = *overworld_.mutable_overworld_map(current_map_);
@@ -1935,6 +2001,9 @@ void OverworldEditor::RefreshMapProperties() {
map.set_sprite_palette(game_state_,
current_ow_map.sprite_palette(game_state_));
map.set_message_id(current_ow_map.message_id());
// CRITICAL FIX: Reload graphics after changing properties
map.LoadAreaGraphics();
}
}
} else {
@@ -1954,6 +2023,9 @@ void OverworldEditor::RefreshMapProperties() {
map.set_sprite_palette(game_state_,
current_ow_map.sprite_palette(game_state_));
map.set_message_id(current_ow_map.message_id());
// CRITICAL FIX: Reload graphics after changing properties
map.LoadAreaGraphics();
}
}
}

View File

@@ -123,6 +123,8 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
absl::Status RefreshMapPalette();
void RefreshMapProperties();
absl::Status RefreshTile16Blockset();
void ForceRefreshGraphics(int map_index);
void RefreshSiblingMapGraphics(int map_index, bool include_self = false);
void DrawOverworldMaps();
void DrawOverworldEdits();

View 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

View 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

View File

@@ -633,14 +633,6 @@ absl::Status Overworld::LoadExits() {
uint16_t px = (uint16_t)((rom_data[OWExitXPlayer + (i * 2) + 1] << 8) +
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,
exit_x_scroll, py, px, exit_y_camera, exit_x_camera,
exit_scroll_mod_y, exit_scroll_mod_x, exit_door_type_1,

View File

@@ -194,10 +194,6 @@ class OverworldExit : public GameEntity {
map_pos_ = (uint16_t)(((vram_y_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;
}
};

View File

@@ -53,12 +53,6 @@ class OverworldItem : public GameEntity {
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);
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;