10#include "absl/status/statusor.h"
11#include "absl/strings/str_format.h"
25#include "imgui/imgui.h"
33using ImGui::BeginTable;
35using ImGui::Separator;
36using ImGui::TableNextColumn;
54 const int parent = map->parent();
64 return absl::FailedPreconditionError(
"ROM is not loaded");
72 return absl::FailedPreconditionError(
73 "Wide and Tall areas require ZSCustomOverworld v3+");
75 return absl::OkStatus();
80 return absl::FailedPreconditionError(
81 "This field requires ZSCustomOverworld v2+");
83 return absl::OkStatus();
86 return absl::FailedPreconditionError(
87 "Animated GFX requires ZSCustomOverworld v3+");
89 return absl::OkStatus();
92 return absl::FailedPreconditionError(
93 "Custom tile graphics require ZSCustomOverworld v1+");
95 return absl::OkStatus();
98 return absl::FailedPreconditionError(
99 "Visual effect editing requires ZSCustomOverworld v1+");
101 return absl::OkStatus();
103 return absl::OkStatus();
131 if (!rom || address < 0 ||
static_cast<size_t>(address) >= rom->
size()) {
134 (*rom)[address] = value;
139 if (!rom || address < 0 ||
static_cast<size_t>(address + 1) >= rom->
size()) {
142 (*rom)[address] = value & 0xFF;
143 (*rom)[address + 1] = (value >> 8) & 0xFF;
158 int source_map_id = map_id;
160 source_map_id = selected_map->parent();
167 clipboard.
valid =
true;
169 clipboard.
area_size =
static_cast<int>(map->area_size());
179 for (
int i = 0; i < 3; ++i) {
183 for (
int i = 0; i < 4; ++i) {
184 clipboard.
music[i] = map->area_music(i);
186 for (
int i = 0; i < 8; ++i) {
203 const int clamped_state = std::clamp(game_state, 0, 2);
222 int current_map,
bool& show_custom_bg_color_editor) {
223 (void)show_custom_bg_color_editor;
225 Text(tr(
"No overworld loaded"));
231 Text(tr(
"Custom background colors require ZSCustomOverworld v2+"));
235 Text(tr(
"Custom Background Color Editor"));
239 bool use_area_specific_bg_color =
241 if (ImGui::Checkbox(tr(
"Use Area-Specific Background Color"),
242 &use_area_specific_bg_color)) {
245 use_area_specific_bg_color ? 0x01 : 0x00;
248 if (use_area_specific_bg_color) {
250 const int property_map = EffectivePropertyMapId(
overworld_, current_map);
255 uint16_t current_color = map->area_specific_bg_color();
261 if (ImGui::ColorPicker4(
262 "Background Color", (
float*)&color_vec,
263 ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex)) {
268 new_snes_color.
snes()});
271 Text(tr(
"SNES Color: 0x%04X"), current_color);
276 bool& show_overlay_editor) {
277 (void)show_overlay_editor;
279 Text(tr(
"No overworld loaded"));
285 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
287 ImGui::Text(tr(
"Map: 0x%02X"), current_map);
293 " Enhanced overlay editing requires ZSCustomOverworld v1+");
295 ImGui::TextWrapped(tr(
296 "Subscreen overlays are a vanilla feature used for atmospheric effects "
297 "like fog, rain, and forest canopy. ZSCustomOverworld expands this by "
298 "allowing per-area overlay configuration and additional "
305 ImGuiTreeNodeFlags_DefaultOpen)) {
307 ImGui::TextWrapped(tr(
308 "Visual effects (subscreen overlays) are semi-transparent layers drawn "
309 "on top of or behind your map. They reference special area maps "
311 "for their tile16 graphics data."));
313 ImGui::Text(tr(
"Common uses:"));
314 ImGui::BulletText(tr(
"Fog effects (Lost Woods, Skull Woods)"));
315 ImGui::BulletText(tr(
"Rain (Misery Mire)"));
316 ImGui::BulletText(tr(
"Forest canopy (Lost Woods)"));
317 ImGui::BulletText(tr(
"Sky backgrounds (Death Mountain)"));
318 ImGui::BulletText(tr(
"Under bridge views"));
324 bool use_subscreen_overlay =
327 &use_subscreen_overlay)) {
330 use_subscreen_overlay ? 0x01 : 0x00;
332 if (ImGui::IsItemHovered()) {
334 tr(
"Enable/disable visual effect overlay for this map area"));
337 if (use_subscreen_overlay) {
339 uint16_t current_overlay =
344 0, current_overlay});
346 if (ImGui::IsItemHovered()) {
348 tr(
"ID of the special area map (0x80-0x9F) to use for\n"
349 "visual effects. That map's tile16 data will be drawn\n"
350 "as a semi-transparent layer on this area."));
355 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f),
ICON_MD_INFO " %s",
356 overlay_desc.c_str());
360 " Common Visual Effect IDs")) {
362 ImGui::BulletText(tr(
"0x0093 - Triforce Room Curtain"));
363 ImGui::BulletText(tr(
"0x0094 - Under the Bridge"));
364 ImGui::BulletText(tr(
"0x0095 - Sky Background (LW Death Mountain)"));
365 ImGui::BulletText(tr(
"0x0096 - Pyramid Background"));
366 ImGui::BulletText(tr(
"0x0097 - Fog Overlay (Master Sword Area)"));
367 ImGui::BulletText(tr(
"0x009C - Lava Background (DW Death Mountain)"));
368 ImGui::BulletText(tr(
"0x009D - Fog Overlay (Lost/Skull Woods)"));
369 ImGui::BulletText(tr(
"0x009E - Tree Canopy (Forest)"));
370 ImGui::BulletText(tr(
"0x009F - Rain Effect (Misery Mire)"));
371 ImGui::BulletText(tr(
"0x00FF - No Overlay (Disabled)"));
376 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
ICON_MD_BLOCK
377 " No visual effects enabled for this area");
386 const int map_id = target.
map_id;
387 const bool valid_map = target.
valid();
394 const auto describe = [&](
int id) -> std::string {
396 return absl::StrFormat(
"0x%02X",
id);
400 return m.map_name.empty()
401 ? absl::StrFormat(
"%s | %s", m.map_id_label, m.world_label)
402 : absl::StrFormat(
"%s | %s | %s", m.map_id_label, m.world_label,
405 const auto hint = [
this](
const char* name) -> std::string {
409 bool separator_after =
false) {
419 valid_map ? absl::StrFormat(
"Map 0x%02X", map_id) :
"Outside Overworld");
421 title.
tooltip = describe(map_id);
424 if (valid_map && target.
tile16_id >= 0) {
430 absl::StrFormat(
"Tile16 0x%03X", target.
tile16_id));
432 absl::StrFormat(
"Tile coordinates: (%d, %d)", tile_x, tile_y);
437 const bool has_tile = valid_map && target.
tile16_id >= 0;
464 edit_graphics_callback_(target);
479 [
this, valid_map]() {
488 if (current == target.
map_id) {
491 const auto* current_map =
overworld_ && IsValidMapId(current)
494 return !(current_map && current_map->parent() == target.
parent_map_id);
507 [
this, valid_map]() {
511 properties_item.
tooltip =
"Double-click the map to open properties.";
516 [
this, target, ¤t_map_lock]() {
519 if (current_map_lock) {
520 current_map_lock =
false;
523 current_map_lock =
true;
526 [
this, valid_map, ¤t_map_lock]() {
530 return current_map_lock;
532 pin_item.shortcut = hint(
"overworld.toggle_lock");
534 "Pinned: the current map and its properties stay put while the cursor "
535 "moves. Clicking a map still selects it.";
540 related_menu.
label =
"Related Maps";
542 const auto jump = [
this](
int id) {
549 const int world_start = target.
world * 0x40;
550 const int world_end =
552 for (
int id = world_start;
id < world_end; ++id) {
554 if (
id == map_id || !member || member->parent() != target.
parent_map_id) {
557 const std::string role =
558 id == target.
parent_map_id ?
"Area parent: " :
"Same area: ";
559 related_menu.
subitems.emplace_back(role + describe(
id),
560 [jump,
id]() { jump(
id); });
562 if (target.
world < 2) {
563 const int counterpart = target.
world == 0 ? map_id + 0x40 : map_id - 0x40;
565 if (!related_menu.
subitems.empty()) {
566 related_menu.
subitems.back().separator_after =
true;
569 std::string(
"Other world: ") + describe(counterpart),
570 [jump, counterpart]() { jump(counterpart); });
574 const bool has_related = !related_menu.
subitems.empty();
584 if (has_metadata && shared_clipboard) {
586 "Copy Map Properties",
587 [
this, target, shared_clipboard]() {
598 "Paste Map Properties",
599 [
this, target, shared_clipboard]() {
605 "Paste map properties from 0x%02X",
608 [shared_clipboard]() {
623 insert_menu.
label =
"Insert";
640 for (
const EntityType& entity : entity_types) {
641 const char* type = entity.type;
643 entity.label, entity.icon, [
this, target, type]() {
652 view_menu.
label =
"View";
653 const auto add_toggle = [&](
const char* icon,
const char* label,
655 const char* shortcut_name) {
656 if (!toggle.toggle || !toggle.is_on) {
661 item.
shortcut = hint(shortcut_name);
662 view_menu.
subitems.push_back(std::move(item));
666 "overworld.toggle_entities");
669 view_menu.
subitems.back().separator_after =
true;
671 const auto add_view_action = [&](
const char* icon,
const char* label,
672 const std::function<void()>& callback,
673 const char* shortcut_name) {
680 [callback]() {
return bool(callback); });
682 item.
shortcut = hint(shortcut_name);
683 view_menu.
subitems.push_back(std::move(item));
686 "overworld.zoom_in");
688 "overworld.zoom_out");
691 "overworld.zoom_fit");
709 const std::vector<OverworldPropertyEdit>& edits,
710 const std::string& description) {
714 for (
const auto& edit : edits) {
720 return absl::OkStatus();
725 return CheckFieldSupported(
rom_, edit);
731 return absl::FailedPreconditionError(
"Overworld is not available");
733 const int map_id = EffectivePropertyMapId(
735 if (!IsValidMapId(map_id)) {
736 return absl::InvalidArgumentError(
737 absl::StrFormat(
"Invalid overworld map: %d", edit.
map_id));
741 return absl::NotFoundError(
742 absl::StrFormat(
"Overworld map 0x%02X is unavailable", map_id));
745 switch (edit.
field) {
747 return static_cast<int>(map->area_size());
749 return map->area_graphics();
751 return map->area_palette();
753 return map->main_palette();
755 return map->sprite_graphics(std::clamp(edit.
index, 0, 2));
757 return map->sprite_palette(std::clamp(edit.
index, 0, 2));
759 return map->animated_gfx();
761 return map->custom_tileset(std::clamp(edit.
index, 0, 7));
763 return map->message_id();
765 return map->area_music(std::clamp(edit.
index, 0, 3));
771 return map->area_specific_bg_color();
773 return map->subscreen_overlay();
775 return absl::InvalidArgumentError(
"Unknown overworld property field");
781 return absl::FailedPreconditionError(
782 "Overworld property editing requires loaded overworld data and ROM");
785 if (!IsValidMapId(map_id)) {
786 return absl::InvalidArgumentError(
787 absl::StrFormat(
"Invalid overworld map: %d", edit.
map_id));
791 return absl::NotFoundError(
792 absl::StrFormat(
"Overworld map 0x%02X is unavailable", map_id));
795 const absl::Status supported = CheckFieldSupported(
rom_, edit);
796 if (!supported.ok()) {
800 const int game_state = std::clamp(edit.
index, 0, 2);
801 switch (edit.
field) {
813 map->set_area_graphics(
static_cast<uint8_t
>(edit.
value));
816 (*maps_bmp_)[map_id].set_modified(
true);
819 map->LoadAreaGraphics();
825 map->set_area_palette(
static_cast<uint8_t
>(edit.
value));
831 map->set_main_palette(
static_cast<uint8_t
>(edit.
value));
837 map->set_sprite_graphics(game_state,
static_cast<uint8_t
>(edit.
value));
843 map->set_sprite_palette(game_state,
static_cast<uint8_t
>(edit.
value));
848 map->set_animated_gfx(
static_cast<uint8_t
>(edit.
value));
855 const int slot = std::clamp(edit.
index, 0, 7);
856 map->set_custom_tileset(slot,
static_cast<uint8_t
>(edit.
value));
858 map->LoadAreaGraphics();
867 map->set_message_id(
static_cast<uint16_t
>(edit.
value));
872 const int music_state = std::clamp(edit.
index, 0, 3);
873 *map->mutable_area_music(music_state) =
static_cast<uint8_t
>(edit.
value);
874 WriteRomByteIfValid(
rom_, MusicRomAddressForMapState(map_id, music_state),
875 static_cast<uint8_t
>(edit.
value));
880 *map->mutable_mosaic() = edit.
value != 0;
885 map->set_mosaic_expanded(std::clamp(edit.
index, 0, 3), edit.
value != 0);
890 map->set_area_specific_bg_color(
static_cast<uint16_t
>(edit.
value));
893 static_cast<uint16_t
>(edit.
value));
898 map->set_subscreen_overlay(
static_cast<uint16_t
>(edit.
value));
901 static_cast<uint16_t
>(edit.
value));
908 return absl::OkStatus();
927 return absl::OkStatus();
934 return absl::OkStatus();
955 int parent_id = map->parent();
956 std::vector<int> siblings;
958 switch (map->area_size()) {
960 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
963 siblings = {parent_id, parent_id + 1};
966 siblings = {parent_id, parent_id + 8};
972 for (
int sibling : siblings) {
975 if (sibling == map_index && !include_self) {
980 (*maps_bmp_)[sibling].set_modified(
true);
999 if (overlay_id == 0x0093) {
1000 return "Triforce Room Curtain";
1001 }
else if (overlay_id == 0x0094) {
1002 return "Under the Bridge";
1003 }
else if (overlay_id == 0x0095) {
1004 return "Sky Background (LW Death Mountain)";
1005 }
else if (overlay_id == 0x0096) {
1006 return "Pyramid Background";
1007 }
else if (overlay_id == 0x0097) {
1008 return "First Fog Overlay (Master Sword Area)";
1009 }
else if (overlay_id == 0x009C) {
1010 return "Lava Background (DW Death Mountain)";
1011 }
else if (overlay_id == 0x009D) {
1012 return "Second Fog Overlay (Lost Woods/Skull Woods)";
1013 }
else if (overlay_id == 0x009E) {
1014 return "Tree Canopy (Forest)";
1015 }
else if (overlay_id == 0x009F) {
1016 return "Rain Effect (Misery Mire)";
1017 }
else if (overlay_id == 0x00FF) {
1018 return "No Overlay";
1020 return "Custom overlay";
1026 bool show_overlay_preview) {
1033 uint16_t overlay_id = 0x00FF;
1034 bool has_subscreen_overlay =
false;
1036 bool is_special_overworld_map = (current_map >= 0x80 && current_map < 0xA0);
1038 if (is_special_overworld_map) {
1046 has_subscreen_overlay = (overlay_id != 0x00FF);
1048 if (!has_subscreen_overlay)
1052 int overlay_map_index = -1;
1053 if (overlay_id >= 0x80 && overlay_id < 0xA0) {
1054 overlay_map_index = overlay_id;
1061 const auto& overlay_bitmap = (*maps_bmp_)[overlay_map_index];
1062 if (!overlay_bitmap.is_active() || !overlay_bitmap.texture())
1066 int current_map_x = current_map % 8;
1067 int current_map_y = current_map / 8;
1068 if (current_world == 1) {
1069 current_map_x = (current_map - 0x40) % 8;
1070 current_map_y = (current_map - 0x40) / 8;
1071 }
else if (current_world == 2) {
1072 current_map_x = (current_map - 0x80) % 8;
1073 current_map_y = (current_map - 0x80) / 8;
1081 bool is_background_overlay =
1082 (overlay_id == 0x0095 || overlay_id == 0x0096 || overlay_id == 0x009C);
1085 ImU32 overlay_color =
1086 is_background_overlay ? IM_COL32(255, 255, 255, 128)
1088 IM_COL32(255, 255, 255,
1093 (ImTextureID)(intptr_t)overlay_bitmap.texture(), ImVec2(map_x, map_y),
1096 ImVec2(0, 0), ImVec2(1, 1), ImGui::GetColorU32(overlay_color));
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void set_dirty(bool dirty)
void RefreshMapProperties(int map_id)
void PrepareMapForGraphicsRefresh(int map_index)
absl::Status ApplyPropertyEdit(const OverworldPropertyEdit &edit)
RefreshCallback refresh_overworld_map_
RefreshMapCallback refresh_map_properties_
RefreshMapPaletteCallback refresh_map_palette_
std::function< void()> center_map_callback_
void DrawOverlayEditor(int current_map, bool &show_overlay_editor)
PropertyEditBatchCallback property_edit_batch_callback_
std::function< void(int, bool)> map_selection_callback_
void RefreshOverworldMap()
std::function< std::string(const char *) shortcut_hint_)
ContextViewToggle overlay_toggle_
std::function< void()> zoom_out_callback_
RefreshPaletteCallback refresh_tile16_blockset_
void DrawOverlayPreviewOnMap(int current_map, int current_world, bool show_overlay_preview)
std::function< bool(const OverworldContextTarget &) sample_tile16_callback_)
absl::Status ApplyPropertyEdits(const std::vector< OverworldPropertyEdit > &edits, const std::string &description={})
absl::Status CheckPropertyEditSupported(const OverworldPropertyEdit &edit) const
std::function< void(int)> map_jump_callback_
std::function< void(const std::string &, const OverworldContextTarget &) entity_insert_callback_)
std::function< void(const OverworldContextTarget &) edit_graphics_callback_)
absl::Status RefreshMapPalette(int map_id)
ContextViewToggle grid_toggle_
std::function< void()> zoom_fit_callback_
absl::Status RefreshTile16Blockset()
std::function< void()> zoom_in_callback_
ContextViewToggle entities_toggle_
int CurrentGameState() const
std::function< void(const OverworldContextTarget &) edit_tile16_callback_)
void SetCurrentGameState(int game_state)
void DrawCustomBackgroundColorEditor(int current_map, bool &show_custom_bg_color_editor)
std::array< gfx::Bitmap, zelda3::kExpandedMapCount > * maps_bmp_
absl::StatusOr< int > ReadPropertyValue(const OverworldPropertyEdit &edit) const
std::function< void()> open_map_properties_callback_
void SetupCanvasContextMenu(gui::Canvas &canvas, const OverworldContextTarget &target, bool ¤t_map_lock, int current_mode=0, project::YazeProject *project=nullptr, SharedClipboard *shared_clipboard=nullptr)
absl::Status ApplyPropertyEditDirect(const OverworldPropertyEdit &edit)
zelda3::Overworld * overworld_
void ForceRefreshGraphics(int map_index)
std::function< int()> current_map_provider_
void RefreshSiblingMapGraphics(int map_index, bool include_self=false)
ForceRefreshGraphicsCallback force_refresh_graphics_
std::string GetOverlayDescription(uint16_t overlay_id)
PropertyEditCallback property_edit_callback_
RAII timer for automatic timing management.
constexpr uint16_t snes() const
Get SNES 15-bit color.
Modern, robust canvas for drawing and manipulating graphics.
auto global_scale() const
void ClearContextMenuItems()
void AddContextMenuItem(const gui::CanvasMenuItem &item)
void SetShowBuiltinContextMenu(bool show)
Represents a single Overworld map screen.
const std::array< bool, 4 > & mosaic_expanded() const
static bool SupportsCustomBGColors(OverworldVersion version)
Check if ROM supports custom background colors per area (v2+)
static OverworldVersion GetVersion(const Rom &rom)
Detect ROM version from ASM marker byte.
static bool SupportsAreaEnum(OverworldVersion version)
Check if ROM supports area enum system (v3+ only)
static bool SupportsAnimatedGFX(OverworldVersion version)
Check if ROM supports animated GFX selection (v3+)
static bool SupportsExpandedSpace(OverworldVersion version)
Check if ROM uses expanded ROM space for overworld data.
auto overworld_map(int i) const
auto mutable_overworld_map(int i)
absl::Status ConfigureMultiAreaMap(int parent_index, AreaSizeEnum size)
Configure a multi-area map structure (Large/Wide/Tall)
#define ICON_MD_GRID_VIEW
#define ICON_MD_CENTER_FOCUS_STRONG
#define ICON_MD_ACCOUNT_TREE
#define ICON_MD_FIT_SCREEN
#define ICON_MD_LIGHTBULB
#define ICON_MD_VISIBILITY
#define ICON_MD_DOOR_BACK
#define ICON_MD_CONTENT_PASTE
#define ICON_MD_DOOR_FRONT
#define ICON_MD_HELP_OUTLINE
#define ICON_MD_ADD_LOCATION
#define ICON_MD_PEST_CONTROL_RODENT
#define ICON_MD_CONTENT_COPY
void WriteRomWordIfValid(Rom *rom, int address, uint16_t value)
int EffectivePropertyMapId(zelda3::Overworld *overworld, int map_id)
absl::Status CheckFieldSupported(const Rom *rom, const OverworldPropertyEdit &edit)
OverworldMapMetadataClipboard CaptureMapMetadataClipboard(const zelda3::Overworld &overworld, int map_id)
bool WriteRomByteIfValid(Rom *rom, int address, uint8_t value)
bool IsValidMapId(int map_id)
int MusicRomAddressForMapState(int map_id, int state)
std::vector< OverworldPropertyEdit > BuildOverworldMetadataPasteEdits(int target_map_id, const OverworldMapMetadataClipboard &clipboard, OverworldMapMetadataClipboardScope requested_scope)
bool CanPasteOverworldMapMetadata(const OverworldMapMetadataClipboard &clipboard, OverworldMapMetadataClipboardScope requested_scope)
constexpr unsigned int kOverworldMapSize
constexpr int kTile16Size
OverworldMapMetadata BuildOverworldMapMetadata(const zelda3::Overworld &overworld, const Rom *rom, const project::YazeProject *project, int map_id, int game_state)
constexpr float kInputFieldSize
EntityType
Type of entity that can be selected in the dungeon editor.
std::string DescribeOverworldMapMetadataClipboard(const OverworldMapMetadataClipboard &clipboard)
bool InputHexWord(const char *label, uint16_t *data, float input_width, bool no_step)
ImVec4 ConvertSnesColorToImVec4(const gfx::SnesColor &color)
Convert SnesColor to standard ImVec4 for display.
gfx::SnesColor ConvertImVec4ToSnesColor(const ImVec4 &color)
Convert standard ImVec4 to SnesColor.
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kSpecialWorldMapIdStart
constexpr int kNumOverworldMaps
constexpr int kOverworldMusicBeginning
AreaSizeEnum
Area size enumeration for v3+ ROMs.
constexpr int kOverworldMusicDarkWorld
constexpr int kOverworldMusicAgahnim
@ kVanilla
0xFF in ROM, no ZScream ASM applied
constexpr int kOverworldMusicMasterSword
constexpr int kOverworldMusicZelda
constexpr int kDarkWorldMapIdStart
constexpr int OverworldCustomSubscreenOverlayEnabled
constexpr int OverworldCustomAreaSpecificBGPalette
constexpr int OverworldCustomSubscreenOverlayArray
#define RETURN_IF_ERROR(expr)
SNES color in 15-bit RGB format (BGR555)
A View-menu toggle that mirrors a toolbar toggle.
bool valid(int map_count=zelda3::kNumOverworldMaps) const
OverworldPropertyField field
bool has_overworld_map_metadata
OverworldMapMetadataClipboard overworld_map_metadata
Modern project structure with comprehensive settings consolidation.
struct snes_color snes_color
SNES color in 15-bit RGB format (BGR555)