Enhance OverworldEditor with new customization features

- Updated OverworldEditor to include new table columns for CopyMap, MapLock, CustomBG, and Overlay, improving user interaction and customization options.
- Implemented DrawCustomBackgroundColorEditor and DrawOverlayEditor methods for managing custom background colors and overlay settings, respectively.
- Added context menu functionality for map locking and quick access to map properties, enhancing usability.
- Refactored OverworldMap to support new features related to animated graphics and subscreen overlays, ensuring compatibility with ZScreamCustomOverworld v3.
This commit is contained in:
scawful
2025-09-24 15:58:46 -04:00
parent 49b4f6d677
commit 12bb5dc3d5
5 changed files with 350 additions and 27 deletions

View File

@@ -141,7 +141,7 @@ void OverworldEditor::Initialize() {
HOVER_HINT("Gfx Group Editor");
});
gui::AddTableColumn(toolset_table_, "##sep3", ICON_MD_MORE_VERT);
gui::AddTableColumn(toolset_table_, "##Properties", [&]() {
gui::AddTableColumn(toolset_table_, "##CopyMap", [&]() {
if (Button(ICON_MD_CONTENT_COPY)) {
std::vector<uint8_t> png_data;
png_data = maps_bmp_[current_map_].GetPngData();
@@ -161,6 +161,24 @@ void OverworldEditor::Initialize() {
gui::AddTableColumn(toolset_table_, "##Properties", [&]() {
Checkbox("Properties", &show_properties_editor_);
});
gui::AddTableColumn(toolset_table_, "##MapLock", [&]() {
if (Button(current_map_lock_ ? ICON_MD_LOCK : ICON_MD_LOCK_OPEN)) {
current_map_lock_ = !current_map_lock_;
}
HOVER_HINT(current_map_lock_ ? "Unlock Map" : "Lock Map");
});
gui::AddTableColumn(toolset_table_, "##CustomBG", [&]() {
if (Button(ICON_MD_PALETTE)) {
show_custom_bg_color_editor_ = !show_custom_bg_color_editor_;
}
HOVER_HINT("Custom Background Colors");
});
gui::AddTableColumn(toolset_table_, "##Overlay", [&]() {
if (Button(ICON_MD_LAYERS)) {
show_overlay_editor_ = !show_overlay_editor_;
}
HOVER_HINT("Overlay Editor");
});
}
absl::Status OverworldEditor::Load() {
@@ -220,6 +238,18 @@ void OverworldEditor::DrawToolset() {
ImGui::End();
}
if (show_custom_bg_color_editor_) {
ImGui::Begin("Custom Background Colors", &show_custom_bg_color_editor_);
DrawCustomBackgroundColorEditor();
ImGui::End();
}
if (show_overlay_editor_) {
ImGui::Begin("Overlay Editor", &show_overlay_editor_);
DrawOverlayEditor();
ImGui::End();
}
// TODO: Customizable shortcuts for the Overworld Editor
if (!ImGui::IsAnyItemActive()) {
using enum EditingMode;
@@ -423,6 +453,40 @@ void OverworldEditor::DrawCustomOverworldMapSettings() {
ImGui::EndTable();
}
}
// Add additional v3 features
if (asm_version >= 3 && asm_version != 0xFF) {
Separator();
Text("ZSCustomOverworld v3 Features:");
// Main Palette
if (gui::InputHexByte("Main Palette",
overworld_.mutable_overworld_map(current_map_)
->mutable_main_palette(),
kInputFieldSize)) {
RefreshMapProperties();
status_ = RefreshMapPalette();
RefreshOverworldMap();
}
// Animated GFX
if (gui::InputHexByte("Animated GFX",
overworld_.mutable_overworld_map(current_map_)
->mutable_animated_gfx(),
kInputFieldSize)) {
RefreshMapProperties();
RefreshOverworldMap();
}
// Subscreen Overlay
if (gui::InputHexWord("Subscreen Overlay",
overworld_.mutable_overworld_map(current_map_)
->mutable_subscreen_overlay(),
kInputFieldSize + 20)) {
RefreshMapProperties();
RefreshOverworldMap();
}
}
ImGui::EndTable();
}
@@ -783,6 +847,8 @@ void OverworldEditor::DrawOverworldCanvas() {
ow_map_canvas_.DrawContextMenu();
} else {
ow_map_canvas_.set_draggable(false);
// Always show our custom overworld context menu
DrawOverworldContextMenu();
}
if (overworld_.is_loaded()) {
@@ -1340,6 +1406,227 @@ absl::Status OverworldEditor::RefreshTile16Blockset() {
return absl::OkStatus();
}
void OverworldEditor::DrawCustomBackgroundColorEditor() {
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version < 2 || asm_version == 0xFF) {
Text("Custom background colors are only available in ZSCustomOverworld v2+");
return;
}
// Check if area-specific background colors are enabled
bool bg_enabled = (*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] != 0x00;
if (Checkbox("Enable Area-Specific Background Colors", &bg_enabled)) {
(*rom_)[zelda3::OverworldCustomAreaSpecificBGEnabled] = bg_enabled ? 0x01 : 0x00;
}
if (!bg_enabled) {
Text("Area-specific background colors are disabled.");
return;
}
Separator();
// Display current map's background color
Text("Current Map: %d (0x%02X)", current_map_, current_map_);
// Get current background color
uint16_t current_bg_color = (*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2)] |
((*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2) + 1] << 8);
// Convert SNES color to ImVec4
ImVec4 current_color = ImVec4(
((current_bg_color & 0x1F) * 8) / 255.0f,
(((current_bg_color >> 5) & 0x1F) * 8) / 255.0f,
(((current_bg_color >> 10) & 0x1F) * 8) / 255.0f,
1.0f
);
// Color picker
if (ColorPicker4("Background Color", (float*)&current_color,
ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_InputRGB)) {
// Convert ImVec4 back to SNES color
uint16_t new_color =
(static_cast<uint16_t>(current_color.x * 31) & 0x1F) |
((static_cast<uint16_t>(current_color.y * 31) & 0x1F) << 5) |
((static_cast<uint16_t>(current_color.z * 31) & 0x1F) << 10);
// Write to ROM
(*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2)] = new_color & 0xFF;
(*rom_)[zelda3::OverworldCustomAreaSpecificBGPalette + (current_map_ * 2) + 1] = (new_color >> 8) & 0xFF;
// Update the overworld map
overworld_.mutable_overworld_map(current_map_)->set_area_specific_bg_color(new_color);
// Refresh the map
RefreshOverworldMap();
}
Separator();
// Show color preview
Text("Color Preview:");
ImGui::ColorButton("##bg_preview", current_color, ImGuiColorEditFlags_NoTooltip, ImVec2(100, 50));
SameLine();
Text("SNES Color: 0x%04X", current_bg_color);
}
void OverworldEditor::DrawOverlayEditor() {
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version < 1 || asm_version == 0xFF) {
Text("Overlay editor is only available in ZSCustomOverworld v1+");
return;
}
// Check if subscreen overlays are enabled
bool overlay_enabled = (*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] != 0x00;
if (Checkbox("Enable Subscreen Overlays", &overlay_enabled)) {
(*rom_)[zelda3::OverworldCustomSubscreenOverlayEnabled] = overlay_enabled ? 0x01 : 0x00;
}
if (!overlay_enabled) {
Text("Subscreen overlays are disabled.");
return;
}
Separator();
// Display current map's overlay
Text("Current Map: %d (0x%02X)", current_map_, current_map_);
// Get current overlay ID
uint16_t current_overlay = (*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2)] |
((*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2) + 1] << 8);
// Overlay ID input
if (gui::InputHexWord("Overlay ID", &current_overlay, 100)) {
// Write to ROM
(*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2)] = current_overlay & 0xFF;
(*rom_)[zelda3::OverworldCustomSubscreenOverlayArray + (current_map_ * 2) + 1] = (current_overlay >> 8) & 0xFF;
// Update the overworld map
overworld_.mutable_overworld_map(current_map_)->set_subscreen_overlay(current_overlay);
// Refresh the map
RefreshOverworldMap();
}
Separator();
// Show overlay information
Text("Overlay Information:");
Text("ID: 0x%04X", current_overlay);
if (current_overlay == 0x00FF) {
Text("No overlay");
} else if (current_overlay == 0x009D) {
Text("Fog 2 (Lost Woods/Skull Woods)");
} else if (current_overlay == 0x0095) {
Text("Sky Background (Death Mountain)");
} else if (current_overlay == 0x009C) {
Text("Lava (Dark World Death Mountain)");
} else if (current_overlay == 0x0096) {
Text("Pyramid Background");
} else if (current_overlay == 0x0097) {
Text("Fog 1 (Master Sword Area)");
} else if (current_overlay == 0x0093) {
Text("Triforce Room Curtains");
} else {
Text("Custom overlay");
}
}
void OverworldEditor::DrawMapLockControls() {
if (current_map_lock_) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f));
Text("Map Locked: %d (0x%02X)", current_map_, current_map_);
PopStyleColor();
if (Button("Unlock Map")) {
current_map_lock_ = false;
}
} else {
Text("Map: %d (0x%02X) - Click to lock", current_map_, current_map_);
if (Button("Lock Map")) {
current_map_lock_ = true;
}
}
}
void OverworldEditor::DrawOverworldContextMenu() {
// Get the current map from mouse position
auto mouse_position = ow_map_canvas_.drawn_tile_position();
int map_x = mouse_position.x / kOverworldMapSize;
int map_y = mouse_position.y / kOverworldMapSize;
int hovered_map = map_x + map_y * 8;
if (current_world_ == 1) {
hovered_map += 0x40;
} else if (current_world_ == 2) {
hovered_map += 0x80;
}
// Only show context menu if we're hovering over a valid map
if (hovered_map >= 0 && hovered_map < 0xA0) {
if (ImGui::BeginPopupContextWindow("OverworldMapContext")) {
Text("Map %d (0x%02X)", hovered_map, hovered_map);
Separator();
// Map lock controls
if (current_map_lock_ && current_map_ == hovered_map) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f));
Text("Currently Locked");
PopStyleColor();
if (MenuItem("Unlock Map")) {
current_map_lock_ = false;
}
} else {
if (MenuItem("Lock to This Map")) {
current_map_lock_ = true;
current_map_ = hovered_map;
}
}
Separator();
// Quick access to map settings
if (MenuItem("Map Properties")) {
show_properties_editor_ = true;
current_map_ = hovered_map;
}
// Custom overworld features (only show if v3+)
static uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
if (asm_version >= 3 && asm_version != 0xFF) {
if (MenuItem("Custom Background Color")) {
show_custom_bg_color_editor_ = true;
current_map_ = hovered_map;
}
if (MenuItem("Overlay Settings")) {
show_overlay_editor_ = true;
current_map_ = hovered_map;
}
}
Separator();
// Canvas controls
if (MenuItem("Reset Canvas Position")) {
ow_map_canvas_.set_scrolling(ImVec2(0, 0));
}
if (MenuItem("Zoom to Fit")) {
ow_map_canvas_.set_global_scale(1.0f);
ow_map_canvas_.set_scrolling(ImVec2(0, 0));
}
ImGui::EndPopup();
}
}
}
void OverworldEditor::DrawOverworldProperties() {
static bool init_properties = false;

View File

@@ -158,6 +158,10 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
absl::Status LoadSpriteGraphics();
void DrawOverworldProperties();
void DrawCustomBackgroundColorEditor();
void DrawOverlayEditor();
void DrawMapLockControls();
void DrawOverworldContextMenu();
absl::Status UpdateUsageStats();
void DrawUsageGrid();
@@ -215,6 +219,9 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
bool middle_mouse_dragging_ = false;
bool is_dragging_entity_ = false;
bool current_map_lock_ = false;
bool show_custom_bg_color_editor_ = false;
bool show_overlay_editor_ = false;
bool use_area_specific_bg_color_ = false;
gfx::Tilemap tile16_blockset_;
@@ -258,7 +265,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
gui::CanvasGridSize::k16x16};
gui::Canvas properties_canvas_;
gui::Table toolset_table_{"##ToolsetTable0", 22, kToolsetTableFlags};
gui::Table toolset_table_{"##ToolsetTable0", 25, kToolsetTableFlags};
gui::Table map_settings_table_{kOWMapTable.data(), 8, kOWMapFlags,
ImVec2(0, 0)};

View File

@@ -165,6 +165,7 @@ class Canvas {
auto draw_list() const { return draw_list_; }
auto zero_point() const { return canvas_p0_; }
auto scrolling() const { return scrolling_; }
void set_scrolling(ImVec2 scroll) { scrolling_ = scroll; }
auto drawn_tile_position() const { return drawn_tile_pos_; }
auto canvas_size() const { return canvas_sz_; }
void set_global_scale(float scale) { global_scale_ = scale; }

View File

@@ -826,6 +826,16 @@ absl::Status OverworldMap::LoadPalette() {
void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
int size, uint8_t *all_gfx) {
// Ensure we don't go out of bounds
int max_offset = static_graphics_offset * size + size;
if (max_offset > rom_->graphics_buffer().size()) {
// Fill with zeros if out of bounds
for (int i = 0; i < size; i++) {
current_gfx_[(index * size) + i] = 0x00;
}
return;
}
for (int i = 0; i < size; i++) {
auto byte = all_gfx[i + (static_graphics_offset * size)];
switch (index) {
@@ -842,10 +852,29 @@ void OverworldMap::ProcessGraphicsBuffer(int index, int static_graphics_offset,
absl::Status OverworldMap::BuildTileset() {
if (current_gfx_.size() == 0) current_gfx_.resize(0x10000, 0x00);
for (int i = 0; i < 0x10; i++) {
ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000,
// Process the 8 main graphics sheets (slots 0-7)
for (int i = 0; i < 8; i++) {
if (static_graphics_[i] != 0) {
ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000,
rom_->graphics_buffer().data());
}
}
// Process sprite graphics (slots 8-15)
for (int i = 8; i < 16; i++) {
if (static_graphics_[i] != 0) {
ProcessGraphicsBuffer(i, static_graphics_[i], 0x1000,
rom_->graphics_buffer().data());
}
}
// Process animated graphics if available (slot 16)
if (static_graphics_[16] != 0) {
ProcessGraphicsBuffer(7, static_graphics_[16], 0x1000,
rom_->graphics_buffer().data());
}
return absl::OkStatus();
}

View File

@@ -26,34 +26,21 @@ constexpr int OverworldCustomAreaSpecificBGPalette = 0x140000;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomAreaSpecificBGEnabled = 0x140140;
// Additional v3 constants
constexpr int OverworldCustomSubscreenOverlayArray = 0x140340; // 2 bytes for each overworld area (0x140)
constexpr int OverworldCustomSubscreenOverlayEnabled = 0x140144; // 1 byte, not 0 if enabled
constexpr int OverworldCustomAnimatedGFXArray = 0x1402A0; // 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomAnimatedGFXEnabled = 0x140143; // 1 byte, not 0 if enabled
constexpr int OverworldCustomTileGFXGroupArray = 0x140480; // 8 bytes for each overworld area (0x500)
constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148; // 1 byte, not 0 if enabled
constexpr int OverworldCustomMosaicArray = 0x140200; // 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomMosaicEnabled = 0x140142; // 1 byte, not 0 if enabled
// 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomMainPaletteArray = 0x140160;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomMainPaletteEnabled = 0x140141;
// 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomMosaicArray = 0x140200;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomMosaicEnabled = 0x140142;
// 1 byte for each overworld area (0xA0)
constexpr int OverworldCustomAnimatedGFXArray = 0x1402A0;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomAnimatedGFXEnabled = 0x140143;
// 2 bytes for each overworld area (0x140)
constexpr int OverworldCustomSubscreenOverlayArray = 0x140340;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomSubscreenOverlayEnabled = 0x140144;
// 8 bytes for each overworld area (0x500)
constexpr int OverworldCustomTileGFXGroupArray = 0x140480;
// 1 byte, not 0 if enabled
constexpr int OverworldCustomTileGFXGroupEnabled = 0x140148;
// v3 expanded constants
constexpr int kOverworldMessagesExpanded = 0x1417F8;
@@ -145,12 +132,24 @@ class OverworldMap : public gfx::GfxContext {
area_specific_bg_color_ = color;
}
auto subscreen_overlay() const { return subscreen_overlay_; }
void set_subscreen_overlay(uint16_t overlay) { subscreen_overlay_ = overlay; }
auto animated_gfx() const { return animated_gfx_; }
void set_animated_gfx(uint8_t gfx) { animated_gfx_ = gfx; }
auto custom_tileset(int index) const { return custom_gfx_ids_[index]; }
void set_custom_tileset(int index, uint8_t value) { custom_gfx_ids_[index] = value; }
auto mutable_current_graphics() { return &current_gfx_; }
auto mutable_area_graphics() { return &area_graphics_; }
auto mutable_area_palette() { return &area_palette_; }
auto mutable_sprite_graphics(int i) { return &sprite_graphics_[i]; }
auto mutable_sprite_palette(int i) { return &sprite_palette_[i]; }
auto mutable_message_id() { return &message_id_; }
auto mutable_main_palette() { return &main_palette_; }
auto mutable_animated_gfx() { return &animated_gfx_; }
auto mutable_subscreen_overlay() { return &subscreen_overlay_; }
auto mutable_area_music(int i) { return &area_music_[i]; }
auto mutable_static_graphics(int i) { return &static_graphics_[i]; }