4#define IM_PI 3.14159265358979323846f
11#include <unordered_map>
14#include "absl/status/status.h"
15#include "absl/strings/str_format.h"
37#include "imgui/imgui.h"
38#include "imgui_memory_editor.h"
54 card_manager.RegisterCard({
55 .card_id =
"overworld.tile16_selector",
56 .display_name =
"Tile16 Selector",
58 .category =
"Overworld",
59 .shortcut_hint =
"Ctrl+Alt+1",
64 card_manager.RegisterCard({
65 .card_id =
"overworld.tile8_selector",
66 .display_name =
"Tile8 Selector",
68 .category =
"Overworld",
69 .shortcut_hint =
"Ctrl+Alt+2",
74 card_manager.RegisterCard({
75 .card_id =
"overworld.area_graphics",
76 .display_name =
"Area Graphics",
78 .category =
"Overworld",
79 .shortcut_hint =
"Ctrl+Alt+3",
84 card_manager.RegisterCard({
85 .card_id =
"overworld.scratch",
86 .display_name =
"Scratch Workspace",
88 .category =
"Overworld",
89 .shortcut_hint =
"Ctrl+Alt+4",
94 card_manager.RegisterCard({
95 .card_id =
"overworld.gfx_groups",
96 .display_name =
"GFX Groups",
98 .category =
"Overworld",
99 .shortcut_hint =
"Ctrl+Alt+5",
104 card_manager.RegisterCard({
105 .card_id =
"overworld.usage_stats",
106 .display_name =
"Usage Statistics",
108 .category =
"Overworld",
109 .shortcut_hint =
"Ctrl+Alt+6",
114 card_manager.RegisterCard({
115 .card_id =
"overworld.v3_settings",
116 .display_name =
"v3 Settings",
118 .category =
"Overworld",
119 .shortcut_hint =
"Ctrl+Alt+7",
154 LOG_DEBUG(
"OverworldEditor",
"Loading overworld.");
156 return absl::FailedPreconditionError(
"ROM not loaded");
176 LOG_DEBUG(
"OverworldEditor",
"Overworld editor refreshed after Tile16 changes");
177 return absl::OkStatus();
182 return absl::OkStatus();
211 static bool cards_configured =
false;
212 if (!cards_configured) {
238 cards_configured =
true;
286 tile16_editor_card.
End();
298 gfx_groups_card.
End();
310 usage_stats_card.
End();
315 ImGui::SetNextWindowSize(ImVec2(650, 750), ImGuiCond_FirstUseEver);
326 ImGui::SetNextWindowSize(ImVec2(400, 500), ImGuiCond_FirstUseEver);
337 ImGui::SetNextWindowSize(ImVec2(500, 450), ImGuiCond_FirstUseEver);
352 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
353 if (hovered_entity) {
355 switch (hovered_entity->entity_type_) {
358 ImGui::OpenPopup(
"Exit editor");
363 ImGui::OpenPopup(
"Entrance Editor");
367 ImGui::OpenPopup(
"Item editor");
371 ImGui::OpenPopup(
"Sprite editor");
380 if (hovered_entity && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
384 }
else if (hovered_entity->entity_type_ ==
428 static bool use_work_area =
true;
429 static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
430 ImGuiWindowFlags_NoMove |
431 ImGuiWindowFlags_NoSavedSettings;
432 const ImGuiViewport* viewport = ImGui::GetMainViewport();
433 ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos);
434 ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size);
472 const char* entity_label =
"";
473 const char* entity_icon =
"";
483 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%s Editing: %s", entity_icon, entity_label);
488 ImGui::OpenPopup(
"UpgradeROMVersion");
498 RefreshMapProperties();
501 maps_bmp_[current_map_].set_modified(true);
502 RefreshChildMapOnDemand(current_map_);
503 RefreshSiblingMapGraphics(current_map_);
506 RefreshTile16Blockset();
515 RefreshSiblingMapGraphics(current_map_);
516 RefreshMapProperties();
517 status_ = RefreshMapPalette();
518 RefreshOverworldMap();
578 if (ImGui::BeginPopupModal(
"UpgradeROMVersion",
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
582 "This will apply the ZSCustomOverworld ASM patch to your ROM,\n"
583 "enabling advanced features like custom tile graphics, animated GFX,\n"
584 "wide/tall areas, and more.");
588 ImGui::Text(
"Current Version: %s",
589 current_version == 0xFF ?
"Vanilla" : absl::StrFormat(
"v%d", current_version).c_str());
591 static int target_version = 3;
592 ImGui::RadioButton(
"v2 (Basic features)", &target_version, 2);
594 ImGui::RadioButton(
"v3 (All features)", &target_version, 3);
598 if (ImGui::Button(
ICON_MD_CHECK " Apply Upgrade", ImVec2(150, 0))) {
604 ImGui::CloseCurrentPopup();
606 LOG_ERROR(
"OverworldEditor",
"Upgrade failed: %s", status.message().data());
611 ImGui::CloseCurrentPopup();
629 if (!ImGui::IsAnyItemActive()) {
633 if (ImGui::IsKeyDown(ImGuiKey_1)) {
635 }
else if (ImGui::IsKeyDown(ImGuiKey_2)) {
640 if (ImGui::IsKeyDown(ImGuiKey_3)) {
643 }
else if (ImGui::IsKeyDown(ImGuiKey_4)) {
646 }
else if (ImGui::IsKeyDown(ImGuiKey_5)) {
649 }
else if (ImGui::IsKeyDown(ImGuiKey_6)) {
652 }
else if (ImGui::IsKeyDown(ImGuiKey_7)) {
655 }
else if (ImGui::IsKeyDown(ImGuiKey_8)) {
661 if (ImGui::IsKeyDown(ImGuiKey_F11)) {
666 if (ImGui::IsKeyDown(ImGuiKey_L) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
671 if (ImGui::IsKeyDown(ImGuiKey_T) && ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) {
680 for (
int i = 0; i < 0x40; i++) {
684 if (world_index < 0 || world_index >=
static_cast<int>(
maps_bmp_.size())) {
704 ImDrawList* draw_list = ImGui::GetWindowDrawList();
706 ImVec2 placeholder_pos =
707 ImVec2(canvas_pos.x + map_x, canvas_pos.y + map_y);
708 ImVec2 placeholder_size =
712 draw_list->AddRectFilled(
714 ImVec2(placeholder_pos.x + placeholder_size.x,
715 placeholder_pos.y + placeholder_size.y),
716 IM_COL32(32, 32, 32, 128));
719 ImVec2 spinner_pos = ImVec2(
720 placeholder_pos.x + placeholder_size.x / 2,
721 placeholder_pos.y + placeholder_size.y / 2
724 const float spinner_radius = 8.0f;
725 const float rotation =
static_cast<float>(ImGui::GetTime()) * 3.0f;
726 const float start_angle = rotation;
727 const float end_angle = rotation +
IM_PI * 1.5f;
729 draw_list->PathArcTo(spinner_pos, spinner_radius, start_angle, end_angle, 12);
730 draw_list->PathStroke(IM_COL32(100, 180, 100, 255), 0, 2.5f);
755 if (current_map_ < 0 || current_map_ >=
static_cast<int>(
maps_bmp_.size())) {
763 "Error: tile16_blockset_ is not properly initialized (active: %s, "
777 int mouse_x = mouse_position.x;
778 int mouse_y = mouse_position.y;
784 auto& selected_world =
789 int index_x = superX * 32 + tile16_x;
790 int index_y = superY * 32 + tile16_y;
796 const ImVec2& click_position,
const std::vector<uint8_t>& tile_data) {
799 if (current_map_ < 0 || current_map_ >=
static_cast<int>(
maps_bmp_.size())) {
801 "ERROR: RenderUpdatedMapBitmap - Invalid current_map_ %d "
802 "(maps_bmp_.size()=%zu)",
814 ImVec2 start_position;
815 start_position.x =
static_cast<float>(tile_index_x *
kTile16Size);
816 start_position.y =
static_cast<float>(tile_index_y *
kTile16Size);
822 if (!current_bitmap.
is_active() || current_bitmap.
size() == 0) {
824 "ERROR: RenderUpdatedMapBitmap - Bitmap %d is not active or has no "
825 "data (active=%s, size=%zu)",
827 current_bitmap.
size());
837 if (pixel_index < 0 ||
838 pixel_index >=
static_cast<int>(current_bitmap.
size())) {
840 "ERROR: RenderUpdatedMapBitmap - pixel_index %d out of bounds "
842 pixel_index, current_bitmap.
size());
848 if (tile_data_index < 0 ||
849 tile_data_index >=
static_cast<int>(tile_data.size())) {
851 "ERROR: RenderUpdatedMapBitmap - tile_data_index %d out of bounds "
852 "(tile_data size=%zu)",
853 tile_data_index, tile_data.size());
857 current_bitmap.
WriteToPixel(pixel_index, tile_data[tile_data_index]);
869 LOG_DEBUG(
"OverworldEditor",
"CheckForOverworldEdits: Frame %d",
870 ImGui::GetFrameCount());
884 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) ||
885 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
886 LOG_DEBUG(
"OverworldEditor",
"CheckForOverworldEdits: About to apply rectangle selection");
888 auto& selected_world =
904 std::swap(start_x, end_x);
906 std::swap(start_y, end_y);
908 constexpr int local_map_size = 512;
910 constexpr int tiles_per_local_map = local_map_size /
kTile16Size;
913 "CheckForOverworldEdits: About to fill rectangle with "
914 "current_tile16_=%d",
925 int local_map_x = x / local_map_size;
926 int local_map_y = y / local_map_size;
933 int index_x = local_map_x * tiles_per_local_map + tile16_x;
934 int index_y = local_map_y * tiles_per_local_map + tile16_y;
940 int rect_width = ((end_x - start_x) /
kTile16Size) + 1;
941 int rect_height = ((end_y - start_y) /
kTile16Size) + 1;
945 int start_local_map_x = start_x / local_map_size;
946 int start_local_map_y = start_y / local_map_size;
947 int end_local_map_x = end_x / local_map_size;
948 int end_local_map_y = end_y / local_map_size;
950 bool in_same_local_map = (start_local_map_x == end_local_map_x) && (start_local_map_y == end_local_map_y);
952 if (in_same_local_map &&
953 index_x >= 0 && (index_x + rect_width - 1) < 0x200 &&
954 index_y >= 0 && (index_y + rect_height - 1) < 0x200) {
955 selected_world[index_x][index_y] = tile16_id;
958 ImVec2 tile_position(x, y);
960 if (!tile_data.empty()) {
963 "CheckForOverworldEdits: Updated bitmap at position (%d,%d) "
967 LOG_ERROR(
"OverworldEditor",
"ERROR: Failed to get tile data for tile16_id=%d",
980 "CheckForOverworldEdits: Rectangle selection applied and cleared");
1019 return absl::FailedPreconditionError(
"No editor context");
1023 std::vector<int> ids;
1027 static_cast<int>(std::floor(std::min(start.x, end.x) / 16.0f));
1029 static_cast<int>(std::floor(std::max(start.x, end.x) / 16.0f));
1031 static_cast<int>(std::floor(std::min(start.y, end.y) / 16.0f));
1033 static_cast<int>(std::floor(std::max(start.y, end.y) / 16.0f));
1034 const int width = end_x - start_x + 1;
1035 const int height = end_y - start_y + 1;
1036 ids.reserve(width * height);
1039 for (
int y = start_y; y <= end_y; ++y) {
1040 for (
int x = start_x; x <= end_x; ++x) {
1049 return absl::OkStatus();
1057 return absl::OkStatus();
1059 return absl::FailedPreconditionError(
"Nothing selected to copy");
1064 return absl::FailedPreconditionError(
"No editor context");
1066 return absl::FailedPreconditionError(
"Clipboard empty");
1070 return absl::FailedPreconditionError(
"No paste target");
1077 const int tile16_x =
1079 const int tile16_y =
1082 auto& selected_world =
1089 const int tiles_per_local_map = 512 /
kTile16Size;
1096 if (width * height !=
static_cast<int>(ids.size())) {
1097 return absl::InternalError(
"Clipboard dimensions mismatch");
1100 for (
int dy = 0; dy < height; ++dy) {
1101 for (
int dx = 0; dx < width; ++dx) {
1102 const int id = ids[dy * width + dx];
1103 const int gx = tile16_x + dx;
1104 const int gy = tile16_y + dy;
1106 const int global_x = superX * 32 + gx;
1107 const int global_y = superY * 32 + gy;
1108 if (global_x < 0 || global_x >= 256 || global_y < 0 || global_y >= 256)
1110 selected_world[global_x][global_y] = id;
1115 return absl::OkStatus();
1123 const int large_map_size = 1024;
1131 int hovered_map = map_x + map_y * 8;
1133 hovered_map += 0x40;
1135 hovered_map += 0x80;
1151 bool use_v3_area_sizes = (asm_version >= 3);
1154 if (use_v3_area_sizes) {
1157 const int highlight_parent =
1165 parent_map_x = highlight_parent % 8;
1166 parent_map_y = highlight_parent / 8;
1169 parent_map_x = (highlight_parent - 0x40) % 8;
1170 parent_map_y = (highlight_parent - 0x40) / 8;
1173 parent_map_x = (highlight_parent - 0x80) % 8;
1174 parent_map_y = (highlight_parent - 0x80) / 8;
1178 switch (area_size) {
1179 case AreaSizeEnum::LargeArea:
1183 large_map_size, large_map_size);
1185 case AreaSizeEnum::WideArea:
1191 case AreaSizeEnum::TallArea:
1197 case AreaSizeEnum::SmallArea:
1209 const int highlight_parent =
1220 parent_map_x = highlight_parent % 8;
1221 parent_map_y = highlight_parent / 8;
1224 parent_map_x = (highlight_parent - 0x40) % 8;
1225 parent_map_y = (highlight_parent - 0x40) / 8;
1228 parent_map_x = (highlight_parent - 0x80) % 8;
1229 parent_map_y = (highlight_parent - 0x80) / 8;
1234 large_map_size, large_map_size);
1241 current_map_x = current_highlighted_map % 8;
1242 current_map_y = current_highlighted_map / 8;
1245 current_map_x = (current_highlighted_map - 0x40) % 8;
1246 current_map_y = (current_highlighted_map - 0x40) / 8;
1250 current_map_x = (current_highlighted_map - 0x80) % 8;
1251 current_map_y = (current_highlighted_map - 0x80) / 8;
1278 if (ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
1283 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
1287 return absl::OkStatus();
1297 constexpr float kWorldSize = 512.0f * 8.0f;
1298 return ImVec2(kWorldSize * scale, kWorldSize * scale);
1304 float max_scroll_x = std::max(0.0f, content_size.x - visible_size.x);
1305 float max_scroll_y = std::max(0.0f, content_size.y - visible_size.y);
1309 float clamped_x = std::clamp(scroll.x, -max_scroll_x, 0.0f);
1310 float clamped_y = std::clamp(scroll.y, -max_scroll_y, 0.0f);
1312 return ImVec2(clamped_x, clamped_y);
1319 if (ImGui::IsMouseDragging(ImGuiMouseButton_Middle) && ImGui::IsItemHovered()) {
1323 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
1325 ImVec2 new_scroll = ImVec2(
1326 current_scroll.x + mouse_delta.x,
1327 current_scroll.y + mouse_delta.y
1333 new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size);
1346 if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
1350 const ImGuiIO& io = ImGui::GetIO();
1353 if (io.MouseWheel != 0.0f && io.KeyCtrl) {
1355 float zoom_delta = io.MouseWheel * 0.1f;
1356 float new_scale = current_scale + zoom_delta;
1359 new_scale = std::clamp(new_scale, 0.25f, 2.0f);
1361 if (new_scale != current_scale) {
1363 ImVec2 mouse_pos_canvas = ImVec2(
1370 ImVec2 content_pos_before = ImVec2(
1371 (mouse_pos_canvas.x - scroll.x) / current_scale,
1372 (mouse_pos_canvas.y - scroll.y) / current_scale
1379 ImVec2 new_scroll = ImVec2(
1380 mouse_pos_canvas.x - (content_pos_before.x * new_scale),
1381 mouse_pos_canvas.y - (content_pos_before.y * new_scale)
1385 ImVec2 content_size = CalculateOverworldContentSize(new_scale);
1387 new_scroll = ClampScrollPosition(new_scroll, content_size, visible_size);
1401 ImVec2 content_size = CalculateOverworldContentSize(scale);
1405 ImVec2 centered_scroll = ImVec2(
1406 -(content_size.x - visible_size.x) / 2.0f,
1407 -(content_size.y - visible_size.y) / 2.0f
1489 ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
1500 ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
1501 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
1502 ImVec2 mouse_delta = ImGui::GetIO().MouseDelta;
1535 ImGui::BeginGroup();
1549 "OwBlocksetSelector", selector_config);
1559 if (result.selection_changed) {
1573 if (result.tile_double_clicked) {
1579 return absl::OkStatus();
1588 int offset = 0x40 * (key + 1);
1593 auto texture = value.texture();
1595 (ImTextureID)(intptr_t)texture,
1622 ImGui::BeginGroup();
1639 return absl::OkStatus();
1666 return absl::OkStatus();
1672 LOG_DEBUG(
"OverworldEditor",
"Loading overworld.");
1680 LOG_DEBUG(
"OverworldEditor",
"Loading overworld graphics (optimized).");
1692 LOG_DEBUG(
"OverworldEditor",
"Loading overworld tileset (deferred textures).");
1704 LOG_DEBUG(
"OverworldEditor",
"Loading overworld tile16 graphics.");
1721 constexpr int kEssentialMapsPerWorld = 8;
1722 constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
1723 constexpr int kDarkWorldEssential =
1725 constexpr int kSpecialWorldEssential =
1729 "Creating bitmaps for essential maps only (first %d maps per world)",
1730 kEssentialMapsPerWorld);
1732 std::vector<gfx::Bitmap*> maps_to_texture;
1733 maps_to_texture.reserve(kEssentialMapsPerWorld *
1739 bool is_essential =
false;
1742 if (i < kLightWorldEssential) {
1743 is_essential =
true;
1745 is_essential =
true;
1747 i < kSpecialWorldEssential) {
1748 is_essential =
true;
1759 maps_to_texture.push_back(&
maps_bmp_[i]);
1760 }
catch (
const std::bad_alloc& e) {
1761 std::cout <<
"Error allocating map " << i <<
": " << e.what()
1772 const int initial_texture_count =
1773 std::min(4,
static_cast<int>(maps_to_texture.size()));
1776 for (
int i = 0; i < initial_texture_count; ++i) {
1785 for (
size_t i = initial_texture_count; i < maps_to_texture.size(); ++i) {
1789 if (&
maps_bmp_[j] == maps_to_texture[i]) {
1796 if (map_index >= 0) {
1797 int map_world = map_index / 0x40;
1814 return absl::OkStatus();
1819 const int depth = 0x10;
1820 for (
int i = 0; i < 3; i++)
1822 int width = sprite.width();
1823 int height = sprite.height();
1824 if (width == 0 || height == 0) {
1831 *sprite.preview_graphics());
1836 return absl::OkStatus();
1846 int refresh_count = 0;
1847 const int max_refreshes_per_frame = 2;
1855 if (is_current_map || is_current_world) {
1871 LOG_ERROR(
"OverworldEditor",
"Failed to build map %d: %s", map_index,
1879 if (!bitmap.is_active()) {
1885 bitmap.SetPalette(palette);
1886 }
catch (
const std::bad_alloc& e) {
1887 LOG_ERROR(
"OverworldEditor",
"Error allocating bitmap for map %d: %s",
1888 map_index, e.what());
1893 if (!bitmap.texture() && bitmap.is_active()) {
1911 maps_bmp_[map_index].set_modified(
true);
1937 if (!is_current_map && !is_current_world) {
1939 maps_bmp_[map_index].set_modified(
true);
1954 bool needs_graphics_rebuild =
maps_bmp_[map_index].modified();
1955 bool needs_palette_rebuild =
false;
1957 if (needs_graphics_rebuild) {
1959 map->LoadAreaGraphics();
1962 auto status = map->BuildTileset();
1964 LOG_ERROR(
"OverworldEditor",
"Failed to build tileset for map %d: %s",
1965 map_index, status.message().data());
1973 LOG_ERROR(
"OverworldEditor",
"Failed to build tiles16 graphics for map %d: %s",
1974 map_index, status.message().data());
1981 LOG_ERROR(
"OverworldEditor",
"Failed to build bitmap for map %d: %s",
1982 map_index, status.message().data());
1987 maps_bmp_[map_index].set_data(map->bitmap_data());
1988 maps_bmp_[map_index].set_modified(
false);
1991 if (!
maps_bmp_[map_index].ValidateDataSurfaceSync()) {
1992 LOG_WARN(
"OverworldEditor",
"Warning: Surface synchronization issue detected for map %d",
2009 bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
2011 if (use_v3_area_sizes) {
2016 if (map->is_large_map()) {
2034 static std::set<int> currently_processing;
2035 if (currently_processing.count(map_index)) {
2040 if (area_size == AreaSizeEnum::SmallArea) {
2045 "RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)",
2046 (area_size == AreaSizeEnum::LargeArea) ?
"large"
2047 : (area_size == AreaSizeEnum::WideArea) ?
"wide"
2049 map_index, map->
parent());
2052 std::vector<int> sibling_maps;
2053 int parent_id = map->
parent();
2056 switch (area_size) {
2057 case AreaSizeEnum::LargeArea: {
2061 sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
2063 "RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
2064 parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
2068 case AreaSizeEnum::WideArea: {
2071 sibling_maps = {parent_id, parent_id + 1};
2073 "RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d",
2074 parent_id, parent_id + 1);
2078 case AreaSizeEnum::TallArea: {
2081 sibling_maps = {parent_id, parent_id + 8};
2083 "RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d",
2084 parent_id, parent_id + 8);
2090 "RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
2091 static_cast<int>(area_size), map_index);
2096 for (
int sibling : sibling_maps) {
2097 currently_processing.insert(sibling);
2101 for (
int sibling : sibling_maps) {
2102 if (sibling == map_index) {
2114 bool needs_refresh =
maps_bmp_[sibling].modified();
2116 if ((is_current_map || is_current_world) && needs_refresh) {
2118 "RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d "
2120 (area_size == AreaSizeEnum::LargeArea) ?
"large"
2121 : (area_size == AreaSizeEnum::WideArea) ?
"wide"
2123 sibling, parent_id);
2127 if (sibling_map &&
maps_bmp_[sibling].modified()) {
2128 sibling_map->LoadAreaGraphics();
2130 auto status = sibling_map->BuildTileset();
2136 status = sibling_map->LoadPalette();
2138 status = sibling_map->BuildBitmap(
2141 maps_bmp_[sibling].set_data(sibling_map->bitmap_data());
2163 "RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: "
2165 sibling, status.message().data());
2168 }
else if (!is_current_map && !is_current_world) {
2175 for (
int sibling : sibling_maps) {
2176 currently_processing.erase(sibling);
2187 bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
2189 if (use_v3_area_sizes) {
2194 if (area_size != AreaSizeEnum::SmallArea) {
2196 std::vector<int> sibling_maps;
2199 switch (area_size) {
2200 case AreaSizeEnum::LargeArea:
2202 sibling_maps = {parent_id, parent_id + 1, parent_id + 8,
2205 case AreaSizeEnum::WideArea:
2207 sibling_maps = {parent_id, parent_id + 1};
2209 case AreaSizeEnum::TallArea:
2211 sibling_maps = {parent_id, parent_id + 8};
2218 for (
int sibling_index : sibling_maps) {
2224 maps_bmp_[sibling_index].SetPalette(current_map_palette);
2234 for (
int i = 1; i < 4; i++) {
2244 maps_bmp_[sibling_index].SetPalette(current_map_palette);
2255 return absl::OkStatus();
2260 if (map_index >= 0 && map_index <
static_cast<int>(
maps_bmp_.size())) {
2261 maps_bmp_[map_index].set_modified(
true);
2266 LOG_DEBUG(
"OverworldEditor",
"ForceRefreshGraphics: Map %d marked for refresh", map_index);
2271 if (map_index < 0 || map_index >=
static_cast<int>(
maps_bmp_.size())) {
2280 int parent_id = map->parent();
2281 std::vector<int> siblings;
2283 switch (map->area_size()) {
2285 siblings = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
2288 siblings = {parent_id, parent_id + 1};
2291 siblings = {parent_id, parent_id + 8};
2297 for (
int sibling : siblings) {
2298 if (sibling >= 0 && sibling < 0xA0) {
2300 if (sibling == map_index && !include_self) {
2314 LOG_DEBUG(
"OverworldEditor",
"RefreshSiblingMapGraphics: Refreshed sibling map %d", sibling);
2324 bool use_v3_area_sizes = (asm_version >= 3);
2326 if (use_v3_area_sizes) {
2329 auto area_size = current_ow_map.area_size();
2331 if (area_size != AreaSizeEnum::SmallArea) {
2333 std::vector<int> sibling_maps;
2334 int parent_id = current_ow_map.parent();
2336 switch (area_size) {
2337 case AreaSizeEnum::LargeArea:
2339 sibling_maps = {parent_id + 1, parent_id + 8, parent_id + 9};
2341 case AreaSizeEnum::WideArea:
2343 sibling_maps = {parent_id + 1};
2345 case AreaSizeEnum::TallArea:
2347 sibling_maps = {parent_id + 8};
2354 for (
int sibling_index : sibling_maps) {
2359 map.set_area_graphics(current_ow_map.area_graphics());
2360 map.set_area_palette(current_ow_map.area_palette());
2365 map.set_message_id(current_ow_map.message_id());
2368 map.LoadAreaGraphics();
2373 if (current_ow_map.is_large_map()) {
2375 for (
int i = 1; i < 4; i++) {
2376 int sibling_index = current_ow_map.parent() + i;
2381 map.set_area_graphics(current_ow_map.area_graphics());
2382 map.set_area_palette(current_ow_map.area_palette());
2387 map.set_message_id(current_ow_map.message_id());
2390 map.LoadAreaGraphics();
2397 LOG_DEBUG(
"OverworldEditor",
"RefreshTile16Blockset called");
2400 return absl::OkStatus();
2422 return absl::OkStatus();
2427 if (ImGui::IsMouseClicked(ImGuiMouseButton_Middle) &&
2428 ImGui::IsItemHovered()) {
2433 int hovered_map = map_x + map_y * 8;
2435 hovered_map += 0x40;
2437 hovered_map += 0x80;
2441 if (hovered_map >= 0 && hovered_map < 0xA0) {
2454 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) &&
2455 ImGui::IsItemHovered()) {
2483 static bool init_properties =
false;
2485 if (!init_properties) {
2486 for (
int i = 0; i < 0x40; i++) {
2487 std::string area_graphics_str = absl::StrFormat(
2490 ->push_back(area_graphics_str);
2492 area_graphics_str = absl::StrFormat(
2495 ->push_back(area_graphics_str);
2497 std::string area_palette_str =
2500 ->push_back(area_palette_str);
2502 area_palette_str = absl::StrFormat(
2505 ->push_back(area_palette_str);
2506 std::string sprite_gfx_str = absl::StrFormat(
2509 ->push_back(sprite_gfx_str);
2511 sprite_gfx_str = absl::StrFormat(
2514 ->push_back(sprite_gfx_str);
2516 sprite_gfx_str = absl::StrFormat(
2519 ->push_back(sprite_gfx_str);
2521 sprite_gfx_str = absl::StrFormat(
2524 ->push_back(sprite_gfx_str);
2526 std::string sprite_palette_str = absl::StrFormat(
2529 ->push_back(sprite_palette_str);
2531 sprite_palette_str = absl::StrFormat(
2534 ->push_back(sprite_palette_str);
2536 sprite_palette_str = absl::StrFormat(
2539 ->push_back(sprite_palette_str);
2541 sprite_palette_str = absl::StrFormat(
2544 ->push_back(sprite_palette_str);
2546 init_properties =
true;
2549 Text(
"Area Gfx LW/DW");
2557 Text(
"Sprite Gfx LW/DW");
2571 Text(
"Area Pal LW/DW");
2578 static bool show_gfx_group =
false;
2579 Checkbox(
"Show Gfx Group Editor", &show_gfx_group);
2580 if (show_gfx_group) {
2588 if (BeginTable(
"UsageStatsTable", 3,
2589 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter,
2591 TableSetupColumn(
"Entrances");
2592 TableSetupColumn(
"Grid", ImGuiTableColumnFlags_WidthStretch,
2593 ImGui::GetContentRegionAvail().x);
2594 TableSetupColumn(
"Usage", ImGuiTableColumnFlags_WidthFixed, 256);
2599 if (BeginChild(
"UnusedSpritesetScroll", ImVec2(0, 0),
true,
2600 ImGuiWindowFlags_HorizontalScrollbar)) {
2601 for (
int i = 0; i < 0x81; i++) {
2605 std::string str = absl::StrFormat(
"%#x - %s", i, entrance_name);
2608 ? ImGuiSelectableFlags_Disabled
2614 if (IsItemHovered()) {
2616 Text(
"Entrance ID: %d", i);
2637 return absl::OkStatus();
2642 int total_squares = 128;
2643 int squares_wide = 8;
2644 int squares_tall = (total_squares + squares_wide - 1) /
2648 for (
int row = 0; row < squares_tall; ++row) {
2651 for (
int col = 0; col < squares_wide; ++col) {
2652 if (row * squares_wide + col >= total_squares) {
2664 if (Button(
"##square", ImVec2(20, 20))) {
2676 if (IsItemHovered()) {
2691 Text(
"Current Tile16 Drawn Position (Relative): %d, %d", relative_x,
2695 Text(
"Light World Map Tiles: %d",
2697 Text(
"Dark World Map Tiles: %d",
2699 Text(
"Special World Map Tiles: %d",
2702 static bool view_lw_map_tiles =
false;
2703 static MemoryEditor mem_edit;
2705 if (Button(
"View Light World Map Tiles")) {
2706 view_lw_map_tiles = !view_lw_map_tiles;
2709 if (view_lw_map_tiles) {
2710 mem_edit.DrawContents(
2721 return absl::OkStatus();
2729 if (target_version < 2 || target_version > 3) {
2730 return absl::InvalidArgumentError(absl::StrFormat(
2731 "Invalid target version: %d. Must be 2 or 3.", target_version));
2736 if (current_version != 0xFF && current_version >= target_version) {
2737 return absl::AlreadyExistsError(absl::StrFormat(
2738 "ROM is already version %d or higher", current_version));
2741 LOG_DEBUG(
"OverworldEditor",
"Applying ZSCustomOverworld ASM v%d to ROM...",
2745 auto asar_wrapper = std::make_unique<core::AsarWrapper>();
2749 std::vector<uint8_t> original_rom_data =
rom_->
vector();
2750 std::vector<uint8_t> working_rom_data = original_rom_data;
2754 std::string asm_file_name = (target_version == 3)
2756 :
"asm/ZSCustomOverworld.asm";
2761 LOG_DEBUG(
"OverworldEditor",
"Using ASM file: %s", asm_file_path.c_str());
2764 if (!std::filesystem::exists(asm_file_path)) {
2765 return absl::NotFoundError(absl::StrFormat(
2766 "ASM file not found at: %s\n\n"
2767 "Expected location: assets/%s\n"
2768 "Make sure the assets directory is accessible.",
2769 asm_file_path, asm_file_name));
2774 asar_wrapper->ApplyPatch(asm_file_path, working_rom_data);
2775 if (!patch_result.ok()) {
2776 return absl::InternalError(absl::StrFormat(
2777 "Failed to apply ASM patch: %s", patch_result.status().message()));
2780 const auto& result = patch_result.value();
2781 if (!result.success) {
2782 std::string error_details =
"ASM patch failed with errors:\n";
2783 for (
const auto& error : result.errors) {
2784 error_details +=
" - " + error +
"\n";
2786 if (!result.warnings.empty()) {
2787 error_details +=
"Warnings:\n";
2788 for (
const auto& warning : result.warnings) {
2789 error_details +=
" - " + warning +
"\n";
2792 return absl::InternalError(error_details);
2802 LOG_DEBUG(
"OverworldEditor",
"ASM patch applied successfully. Found %zu symbols:",
2803 result.symbols.size());
2804 for (
const auto& symbol : result.symbols) {
2805 LOG_DEBUG(
"OverworldEditor",
" %s @ $%06X", symbol.name.c_str(),
2812 LOG_DEBUG(
"OverworldEditor",
"ZSCustomOverworld v%d successfully applied to ROM",
2814 return absl::OkStatus();
2816 }
catch (
const std::exception& e) {
2819 if (!restore_result.ok()) {
2820 LOG_ERROR(
"OverworldEditor",
"Failed to restore ROM data: %s",
2821 restore_result.message().data());
2823 return absl::InternalError(
2824 absl::StrFormat(
"Exception during ASM application: %s", e.what()));
2831 static_cast<uint8_t
>(target_version);
2834 if (target_version >= 2) {
2839 LOG_DEBUG(
"OverworldEditor",
"Enabled v2+ features: Custom BG colors, Main palettes");
2842 if (target_version >= 3) {
2850 "Enabled v3+ features: Subscreen overlays, Animated GFX, Tile GFX "
2854 for (
int i = 0; i < 0xA0; i++) {
2860 const std::vector<int> large_areas = {
2861 0x00, 0x02, 0x05, 0x07, 0x0A, 0x0B, 0x0F, 0x10, 0x11, 0x12,
2862 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1D,
2863 0x1E, 0x25, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x30,
2864 0x32, 0x33, 0x34, 0x35, 0x37, 0x3A, 0x3B, 0x3C, 0x3F};
2866 for (
int area_id : large_areas) {
2867 if (area_id < 0xA0) {
2873 LOG_DEBUG(
"OverworldEditor",
"Initialized area size data for %zu areas",
2874 large_areas.size());
2877 LOG_DEBUG(
"OverworldEditor",
"ROM version markers updated to v%d", target_version);
2878 return absl::OkStatus();
2903 api->SetTileQueryCallback([
this](
int x,
int y) {
2914 if (x < 0 || y < 0 || x >= 512 || y >= 512) {
2927 if (!tile_data.empty()) {
2929 static_cast<float>(y * 16)), tile_data);
2942 if (x < 0 || y < 0 || x >= 512 || y >= 512) {
absl::Status LoadFromData(const std::vector< uint8_t > &data, bool z3_load=true)
void set_dirty(bool dirty)
core::ResourceLabelManager * resource_label()
std::string MakeCardTitle(const std::string &base_title) const
absl::Status Clear() override
std::unique_ptr< MapPropertiesSystem > map_properties_system_
zelda3::OverworldItem current_item_
void HandleMapInteraction()
bool overworld_canvas_fullscreen_
bool map_blockset_loaded_
absl::Status UpdateUsageStats()
absl::Status DrawScratchSpace()
zelda3::OverworldEntranceTileTypes entrance_tiletypes_
zelda3::OverworldEntrance current_entrance_
void CenterOverworldView()
absl::Status ApplyZSCustomOverworldASM(int target_version)
Apply ZSCustomOverworld ASM patch to upgrade ROM version.
zelda3::Sprite current_sprite_
absl::Status CheckForCurrentMap()
Check for changes to the overworld map. Calls RefreshOverworldMap and RefreshTile16Blockset on the cu...
void DrawOverworldEdits()
void ForceRefreshGraphics(int map_index)
std::vector< int > selected_tile16_ids_
gfx::Bitmap current_gfx_bmp_
gfx::Tilemap tile16_blockset_
bool show_tile8_selector_
void ResetOverworldView()
bool middle_mouse_dragging_
Tile16Editor tile16_editor_
gui::Canvas ow_map_canvas_
zelda3::GameEntity * dragged_entity_
bool show_tile16_selector_
std::array< gfx::Bitmap, zelda3::kNumOverworldMaps > maps_bmp_
bool show_map_properties_panel_
void RefreshOverworldMap()
void CheckForOverworldEdits()
Check for changes to the overworld map.
absl::Status UpdateROMVersionMarkers(int target_version)
Update ROM version markers and feature flags after ASM patching.
zelda3::OverworldExit current_exit_
void RefreshMapProperties()
void RefreshSiblingMapGraphics(int map_index, bool include_self=false)
void RenderUpdatedMapBitmap(const ImVec2 &click_position, const std::vector< uint8_t > &tile_data)
void RefreshOverworldMapOnDemand(int map_index)
On-demand map refresh that only updates what's actually needed.
gui::Canvas current_gfx_canvas_
void Initialize() override
bool show_overlay_editor_
void SetupCanvasAutomation()
void HandleOverworldPan()
void DrawFullscreenCanvas()
bool dragged_entity_free_movement_
void DrawOverworldProperties()
bool AutomationSetTile(int x, int y, int tile_id)
absl::Status RefreshMapPalette()
void RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap *map)
Safely refresh multi-area maps without recursion.
void DrawOverworldCanvas()
gui::Canvas blockset_canvas_
absl::Status RefreshTile16Blockset()
zelda3::Overworld & overworld()
void UpdateBlocksetSelectorState()
void CheckForSelectRectangle()
Draw and create the tile16 IDs that are currently selected.
void RefreshChildMap(int map_index)
std::unique_ptr< OverworldEntityRenderer > entity_renderer_
absl::Status Load() override
void EnsureMapTexture(int map_index)
Ensure a specific map has its texture created.
std::vector< gfx::Bitmap > sprite_previews_
absl::Status Copy() override
absl::Status DrawAreaGraphics()
EntityEditMode entity_edit_mode_
GfxGroupEditor gfx_group_editor_
void ProcessDeferredTextures()
Create textures for deferred map bitmaps on demand.
absl::Status Update() final
std::unique_ptr< gui::TileSelectorWidget > blockset_selector_
bool show_overlay_preview_
absl::Status Paste() override
void ScrollBlocksetCanvasToCurrentTile()
Scroll the blockset canvas to show the current selected tile16.
gui::Canvas graphics_bin_canvas_
gfx::BitmapTable current_graphics_set_
bool show_custom_bg_color_editor_
zelda3::Overworld overworld_
absl::Status LoadGraphics()
Load the Bitmap objects for each OverworldMap.
void RefreshChildMapOnDemand(int map_index)
On-demand child map refresh with selective updates.
absl::Status LoadSpriteGraphics()
gfx::IRenderer * renderer_
absl::Status Save() override
void HandleOverworldZoom()
zelda3::GameEntity * current_entity_
gui::Canvas properties_canvas_
int AutomationGetTile(int x, int y)
gfx::SnesPalette palette_
gfx::Bitmap tile16_blockset_bmp_
absl::Status DrawTile16Selector()
absl::Status Initialize(const gfx::Bitmap &tile16_blockset_bmp, const gfx::Bitmap ¤t_gfx_bmp, std::array< uint8_t, 0x200 > &all_tiles_types)
absl::Status SetCurrentTile(int id)
void set_palette(const gfx::SnesPalette &palette)
void set_on_changes_committed(std::function< absl::Status()> callback)
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
void ProcessTextureQueue(IRenderer *renderer)
std::array< gfx::Bitmap, 223 > & gfx_sheets()
Get reference to all graphics sheets.
Represents a bitmap image optimized for SNES ROM hacking.
void WriteToPixel(int position, uint8_t value)
Write a value to a pixel at the given position.
TextureHandle texture() const
const std::vector< uint8_t > & vector() const
void set_modified(bool modified)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap.
SDL_Surface * surface() const
RAII timer for automatic timing management.
void SetTilePaintCallback(TilePaintCallback callback)
void set_scrolling(ImVec2 scroll)
auto selected_tile_pos() const
void DrawBitmap(Bitmap &bitmap, int border_offset, float scale)
auto global_scale() const
auto select_rect_active() const
auto selected_tiles() const
void DrawBitmapGroup(std::vector< int > &group, gfx::Tilemap &tilemap, int tile_size, float scale=1.0f, int local_map_size=0x200, ImVec2 total_map_size=ImVec2(0x1000, 0x1000))
Draw group of bitmaps for multi-tile selection preview.
CanvasAutomationAPI * GetAutomationAPI()
auto hover_mouse_pos() const
void UpdateInfoGrid(ImVec2 bg_size, float grid_size=64.0f, int label_id=0)
auto drawn_tile_position() const
bool DrawTilemapPainter(gfx::Tilemap &tilemap, int current_tile)
bool DrawTileSelector(int size, int size_y=0)
auto mutable_labels(int i)
void set_selected_tile_pos(ImVec2 pos)
void set_global_scale(float scale)
void DrawSelectRect(int current_map, int tile_size=0x10, float scale=1.0f)
bool IsMouseHovering() const
void DrawOutline(int x, int y, int w, int h)
void DrawBackground(ImVec2 canvas_size=ImVec2(0, 0))
const ImVector< ImVec2 > & points() const
void DrawGrid(float grid_step=64.0f, int tile_id_offset=8)
auto selected_points() const
auto set_highlight_tile_id(int i)
static EditorCardManager & Get()
Draggable, dockable card for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
void SetPosition(Position pos)
enum yaze::zelda3::GameEntity::EntityType entity_type_
virtual void UpdateMapProperties(uint16_t map_id)=0
Represents a single Overworld map screen.
auto tile16_blockset_data() const
auto current_area_palette() const
void set_current_world(int world)
int GetTileFromPosition(ImVec2 position) const
absl::Status Load(Rom *rom)
absl::Status SaveMapProperties()
absl::Status SaveMap32Tiles()
absl::Status SaveMap16Tiles()
std::vector< gfx::Tile16 > tiles16() const
absl::Status CreateTile32Tilemap()
auto overworld_map(int i) const
void set_current_map(int i)
auto mutable_overworld_map(int i)
absl::Status SaveEntrances()
absl::Status EnsureMapBuilt(int map_index)
Build a map on-demand if it hasn't been built yet.
uint16_t GetTile(int x, int y) const
absl::Status SaveOverworldMaps()
auto mutable_all_tiles_types()
void SetTile(int x, int y, uint16_t tile_id)
auto mutable_sprites(int state)
const std::vector< OverworldEntrance > & entrances() const
auto current_map_bitmap_data() const
OverworldBlockset & GetMapTiles(int world_type)
A class for managing sprites in the overworld and underworld.
#define ICON_MD_GRID_VIEW
#define ICON_MD_COLLECTIONS
#define ICON_MD_OPEN_IN_FULL
#define ICON_MD_FORMAT_COLOR_FILL
#define ICON_MD_DOOR_BACK
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_DOOR_FRONT
#define ICON_MD_ADD_LOCATION
#define ICON_MD_PEST_CONTROL_RODENT
#define ICON_MD_ANALYTICS
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define PRINT_IF_ERROR(expression)
#define RETURN_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
ImVec2 CalculateOverworldContentSize(float scale)
ImVec2 ClampScrollPosition(ImVec2 scroll, ImVec2 content_size, ImVec2 visible_size)
Editors are the view controllers for the application.
void MoveEntityOnGrid(zelda3::GameEntity *entity, ImVec2 canvas_p0, ImVec2 scrolling, bool free_movement)
constexpr unsigned int kOverworldMapSize
bool DrawSpriteEditorPopup(zelda3::Sprite &sprite)
bool DrawOverworldEntrancePopup(zelda3::OverworldEntrance &entrance)
bool DrawItemEditorPopup(zelda3::OverworldItem &item)
constexpr int kTile16Size
constexpr float kInputFieldSize
bool DrawExitEditorPopup(zelda3::OverworldExit &exit)
void UpdateTilemap(IRenderer *renderer, Tilemap &tilemap, const std::vector< uint8_t > &data)
std::vector< uint8_t > GetTilemapData(Tilemap &tilemap, int tile_id)
Tilemap CreateTilemap(IRenderer *renderer, std::vector< uint8_t > &data, int width, int height, int tile_size, int num_tiles, SnesPalette &palette)
void VerticalSpacing(float pixels)
void BeginChildBothScrollbars(int id)
ImVec4 GetSelectedColor()
void CenterText(const char *text)
void EndWindowWithDisplaySettings()
void BeginWindowWithDisplaySettings(const char *id, bool *active, const ImVec2 &size, ImGuiWindowFlags flags)
void BeginChildWithScrollbar(const char *str_id)
std::string HexByte(uint8_t byte, HexStringParams params)
std::string GetResourcePath(const std::string &resource_path)
constexpr int OverworldCustomTileGFXGroupEnabled
constexpr int OverworldCustomAreaSpecificBGEnabled
constexpr int kOverworldScreenSize
constexpr int kNumTile16Individual
constexpr int kSpecialWorldMapIdStart
constexpr int OverworldCustomAnimatedGFXEnabled
constexpr int OverworldCustomMainPaletteEnabled
constexpr int kNumOverworldMaps
constexpr int OverworldCustomASMHasBeenApplied
constexpr int kDarkWorldMapIdStart
absl::StatusOr< OverworldEntranceTileTypes > LoadEntranceTileTypes(Rom *rom)
constexpr int OverworldCustomMosaicEnabled
constexpr const char * kEntranceNames[]
constexpr int OverworldCustomSubscreenOverlayEnabled
std::string CreateOrGetLabel(const std::string &type, const std::string &key, const std::string &defaultValue)
bool has_overworld_tile16
std::vector< int > overworld_tile16_ids
struct yaze::editor::EditorContext::SharedClipboard shared_clipboard
Bitmap atlas
Master bitmap containing all tiles.