16#include "absl/status/status.h"
17#include "absl/strings/str_format.h"
18#include "absl/types/span.h"
19#include "imgui/imgui.h"
74 std::array<zelda3::Room, 0x128>& rooms) {
76 return absl::FailedPreconditionError(
"ROM not loaded");
79 bool any_dirty =
false;
80 for (
const auto& room : rooms) {
81 if (room.water_fill_dirty()) {
87 return absl::OkStatus();
90 std::vector<zelda3::WaterFillZoneEntry> zones;
92 for (
int room_id = 0; room_id < static_cast<int>(rooms.size()); ++room_id) {
93 auto& room = rooms[room_id];
94 if (!room.has_water_fill_zone()) {
98 const int tile_count = room.WaterFillTileCount();
99 if (tile_count <= 0) {
102 if (tile_count > 255) {
103 return absl::InvalidArgumentError(absl::StrFormat(
104 "Water fill zone in room 0x%02X has %d tiles (max 255)", room_id,
111 z.
fill_offsets.reserve(
static_cast<size_t>(tile_count));
113 const auto& map = room.water_fill_zone().tiles;
114 for (
size_t i = 0; i < map.size(); ++i) {
119 zones.push_back(std::move(z));
122 if (zones.size() > 8) {
123 return absl::InvalidArgumentError(absl::StrFormat(
124 "Too many water fill zones: %zu (max 8 fits in $7EF411 bitfield)",
131 for (
const auto& z : zones) {
132 if (z.room_id >= 0 && z.room_id <
static_cast<int>(rooms.size())) {
133 rooms[z.room_id].set_water_fill_sram_bit_mask(z.sram_bit_mask);
138 for (
auto& room : rooms) {
139 room.ClearWaterFillDirty();
142 return absl::OkStatus();
200 "dungeon.object_editor");
201 panel_manager->RegisterPanelAlias(
"dungeon.entrances",
202 "dungeon.entrance_properties");
206 panel_manager->RegisterPanel(
207 {.card_id =
"dungeon.workbench",
208 .display_name =
"Dungeon Workbench",
209 .window_title =
" Dungeon Workbench",
211 .category =
"Dungeon",
213 .visibility_flag =
nullptr,
216 .disabled_tooltip =
"Load a ROM to edit dungeon rooms"});
218 panel_manager->RegisterPanel(
220 .display_name =
"Room List",
221 .window_title =
" Room List",
223 .category =
"Dungeon",
224 .shortcut_hint =
"Ctrl+Shift+R",
225 .visibility_flag =
nullptr,
228 .disabled_tooltip =
"Load a ROM to browse dungeon rooms"});
230 panel_manager->RegisterPanel(
232 .display_name =
"Entrance List",
233 .window_title =
" Entrance List",
235 .category =
"Dungeon",
236 .shortcut_hint =
"Ctrl+Shift+E",
237 .visibility_flag =
nullptr,
240 .disabled_tooltip =
"Load a ROM to browse dungeon entrances"});
242 panel_manager->RegisterPanel(
243 {.card_id =
"dungeon.entrance_properties",
244 .display_name =
"Entrance Properties",
245 .window_title =
" Entrance Properties",
247 .category =
"Dungeon",
249 .visibility_flag =
nullptr,
252 .disabled_tooltip =
"Load a ROM to edit entrance properties"});
254 panel_manager->RegisterPanel(
256 .display_name =
"Room Matrix",
257 .window_title =
" Room Matrix",
259 .category =
"Dungeon",
260 .shortcut_hint =
"Ctrl+Shift+M",
261 .visibility_flag =
nullptr,
264 .disabled_tooltip =
"Load a ROM to view the room matrix"});
266 panel_manager->RegisterPanel(
268 .display_name =
"Room Graphics",
269 .window_title =
" Room Graphics",
271 .category =
"Dungeon",
272 .shortcut_hint =
"Ctrl+Shift+G",
273 .visibility_flag =
nullptr,
276 .disabled_tooltip =
"Load a ROM to view room graphics"});
278 panel_manager->RegisterPanel(
280 .display_name =
"Palette Editor",
281 .window_title =
" Palette Editor",
283 .category =
"Dungeon",
285 .shortcut_hint =
"Ctrl+Shift+Alt+P",
286 .visibility_flag =
nullptr,
289 .disabled_tooltip =
"Load a ROM to edit dungeon palettes"});
291 panel_manager->RegisterPanel(
292 {.card_id =
"dungeon.room_tags",
293 .display_name =
"Room Tags",
294 .window_title =
" Room Tags",
296 .category =
"Dungeon",
298 .visibility_flag =
nullptr,
301 .disabled_tooltip =
"Load a ROM to view room tags"});
303 panel_manager->RegisterPanel(
304 {.card_id =
"dungeon.dungeon_map",
305 .display_name =
"Dungeon Map",
306 .window_title =
" Dungeon Map",
308 .category =
"Dungeon",
309 .shortcut_hint =
"Ctrl+Shift+D",
310 .visibility_flag =
nullptr,
313 .disabled_tooltip =
"Load a ROM to view the dungeon map"});
328 panel_manager->RegisterEditorPanel(std::make_unique<DungeonRoomSelectorPanel>(
331 panel_manager->RegisterEditorPanel(std::make_unique<DungeonEntranceListPanel>(
336 auto matrix_panel = std::make_unique<DungeonRoomMatrixPanel>(
339 [
this](
int old_room,
int new_room) {
343 matrix_panel->SetRoomIntentCallback(
347 panel_manager->RegisterEditorPanel(std::move(matrix_panel));
351 auto dungeon_map = std::make_unique<DungeonMapPanel>(
354 dungeon_map->SetRoomIntentCallback(
361 panel_manager->RegisterEditorPanel(std::move(dungeon_map));
365 auto workbench = std::make_unique<DungeonWorkbenchPanel>(
371 [
this](
int room_id) {
374 LOG_ERROR(
"DungeonEditorV2",
"Save Room failed: %s",
375 status.message().data());
378 absl::StrFormat(
"Save Room failed: %s", status.message()),
390 [
this]() ->
const std::deque<int>& {
return recent_rooms_; },
391 [
this](
int room_id) {
396 [
this](
const std::string& id) {
ShowPanel(
id); },
402 [
this]() {
Undo().IgnoreError(); }, [
this]() {
Redo().IgnoreError(); },
406 panel_manager->RegisterEditorPanel(std::move(workbench));
409 panel_manager->RegisterEditorPanel(std::make_unique<DungeonEntrancesPanel>(
419 return absl::FailedPreconditionError(
"ROM not loaded");
425 if (!dim_table.IsLoaded()) {
432 return absl::FailedPreconditionError(
"GameData not available");
453 std::make_unique<emu::render::EmulatorRenderService>(
rom_);
456 LOG_ERROR(
"DungeonEditorV2",
"Failed to initialize render service: %s",
457 status.message().data());
471 auto graphics_panel = std::make_unique<DungeonRoomGraphicsPanel>(
485 auto object_editor = std::make_unique<ObjectEditorPanel>(
492 rooms_[current_room_id_].RenderRoomGraphics();
497 object_editor->SetRooms(&
rooms_);
514 object_editor->set_tile_editor_callback([
this](int16_t object_id) {
539 auto collision_panel = std::make_unique<CustomCollisionPanel>(
543 std::move(collision_panel));
545 auto water_fill_panel =
546 std::make_unique<WaterFillPanel>(
nullptr,
nullptr);
549 std::move(water_fill_panel));
553 auto tile_editor_panel =
559 tile_editor_panel->SetObjectCreatedCallback(
560 [
this](
int object_id,
const std::string& filename) {
575 std::move(tile_editor_panel));
588 auto settings_panel = std::make_unique<DungeonSettingsPanel>(
nullptr);
589 settings_panel->SetSaveRoomCallback([
this](
int id) {
SaveRoom(
id); });
590 settings_panel->SetSaveAllRoomsCallback([
this]() {
SaveAllRooms(); });
597 auto room_tag_panel = std::make_unique<RoomTagEditorPanel>();
599 room_tag_panel->SetRooms(&
rooms_);
603 std::move(room_tag_panel));
608 auto overlay_panel = std::make_unique<OverlayManagerPanel>();
611 std::move(overlay_panel));
617 auto minecart_panel = std::make_unique<MinecartTrackEditorPanel>();
620 std::move(minecart_panel));
650 if (room_id >= 0 && room_id < (
int)
rooms_.size()) {
651 rooms_[room_id].RenderRoomGraphics();
658 for (
auto& room :
rooms_) {
659 room.ClearWaterFillZone();
660 room.ClearWaterFillDirty();
663 bool legacy_imported =
false;
664 std::vector<zelda3::WaterFillZoneEntry> zones;
668 zones = std::move(zones_or.value());
670 LOG_WARN(
"DungeonEditorV2",
"WaterFillTable parse failed: %s",
671 zones_or.status().message().data());
674 absl::StrFormat(
"WaterFill table parse failed: %s",
675 zones_or.status().message()),
681 std::string sym_path;
688 if (legacy_or.ok()) {
689 zones = std::move(legacy_or.value());
690 legacy_imported = !zones.empty();
692 LOG_WARN(
"DungeonEditorV2",
"Legacy water gate import failed: %s",
693 legacy_or.status().message().data());
697 for (
const auto& z : zones) {
698 if (z.room_id < 0 || z.room_id >=
static_cast<int>(
rooms_.size())) {
701 auto& room =
rooms_[z.room_id];
702 room.set_water_fill_sram_bit_mask(z.sram_bit_mask);
703 for (uint16_t off : z.fill_offsets) {
704 const int x =
static_cast<int>(off % 64);
705 const int y =
static_cast<int>(off / 64);
706 room.SetWaterFillTile(x, y,
true);
709 if (!legacy_imported) {
710 room.ClearWaterFillDirty();
716 "Imported legacy water gate zones (save to write new table)",
722 return absl::OkStatus();
736 if (loading_card.
Begin()) {
737 ImGui::TextColored(theme.text_secondary_gray,
"Loading dungeon data...");
739 "Independent editor cards will appear once ROM data is loaded.");
742 return absl::OkStatus();
749 if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
752 viewer->DeleteSelectedObjects();
757 if (!ImGui::GetIO().WantTextInput) {
759 if (ImGui::IsKeyPressed(ImGuiKey_Tab) && ImGui::GetIO().KeyCtrl) {
762 int current_idx = -1;
763 for (
int i = 0; i < static_cast<int>(
recent_rooms_.size()); ++i) {
769 if (current_idx != -1) {
771 if (ImGui::GetIO().KeyShift) {
783 int current_idx = -1;
791 if (current_idx != -1) {
793 if (ImGui::GetIO().KeyShift) {
805 if (ImGui::GetIO().KeyCtrl) {
807 const int kCols = 16;
809 if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
812 }
else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
815 }
else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
818 }
else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
824 if (next_room != -1) {
838 return absl::OkStatus();
843 return absl::FailedPreconditionError(
"ROM not loaded");
851 LOG_ERROR(
"DungeonEditorV2",
"Failed to save palette changes: %s",
852 status.message().data());
855 LOG_INFO(
"DungeonEditorV2",
"Saved %zu modified colors to ROM",
859 if (flags.kSaveObjects || flags.kSaveSprites || flags.kSaveRoomHeaders) {
860 for (
int room_id = 0; room_id < static_cast<int>(
rooms_.size());
869 if (flags.kSaveTorches) {
872 LOG_ERROR(
"DungeonEditorV2",
"Failed to save torches: %s",
873 status.message().data());
878 if (flags.kSavePits) {
881 LOG_ERROR(
"DungeonEditorV2",
"Failed to save pits: %s",
882 status.message().data());
887 if (flags.kSaveBlocks) {
890 LOG_ERROR(
"DungeonEditorV2",
"Failed to save blocks: %s",
891 status.message().data());
896 if (flags.kSaveCollision) {
899 LOG_ERROR(
"DungeonEditorV2",
"Failed to save collision: %s",
900 status.message().data());
905 if (flags.kSaveWaterFillZones) {
906 auto status = SaveWaterFillZones(
rom_,
rooms_);
908 LOG_ERROR(
"DungeonEditorV2",
"Failed to save water fill zones: %s",
909 status.message().data());
914 if (flags.kSaveChests) {
917 LOG_ERROR(
"DungeonEditorV2",
"Failed to save chests: %s",
918 status.message().data());
923 if (flags.kSavePotItems) {
926 LOG_ERROR(
"DungeonEditorV2",
"Failed to save pot items: %s",
927 status.message().data());
932 return absl::OkStatus();
937 std::vector<std::pair<uint32_t, uint32_t>> ranges;
950 if (flags.kSaveWaterFillZones &&
952 for (
const auto& room :
rooms_) {
953 if (room.water_fill_dirty()) {
964 if (flags.kSaveCollision) {
966 const bool has_ptr_table =
968 static_cast<int>(rom_data.size()));
970 static_cast<int>(rom_data.size()));
971 if (has_ptr_table && has_data_region) {
972 for (
const auto& room :
rooms_) {
973 if (room.custom_collision_dirty()) {
984 for (
const auto& room :
rooms_) {
985 if (!room.IsLoaded()) {
988 int room_id = room.id();
991 if (flags.kSaveRoomHeaders) {
993 int header_ptr_table =
998 int table_offset = header_ptr_table + (room_id * 2);
1000 if (table_offset + 1 <
static_cast<int>(rom_data.size())) {
1002 (rom_data[table_offset + 1] << 8) |
1003 rom_data[table_offset];
1005 ranges.emplace_back(header_location, header_location + 14);
1011 if (flags.kSaveObjects) {
1017 int entry_offset = obj_ptr_table + (room_id * 3);
1019 if (entry_offset + 2 <
static_cast<int>(rom_data.size())) {
1020 int tile_addr = (rom_data[entry_offset + 2] << 16) |
1021 (rom_data[entry_offset + 1] << 8) |
1022 rom_data[entry_offset];
1025 auto encoded = room.EncodeObjects();
1026 ranges.emplace_back(objects_location,
1027 objects_location + encoded.size() + 2);
1038 return absl::FailedPreconditionError(
"ROM not loaded");
1040 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.size())) {
1041 return absl::InvalidArgumentError(
"Invalid room ID");
1048 LOG_ERROR(
"DungeonEditorV2",
"Failed to save palette changes: %s",
1049 status.message().data());
1053 if (flags.kSaveObjects || flags.kSaveSprites || flags.kSaveRoomHeaders) {
1057 if (flags.kSaveTorches) {
1060 if (flags.kSavePits) {
1063 if (flags.kSaveBlocks) {
1066 if (flags.kSaveCollision) {
1069 if (flags.kSaveWaterFillZones) {
1072 if (flags.kSaveChests) {
1075 if (flags.kSavePotItems) {
1079 return absl::OkStatus();
1084 for (
const auto& room :
rooms_) {
1085 if (room.IsLoaded()) {
1094 return absl::FailedPreconditionError(
"ROM not loaded");
1096 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.size())) {
1097 return absl::InvalidArgumentError(
"Invalid room ID");
1100 auto& room =
rooms_[room_id];
1101 if (!room.IsLoaded()) {
1102 return absl::OkStatus();
1109 for (
const auto& w : result.warnings) {
1110 LOG_WARN(
"DungeonEditorV2",
"Room 0x%03X validation warning: %s", room_id,
1113 if (!result.is_valid) {
1114 for (
const auto& e : result.errors) {
1115 LOG_ERROR(
"DungeonEditorV2",
"Room 0x%03X validation error: %s",
1116 room_id, e.c_str());
1121 "Save blocked: room 0x%03X failed validation (%zu error(s))",
1122 room_id, result.errors.size()),
1125 return absl::FailedPreconditionError(
1126 absl::StrFormat(
"Room 0x%03X failed validation", room_id));
1134 std::vector<std::pair<uint32_t, uint32_t>> ranges;
1139 if (flags.kSaveRoomHeaders) {
1141 int header_ptr_table =
1146 int table_offset = header_ptr_table + (room_id * 2);
1148 if (table_offset + 1 <
static_cast<int>(rom_data.size())) {
1150 (rom_data[table_offset + 1] << 8) |
1151 rom_data[table_offset];
1153 ranges.emplace_back(header_location, header_location + 14);
1159 if (flags.kSaveObjects) {
1165 int entry_offset = obj_ptr_table + (room_id * 3);
1167 if (entry_offset + 2 <
static_cast<int>(rom_data.size())) {
1168 int tile_addr = (rom_data[entry_offset + 2] << 16) |
1169 (rom_data[entry_offset + 1] << 8) |
1170 rom_data[entry_offset];
1177 auto encoded = room.EncodeObjects();
1178 ranges.emplace_back(objects_location,
1179 objects_location + encoded.size() + 2);
1186 auto conflicts = manifest.AnalyzePcWriteRanges(ranges);
1187 if (!conflicts.empty()) {
1188 const auto write_policy =
1190 std::string error_msg = absl::StrFormat(
1191 "Hack manifest write conflicts while saving room 0x%03X:\n\n",
1193 for (
const auto& conflict : conflicts) {
1197 "- Address 0x%06X is %s", conflict.address,
1199 if (!conflict.module.empty()) {
1200 absl::StrAppend(&error_msg,
" (Module: ", conflict.module,
")");
1202 absl::StrAppend(&error_msg,
"\n");
1206 LOG_DEBUG(
"DungeonEditorV2",
"%s", error_msg.c_str());
1208 LOG_WARN(
"DungeonEditorV2",
"%s", error_msg.c_str());
1214 "Save warning: write conflict with hack manifest (see log)",
1221 "Save blocked: write conflict with hack manifest (see log)",
1224 return absl::PermissionDeniedError(
"Write conflict with Hack Manifest");
1229 if (flags.kSaveObjects) {
1230 auto status = room.SaveObjects();
1232 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room objects: %s",
1233 status.message().data());
1238 if (flags.kSaveSprites) {
1239 auto status = room.SaveSprites();
1241 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room sprites: %s",
1242 status.message().data());
1247 if (flags.kSaveRoomHeaders) {
1248 auto status = room.SaveRoomHeader();
1250 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room header: %s",
1251 status.message().data());
1258 if (!sys_status.ok()) {
1259 LOG_ERROR(
"DungeonEditorV2",
"Failed to save room system data: %s",
1260 sys_status.message().data());
1264 return absl::OkStatus();
1279 if (!panel_manager) {
1287 panel_manager->ShowPanel(session_id,
"dungeon.workbench");
1290 for (
const auto& descriptor :
1291 panel_manager->GetPanelsInCategory(session_id,
"Dungeon")) {
1292 const std::string& card_id = descriptor.card_id;
1293 if (card_id ==
"dungeon.workbench") {
1296 if (panel_manager->IsPanelPinned(session_id, card_id)) {
1299 const bool is_room_window = card_id.rfind(
"dungeon.room_", 0) == 0;
1302 panel_manager->HidePanel(session_id, card_id);
1306 panel_manager->HidePanel(session_id,
"dungeon.workbench");
1316 enabled ?
"Dungeon workflow: Workbench"
1317 :
"Dungeon workflow: Standalone Panels",
1332 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1333 bool panel_visible =
true;
1338 if (!panel_visible) {
1351 std::string active_category =
1356 if (active_category !=
"Dungeon" && !is_pinned) {
1372 ImGui::SetNextWindowDockID(
room_dock_id_, ImGuiCond_FirstUseEver);
1374 if (room_card->Begin(&open)) {
1375 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
1412 if (room_id < 0 || room_id >= 0x128) {
1413 ImGui::Text(
"Invalid room ID: %d", room_id);
1417 auto& room =
rooms_[room_id];
1419 if (!room.IsLoaded()) {
1422 ImGui::TextColored(theme.text_error_red,
"Failed to load room: %s",
1423 status.message().data());
1429 if (!sys_status.ok()) {
1430 LOG_ERROR(
"DungeonEditorV2",
"Failed to load system data: %s",
1431 sys_status.message().data());
1436 if (room.IsLoaded()) {
1437 bool needs_render =
false;
1442 if (room.blocks().empty()) {
1443 room.LoadRoomGraphics(room.blockset());
1444 needs_render =
true;
1445 LOG_DEBUG(
"[DungeonEditorV2]",
"Loaded room %d graphics from ROM",
1454 if (room.GetTileObjects().empty()) {
1456 needs_render =
true;
1457 LOG_DEBUG(
"[DungeonEditorV2]",
"Loaded room %d objects from ROM",
1467 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1468 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
1469 room.RenderRoomGraphics();
1470 LOG_DEBUG(
"[DungeonEditorV2]",
"Rendered room %d to bitmaps", room_id);
1474 if (room.IsLoaded()) {
1475 ImGui::TextColored(theme.text_success_green,
ICON_MD_CHECK " Loaded");
1477 ImGui::TextColored(theme.text_error_red,
ICON_MD_PENDING " Not Loaded");
1480 ImGui::TextDisabled(
"Objects: %zu", room.GetTileObjects().size());
1486 bool connected = client && client->IsConnected();
1488 ImGui::BeginDisabled();
1490 std::string warp_label =
1492 if (ImGui::SmallButton(warp_label.c_str())) {
1493 auto status = client->WriteWord(0x7E00A0,
static_cast<uint16_t
>(room_id));
1497 absl::StrFormat(
"Warped to room 0x%03X", room_id),
1503 absl::StrFormat(
"Warp failed: %s", status.message()),
1509 ImGui::EndDisabled();
1510 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1511 ImGui::SetTooltip(
"Connect to Mesen2 first");
1514 if (ImGui::IsItemHovered()) {
1515 ImGui::SetTooltip(
"Warp to room 0x%03X in Mesen2", room_id);
1524 viewer->DrawDungeonCanvas(room_id);
1535 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.size())) {
1554 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.size())) {
1555 LOG_WARN(
"DungeonEditorV2",
"Ignoring invalid room selection: %d", room_id);
1583 if (room_id >= 0 && room_id < (
int)
rooms_.size()) {
1584 auto& room =
rooms_[room_id];
1585 if (!room.IsLoaded()) {
1589 if (room.IsLoaded()) {
1598 auto dungeon_main_pal_group =
1641 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1644 if (request_focus) {
1656 std::string room_name = absl::StrFormat(
1659 std::string base_card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1662 {.card_id = base_card_id,
1663 .display_name = room_name,
1666 .category =
"Dungeon",
1667 .shortcut_hint =
"",
1668 .visibility_flag =
nullptr,
1669 .priority = 200 + room_id});
1676 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
1684 auto status =
Save();
1690 LOG_ERROR(
"DungeonEditorV2",
"SaveAllRooms failed: %s",
1691 status.message().data());
1694 absl::StrFormat(
"Save all failed: %s", status.message()),
1707 it->second->Focus();
1735 LOG_ERROR(
"DungeonEditorV2",
"Cannot place object: Invalid room ID %d",
1739 absl::StrFormat(
"Object 0x%02X: no room selected (invalid room %d)",
1753 "Placing object ID=0x%02X at position (%d,%d) in room %03X", obj.
id_,
1756 room.RenderRoomGraphics();
1758 "Object placed and room re-rendered successfully");
1763 absl::StrFormat(
"Placed 0x%02X in room %03X", obj.
id_,
1771 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.size())) {
1772 LOG_WARN(
"DungeonEditorV2",
"Edit Graphics ignored (invalid room id %d)",
1778 if (!editor_manager) {
1780 "Edit Graphics ignored (editor manager unavailable)");
1784 auto& room =
rooms_[room_id];
1785 room.LoadRoomGraphics(room.blockset());
1787 uint16_t sheet_id = 0;
1788 uint16_t tile_index = 0;
1789 bool resolved_sheet =
false;
1790 if (
auto tiles_or =
object.GetTiles();
1791 tiles_or.ok() && !tiles_or.value().empty()) {
1792 const uint16_t tile_id = tiles_or.value().front().id_;
1793 const size_t block_index =
static_cast<size_t>(tile_id / 64);
1794 const auto blocks = room.blocks();
1795 if (block_index < blocks.size()) {
1796 sheet_id = blocks[block_index];
1797 resolved_sheet =
true;
1801 const int tiles_per_sheet = tiles_per_row * tiles_per_col;
1802 if (tiles_per_sheet > 0) {
1803 tile_index =
static_cast<uint16_t
>(tile_id % tiles_per_sheet);
1808 if (
auto* editor_set = editor_manager->GetCurrentEditorSet()) {
1809 if (
auto* graphics = editor_set->GetGraphicsEditor()) {
1810 if (resolved_sheet) {
1811 graphics->SelectSheet(sheet_id);
1812 graphics->HighlightTile(sheet_id, tile_index,
1813 absl::StrFormat(
"Object 0x%02X",
object.id_));
1826 if (new_room_id < 0 || new_room_id >=
static_cast<int>(
rooms_.size())) {
1844 int swap_index = -1;
1852 if (swap_index < 0) {
1872 slot_id = it->second;
1889 std::string old_card_id = absl::StrFormat(
"dungeon.room_%d", old_room_id);
1890 const bool old_pinned =
1896 std::string new_room_name = absl::StrFormat(
1899 std::string new_card_id = absl::StrFormat(
"dungeon.room_%d", new_room_id);
1902 {.card_id = new_card_id,
1903 .display_name = new_room_name,
1906 .category =
"Dungeon",
1907 .shortcut_hint =
"",
1908 .visibility_flag =
nullptr,
1909 .priority = 200 + new_room_id});
1951 room_viewers_.SetEvictionPredicate([
this](
const int& candidate) {
1961 auto* viewer_ptr = existing->get();
1965 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1968 viewer_ptr->
SetPinCallback([
this, card_id, room_id](
bool pinned) {
1970 dependencies_.panel_manager->SetPanelPinned(card_id, pinned);
1971 if (auto* v = GetViewerForRoom(room_id)) {
1972 v->SetPinned(pinned);
1983 auto viewer = std::make_unique<DungeonCanvasViewer>(rom_);
1984 viewer->SetCompactHeaderMode(
false);
1985 viewer->SetRoomDetailsExpanded(
true);
1988 viewer->SetRenderer(renderer_);
1989 viewer->SetCurrentPaletteGroup(current_palette_group_);
1990 viewer->SetCurrentPaletteId(current_palette_id_);
1991 viewer->SetGameData(game_data_);
1997 viewer->object_interaction().SetMutationCallback([
this, viewer_ptr]() {
1999 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2003 BeginUndoSnapshot(rid);
2005 BeginCollisionUndoSnapshot(rid);
2007 BeginWaterFillUndoSnapshot(rid);
2012 viewer->object_interaction().SetCacheInvalidationCallback([
this,
2015 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2019 rooms_[rid].MarkObjectsDirty();
2020 rooms_[rid].RenderRoomGraphics();
2026 FinalizeUndoAction(rid);
2036 FinalizeCollisionUndoAction(rid);
2045 FinalizeWaterFillUndoAction(rid);
2050 viewer->object_interaction().SetObjectPlacedCallback(
2051 [
this](
const zelda3::RoomObject& obj) { HandleObjectPlaced(obj); });
2053 if (dungeon_editor_system_) {
2054 viewer->SetEditorSystem(dungeon_editor_system_.get());
2056 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2057 if (target_room >= 0 && target_room <
static_cast<int>(rooms_.size())) {
2058 OnRoomSelected(target_room);
2062 viewer->SetRoomSwapCallback([
this](
int old_room,
int new_room) {
2063 SwapRoomInPanel(old_room, new_room);
2065 viewer->SetShowObjectPanelCallback([
this]() { ShowPanel(kObjectToolsId); });
2066 viewer->SetShowSpritePanelCallback(
2067 [
this]() { ShowPanel(
"dungeon.sprite_editor"); });
2068 viewer->SetShowItemPanelCallback(
2069 [
this]() { ShowPanel(
"dungeon.item_editor"); });
2070 viewer->SetShowRoomListCallback([
this]() { ShowPanel(kRoomSelectorId); });
2071 viewer->SetShowRoomMatrixCallback([
this]() { ShowPanel(kRoomMatrixId); });
2072 viewer->SetShowEntranceListCallback(
2073 [
this]() { ShowPanel(kEntranceListId); });
2074 viewer->SetShowRoomGraphicsCallback(
2075 [
this]() { ShowPanel(kRoomGraphicsId); });
2076 viewer->SetShowDungeonSettingsCallback(
2077 [
this]() { ShowPanel(
"dungeon.settings"); });
2078 viewer->SetEditGraphicsCallback(
2079 [
this](
int target_room_id,
const zelda3::RoomObject&
object) {
2080 OpenGraphicsEditorForObject(target_room_id,
object);
2082 viewer->SetSaveRoomCallback([
this](
int target_room_id) {
2083 auto status = SaveRoom(target_room_id);
2085 LOG_ERROR(
"DungeonEditorV2",
"Save Room failed: %s",
2086 status.message().data());
2087 if (dependencies_.toast_manager) {
2088 dependencies_.toast_manager->Show(
2089 absl::StrFormat(
"Save Room failed: %s", status.message()),
2094 if (dependencies_.toast_manager) {
2100 if (dependencies_.panel_manager) {
2101 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2102 viewer->SetPinned(dependencies_.panel_manager->IsPanelPinned(card_id));
2103 viewer->SetPinCallback([
this, card_id, room_id](
bool pinned) {
2104 if (dependencies_.panel_manager) {
2105 dependencies_.panel_manager->SetPanelPinned(card_id, pinned);
2107 if (auto* v = GetViewerForRoom(room_id)) {
2108 v->SetPinned(pinned);
2114 viewer->SetMinecartTrackPanel(minecart_track_editor_panel_);
2115 viewer->SetProject(dependencies_.project);
2117 auto* stored = room_viewers_.Insert(room_id, std::move(viewer));
2118 return stored->get();
2123 if (!workbench_viewer_) {
2124 workbench_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
2125 auto* viewer = workbench_viewer_.get();
2126 viewer->SetCompactHeaderMode(
true);
2127 viewer->SetRoomDetailsExpanded(
false);
2128 viewer->SetHeaderVisible(
false);
2129 viewer->SetRooms(&rooms_);
2130 viewer->SetRenderer(renderer_);
2131 viewer->SetCurrentPaletteGroup(current_palette_group_);
2132 viewer->SetCurrentPaletteId(current_palette_id_);
2133 viewer->SetGameData(game_data_);
2137 viewer->object_interaction().SetMutationCallback([
this, viewer]() {
2138 const int rid = viewer ? viewer->current_room_id() : -1;
2139 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2140 const auto domain = viewer->object_interaction().last_mutation_domain();
2141 if (domain == MutationDomain::kTileObjects) {
2142 BeginUndoSnapshot(rid);
2143 }
else if (domain == MutationDomain::kCustomCollision) {
2144 BeginCollisionUndoSnapshot(rid);
2145 }
else if (domain == MutationDomain::kWaterFill) {
2146 BeginWaterFillUndoSnapshot(rid);
2150 viewer->object_interaction().SetCacheInvalidationCallback([
this, viewer]() {
2151 const int rid = viewer ? viewer->current_room_id() : -1;
2152 if (rid >= 0 && rid <
static_cast<int>(rooms_.size())) {
2154 viewer->object_interaction().last_invalidation_domain();
2155 if (domain == MutationDomain::kTileObjects) {
2156 rooms_[rid].MarkObjectsDirty();
2157 rooms_[rid].RenderRoomGraphics();
2159 viewer->object_interaction().mode_manager().GetMode();
2160 if (mode != InteractionMode::DraggingObjects) {
2161 FinalizeUndoAction(rid);
2163 }
else if (domain == MutationDomain::kCustomCollision) {
2165 viewer->object_interaction().mode_manager().GetMode();
2167 viewer->object_interaction().mode_manager().GetModeState();
2168 if (mode == InteractionMode::PaintCollision && st.is_painting) {
2171 FinalizeCollisionUndoAction(rid);
2172 }
else if (domain == MutationDomain::kWaterFill) {
2174 viewer->object_interaction().mode_manager().GetMode();
2176 viewer->object_interaction().mode_manager().GetModeState();
2177 if (mode == InteractionMode::PaintWaterFill && st.is_painting) {
2180 FinalizeWaterFillUndoAction(rid);
2185 viewer->object_interaction().SetObjectPlacedCallback(
2188 if (dungeon_editor_system_) {
2189 viewer->SetEditorSystem(dungeon_editor_system_.get());
2194 viewer->SetRoomSwapCallback([
this](
int ,
int new_room) {
2195 OnRoomSelected(new_room,
false);
2197 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2198 OnRoomSelected(target_room,
false);
2201 viewer->SetShowObjectPanelCallback([
this]() { ShowPanel(kObjectToolsId); });
2202 viewer->SetShowSpritePanelCallback(
2203 [
this]() { ShowPanel(
"dungeon.sprite_editor"); });
2204 viewer->SetShowItemPanelCallback(
2205 [
this]() { ShowPanel(
"dungeon.item_editor"); });
2206 viewer->SetShowRoomListCallback([
this]() { ShowPanel(kRoomSelectorId); });
2207 viewer->SetShowRoomMatrixCallback([
this]() { ShowPanel(kRoomMatrixId); });
2208 viewer->SetShowEntranceListCallback(
2209 [
this]() { ShowPanel(kEntranceListId); });
2210 viewer->SetShowRoomGraphicsCallback(
2211 [
this]() { ShowPanel(kRoomGraphicsId); });
2212 viewer->SetShowDungeonSettingsCallback(
2213 [
this]() { ShowPanel(
"dungeon.settings"); });
2214 viewer->SetEditGraphicsCallback(
2216 OpenGraphicsEditorForObject(target_room_id,
object);
2218 viewer->SetSaveRoomCallback([
this](
int target_room_id) {
2219 auto status = SaveRoom(target_room_id);
2221 LOG_ERROR(
"DungeonEditorV2",
"Save Room failed: %s",
2222 status.message().data());
2223 if (dependencies_.toast_manager) {
2224 dependencies_.toast_manager->Show(
2225 absl::StrFormat(
"Save Room failed: %s", status.message()),
2230 if (dependencies_.toast_manager) {
2231 dependencies_.toast_manager->Show(
"Room saved", ToastType::kSuccess);
2235 viewer->SetMinecartTrackPanel(minecart_track_editor_panel_);
2236 viewer->SetProject(dependencies_.project);
2239 return workbench_viewer_.get();
2243 if (!workbench_compare_viewer_) {
2244 workbench_compare_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
2245 auto* viewer = workbench_compare_viewer_.get();
2246 viewer->SetCompactHeaderMode(
true);
2247 viewer->SetRoomDetailsExpanded(
false);
2248 viewer->SetRooms(&rooms_);
2249 viewer->SetRenderer(renderer_);
2250 viewer->SetCurrentPaletteGroup(current_palette_group_);
2251 viewer->SetCurrentPaletteId(current_palette_id_);
2252 viewer->SetGameData(game_data_);
2256 viewer->SetObjectInteractionEnabled(
false);
2257 viewer->SetHeaderReadOnly(
true);
2258 viewer->SetHeaderVisible(
false);
2260 if (dungeon_editor_system_) {
2263 viewer->SetEditorSystem(dungeon_editor_system_.get());
2266 viewer->SetMinecartTrackPanel(minecart_track_editor_panel_);
2267 viewer->SetProject(dependencies_.project);
2270 return workbench_compare_viewer_.get();
2273absl::Status DungeonEditorV2::Undo() {
2275 if (pending_undo_.room_id >= 0) {
2276 FinalizeUndoAction(pending_undo_.room_id);
2278 if (pending_collision_undo_.room_id >= 0) {
2279 FinalizeCollisionUndoAction(pending_collision_undo_.room_id);
2281 if (pending_water_fill_undo_.room_id >= 0) {
2282 FinalizeWaterFillUndoAction(pending_water_fill_undo_.room_id);
2284 return undo_manager_.Undo();
2287absl::Status DungeonEditorV2::Redo() {
2289 if (pending_undo_.room_id >= 0) {
2290 FinalizeUndoAction(pending_undo_.room_id);
2292 if (pending_collision_undo_.room_id >= 0) {
2293 FinalizeCollisionUndoAction(pending_collision_undo_.room_id);
2295 if (pending_water_fill_undo_.room_id >= 0) {
2296 FinalizeWaterFillUndoAction(pending_water_fill_undo_.room_id);
2298 return undo_manager_.Redo();
2301absl::Status DungeonEditorV2::Cut() {
2302 if (
auto* viewer = GetViewerForRoom(current_room_id_)) {
2303 viewer->object_interaction().HandleCopySelected();
2304 viewer->object_interaction().HandleDeleteSelected();
2306 return absl::OkStatus();
2309absl::Status DungeonEditorV2::Copy() {
2310 if (
auto* viewer = GetViewerForRoom(current_room_id_)) {
2311 viewer->object_interaction().HandleCopySelected();
2313 return absl::OkStatus();
2316absl::Status DungeonEditorV2::Paste() {
2317 if (
auto* viewer = GetViewerForRoom(current_room_id_)) {
2318 viewer->object_interaction().HandlePasteObjects();
2320 return absl::OkStatus();
2323void DungeonEditorV2::BeginUndoSnapshot(
int room_id) {
2324 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2328 if (has_pending_undo_) {
2330 "BeginUndoSnapshot called twice without FinalizeUndoAction. "
2331 "Previous snapshot for room %d is being leaked. Finalizing now.",
2332 pending_undo_.room_id);
2334 if (pending_undo_.room_id >= 0) {
2335 FinalizeUndoAction(pending_undo_.room_id);
2339 pending_undo_.room_id = room_id;
2340 pending_undo_.before_objects = rooms_[room_id].GetTileObjects();
2341 has_pending_undo_ =
true;
2344void DungeonEditorV2::FinalizeUndoAction(
int room_id) {
2345 if (pending_undo_.room_id < 0 || pending_undo_.room_id != room_id)
2347 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2350 auto after_objects = rooms_[room_id].GetTileObjects();
2352 auto action = std::make_unique<DungeonObjectsAction>(
2353 room_id, std::move(pending_undo_.before_objects),
2354 std::move(after_objects),
2355 [
this](
int rid,
const std::vector<zelda3::RoomObject>& objects) {
2356 RestoreRoomObjects(rid, objects);
2358 undo_manager_.Push(std::move(action));
2360 pending_undo_.room_id = -1;
2361 pending_undo_.before_objects.clear();
2362 has_pending_undo_ =
false;
2365void DungeonEditorV2::SyncPanelsToRoom(
int room_id) {
2367 if (object_editor_panel_) {
2368 object_editor_panel_->SetCurrentRoom(room_id);
2369 object_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2373 if (sprite_editor_panel_) {
2374 sprite_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2376 if (item_editor_panel_) {
2377 item_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2379 if (custom_collision_panel_) {
2380 auto* viewer = GetViewerForRoom(room_id);
2381 custom_collision_panel_->SetCanvasViewer(viewer);
2383 custom_collision_panel_->SetInteraction(&viewer->object_interaction());
2386 if (water_fill_panel_) {
2387 auto* viewer = GetViewerForRoom(room_id);
2388 water_fill_panel_->SetCanvasViewer(viewer);
2390 water_fill_panel_->SetInteraction(&viewer->object_interaction());
2394 if (dungeon_settings_panel_) {
2395 dungeon_settings_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
2398 if (object_tile_editor_panel_) {
2399 object_tile_editor_panel_->SetCurrentPaletteGroup(current_palette_group_);
2402 if (room_tag_editor_panel_) {
2403 room_tag_editor_panel_->SetCurrentRoomId(room_id);
2406 if (overlay_manager_panel_) {
2407 auto* viewer = GetViewerForRoom(room_id);
2410 overlay_state.
show_grid = viewer->mutable_show_grid();
2413 viewer->mutable_show_coordinate_overlay();
2415 viewer->mutable_show_room_debug_info();
2419 viewer->mutable_show_minecart_tracks();
2421 viewer->mutable_show_custom_collision_overlay();
2423 viewer->mutable_show_track_collision_overlay();
2425 viewer->mutable_show_camera_quadrant_overlay();
2427 viewer->mutable_show_minecart_sprite_overlay();
2429 viewer->mutable_show_track_collision_legend();
2430 overlay_manager_panel_->SetState(overlay_state);
2435void DungeonEditorV2::ShowRoomPanel(
int room_id) {
2436 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size())) {
2440 bool already_active =
false;
2441 for (
int i = 0; i < active_rooms_.Size; ++i) {
2442 if (active_rooms_[i] == room_id) {
2443 already_active =
true;
2447 if (!already_active) {
2448 active_rooms_.push_back(room_id);
2449 room_selector_.set_active_rooms(active_rooms_);
2452 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2454 if (dependencies_.panel_manager) {
2455 if (!dependencies_.panel_manager->GetPanelDescriptor(
2456 dependencies_.panel_manager->GetActiveSessionId(), card_id)) {
2457 std::string room_name = absl::StrFormat(
2459 dependencies_.panel_manager->RegisterPanel(
2460 {.card_id = card_id,
2461 .display_name = room_name,
2464 .category =
"Dungeon",
2465 .shortcut_hint =
"",
2466 .visibility_flag =
nullptr,
2467 .priority = 200 + room_id});
2469 dependencies_.panel_manager->ShowPanel(card_id);
2473 if (room_cards_.find(room_id) == room_cards_.end()) {
2474 std::string base_name = absl::StrFormat(
2476 const int slot_id = GetOrCreateRoomPanelSlotId(room_id);
2477 std::string card_name_str = absl::StrFormat(
2478 "%s###RoomPanelSlot%d",
MakePanelTitle(base_name).c_str(), slot_id);
2480 auto card = std::make_shared<gui::PanelWindow>(card_name_str.c_str(),
2482 card->SetDefaultSize(620, 700);
2483 room_cards_[room_id] = card;
2487void DungeonEditorV2::RestoreRoomObjects(
2488 int room_id,
const std::vector<zelda3::RoomObject>& objects) {
2489 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2492 auto& room = rooms_[room_id];
2493 room.GetTileObjects() = objects;
2494 room.RenderRoomGraphics();
2497void DungeonEditorV2::BeginCollisionUndoSnapshot(
int room_id) {
2498 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2501 if (pending_collision_undo_.room_id >= 0) {
2502 FinalizeCollisionUndoAction(pending_collision_undo_.room_id);
2505 pending_collision_undo_.room_id = room_id;
2506 pending_collision_undo_.before = rooms_[room_id].custom_collision();
2509void DungeonEditorV2::FinalizeCollisionUndoAction(
int room_id) {
2510 if (pending_collision_undo_.room_id < 0 ||
2511 pending_collision_undo_.room_id != room_id) {
2514 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2517 auto after = rooms_[room_id].custom_collision();
2518 if (pending_collision_undo_.before.has_data == after.has_data &&
2519 pending_collision_undo_.before.tiles == after.tiles) {
2520 pending_collision_undo_.room_id = -1;
2521 pending_collision_undo_.before = {};
2525 auto action = std::make_unique<DungeonCustomCollisionAction>(
2526 room_id, std::move(pending_collision_undo_.before), std::move(after),
2528 RestoreRoomCustomCollision(rid, map);
2530 undo_manager_.Push(std::move(action));
2532 pending_collision_undo_.room_id = -1;
2533 pending_collision_undo_.before = {};
2536void DungeonEditorV2::RestoreRoomCustomCollision(
2538 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2541 auto& room = rooms_[room_id];
2542 room.custom_collision() = map;
2543 room.MarkCustomCollisionDirty();
2554 for (
size_t i = 0; i < zone.tiles.size(); ++i) {
2555 if (zone.tiles[i] != 0) {
2556 snap.
offsets.push_back(
static_cast<uint16_t
>(i));
2564void DungeonEditorV2::BeginWaterFillUndoSnapshot(
int room_id) {
2565 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2568 if (pending_water_fill_undo_.room_id >= 0) {
2569 FinalizeWaterFillUndoAction(pending_water_fill_undo_.room_id);
2572 pending_water_fill_undo_.room_id = room_id;
2573 pending_water_fill_undo_.before = MakeWaterFillSnapshot(rooms_[room_id]);
2576void DungeonEditorV2::FinalizeWaterFillUndoAction(
int room_id) {
2577 if (pending_water_fill_undo_.room_id < 0 ||
2578 pending_water_fill_undo_.room_id != room_id) {
2581 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2584 auto after = MakeWaterFillSnapshot(rooms_[room_id]);
2585 if (pending_water_fill_undo_.before.sram_bit_mask == after.sram_bit_mask &&
2586 pending_water_fill_undo_.before.offsets == after.offsets) {
2587 pending_water_fill_undo_.room_id = -1;
2588 pending_water_fill_undo_.before = {};
2592 auto action = std::make_unique<DungeonWaterFillAction>(
2593 room_id, std::move(pending_water_fill_undo_.before), std::move(after),
2595 RestoreRoomWaterFill(rid, snap);
2597 undo_manager_.Push(std::move(action));
2599 pending_water_fill_undo_.room_id = -1;
2600 pending_water_fill_undo_.before = {};
2603void DungeonEditorV2::RestoreRoomWaterFill(
int room_id,
2605 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size()))
2608 auto& room = rooms_[room_id];
2609 room.ClearWaterFillZone();
2611 for (uint16_t off : snap.
offsets) {
2612 const int x =
static_cast<int>(off % 64);
2613 const int y =
static_cast<int>(off / 64);
2614 room.SetWaterFillTile(x, y,
true);
2616 room.MarkWaterFillDirty();
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
bool loaded() const
Check if the manifest has been loaded.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
int current_room_id() const
void SetPinned(bool pinned)
DungeonObjectInteraction & object_interaction()
void SetRooms(std::array< zelda3::Room, 0x128 > *rooms)
void SetPinCallback(std::function< void(bool)> callback)
class MinecartTrackEditorPanel * minecart_track_editor_panel_
int GetOrCreateRoomPanelSlotId(int room_id)
std::deque< int > recent_rooms_
absl::Status SaveRoom(int room_id)
void SetAgentMode(bool enabled)
std::array< zelda3::Room, 0x128 > rooms_
class CustomCollisionPanel * custom_collision_panel_
uint64_t current_palette_group_id_
void add_room(int room_id)
void OpenGraphicsEditorForObject(int room_id, const zelda3::RoomObject &object)
class ItemEditorPanel * item_editor_panel_
absl::Status SaveRoomData(int room_id)
void ShowRoomPanel(int room_id)
int next_room_panel_slot_id_
std::array< zelda3::RoomEntrance, 0x8C > entrances_
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
static constexpr const char * kEntranceListId
absl::Status Save() override
gfx::IRenderer * renderer_
void TouchViewerLru(int room_id)
class SpriteEditorPanel * sprite_editor_panel_
void FocusRoom(int room_id)
void HandleObjectPlaced(const zelda3::RoomObject &obj)
class DungeonSettingsPanel * dungeon_settings_panel_
std::unique_ptr< ObjectEditorPanel > owned_object_editor_panel_
std::unique_ptr< zelda3::DungeonEditorSystem > dungeon_editor_system_
class DungeonWorkbenchPanel * workbench_panel_
void SyncPanelsToRoom(int room_id)
void Initialize() override
std::unordered_map< int, int > room_panel_slot_ids_
bool IsWorkbenchWorkflowEnabled() const
void OnRoomSelected(int room_id, bool request_focus=true)
~DungeonEditorV2() override
DungeonRoomGraphicsPanel * room_graphics_panel_
ObjectTileEditorPanel * object_tile_editor_panel_
OverlayManagerPanel * overlay_manager_panel_
ObjectEditorPanel * object_editor_panel_
void ProcessDeferredTextures()
gfx::SnesPalette current_palette_
ImVector< int > active_rooms_
gui::PaletteEditorWidget palette_editor_
void ShowPanel(const std::string &card_id)
static bool IsValidRoomId(int room_id)
void SwapRoomInPanel(int old_room_id, int new_room_id)
static constexpr size_t kMaxRecentRooms
void DrawRoomTab(int room_id)
void SetWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
class RoomTagEditorPanel * room_tag_editor_panel_
void ProcessPendingSwap()
PendingSwap pending_swap_
void ProcessPendingWorkflowMode()
static constexpr const char * kObjectToolsId
DungeonCanvasViewer * GetViewerForRoom(int room_id)
absl::Status Update() override
class WaterFillPanel * water_fill_panel_
absl::Status Undo() override
int LoadedRoomCount() const
ImGuiWindowClass room_window_class_
void SelectObject(int obj_id)
std::unique_ptr< emu::render::EmulatorRenderService > render_service_
DungeonRoomLoader room_loader_
DungeonCanvasViewer * GetWorkbenchViewer()
std::unordered_map< int, std::shared_ptr< gui::PanelWindow > > room_cards_
absl::Status Redo() override
void RemoveViewerFromLru(int room_id)
static constexpr const char * kRoomGraphicsId
void QueueWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
static constexpr const char * kRoomMatrixId
static constexpr const char * kRoomSelectorId
DungeonRoomSelector room_selector_
zelda3::GameData * game_data_
void ReleaseRoomPanelSlotId(int room_id)
absl::Status Load() override
std::vector< std::pair< uint32_t, uint32_t > > CollectWriteRanges() const
static constexpr const char * kPaletteEditorId
PendingWorkflowMode pending_workflow_mode_
DungeonCanvasViewer * GetWorkbenchCompareViewer()
uint64_t current_palette_id_
MutationDomain last_mutation_domain() const
InteractionModeManager & mode_manager()
MutationDomain last_invalidation_domain() const
void SetCustomObjectsFolder(const std::string &folder)
void SetProject(project::YazeProject *project)
void InvalidatePreviewCache()
void SetTileEditorPanel(ObjectTileEditorPanel *panel)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
Set the current palette group for graphics rendering.
absl::Status LoadRoomEntrances(std::array< zelda3::RoomEntrance, 0x8C > &entrances)
absl::Status LoadRoom(int room_id, zelda3::Room &room)
void set_entrances(std::array< zelda3::RoomEntrance, 0x8C > *entrances)
void SetRoomSelectedWithIntentCallback(std::function< void(int, RoomSelectionIntent)> callback)
void set_current_room_id(uint16_t room_id)
void SetRoomSelectedCallback(std::function< void(int)> callback)
void set_active_rooms(const ImVector< int > &rooms)
void set_rooms(std::array< zelda3::Room, 0x128 > *rooms)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void NotifyRoomChanged(int previous_room_id)
Called by the editor when the current room changes.
void SetUndoRedoProvider(std::function< bool()> can_undo, std::function< bool()> can_redo, std::function< void()> on_undo, std::function< void()> on_redo, std::function< std::string()> undo_desc, std::function< std::string()> redo_desc, std::function< int()> undo_depth)
The EditorManager controls the main editor window and manages the various editor classes.
UndoManager undo_manager_
zelda3::GameData * game_data() const
EditorDependencies dependencies_
InteractionMode GetMode() const
Get current interaction mode.
ModeState & GetModeState()
Get mutable reference to mode state.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetProject(project::YazeProject *project)
void SetRoomNavigationCallback(RoomNavigationCallback callback)
void SetRooms(std::array< zelda3::Room, 0x128 > *rooms)
void SetProjectRoot(const std::string &root)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SetGameData(zelda3::GameData *game_data)
void SetPlacementError(const std::string &message)
void SelectObject(int obj_id)
DungeonObjectSelector & object_selector()
void SetAgentOptimizedLayout(bool enabled)
void OpenForObject(int16_t object_id, int room_id, std::array< zelda3::Room, 0x128 > *rooms)
void RegisterPanelAlias(const std::string &legacy_base_id, const std::string &canonical_base_id)
Register a legacy panel ID alias that resolves to a canonical ID.
bool ShowPanel(size_t session_id, const std::string &base_card_id)
void RegisterPanel(size_t session_id, const PanelDescriptor &base_info)
std::string GetActiveCategory() const
bool IsPanelVisible(size_t session_id, const std::string &base_card_id) const
void UnregisterPanel(size_t session_id, const std::string &base_card_id)
void ShowAllPanelsInCategory(size_t session_id, const std::string &category)
bool IsPanelPinned(size_t session_id, const std::string &base_card_id) const
void RegisterEditorPanel(std::unique_ptr< EditorPanel > panel)
Register an EditorPanel instance for central drawing.
size_t GetActiveSessionId() const
void SetPanelPinned(size_t session_id, const std::string &base_card_id, bool pinned)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
std::string GetRedoDescription() const
Description of the action that would be redone (for UI)
std::string GetUndoDescription() const
Description of the action that would be undone (for UI)
size_t UndoStackSize() const
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
static std::shared_ptr< MesenSocketClient > & GetClient()
void ProcessTextureQueue(IRenderer *renderer)
bool HasUnsavedChanges() const
Check if there are ANY unsaved changes.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
static PaletteManager & Get()
Get the singleton instance.
absl::Status SaveAllToRom()
Save ALL modified palettes to ROM.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
static CustomObjectManager & Get()
void AddObjectFile(int object_id, const std::string &filename)
void Initialize(const std::string &custom_objects_folder)
ValidationResult ValidateRoom(const Room &room)
static ObjectDimensionTable & Get()
const WaterFillZoneMap & water_fill_zone() const
uint8_t water_fill_sram_bit_mask() const
#define ICON_MD_ROCKET_LAUNCH
#define ICON_MD_GRID_VIEW
#define ICON_MD_DOOR_FRONT
#define ICON_MD_WORKSPACES
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::string AddressOwnershipToString(AddressOwnership ownership)
const AgentUITheme & GetTheme()
WaterFillSnapshot MakeWaterFillSnapshot(const zelda3::Room &room)
absl::Status SaveWaterFillZones(Rom *rom, std::array< zelda3::Room, 0x128 > &rooms)
Editors are the view controllers for the application.
RoomSelectionIntent
Intent for room selection in the dungeon editor.
absl::StatusOr< PaletteGroup > CreatePaletteGroupFromLargePalette(SnesPalette &palette, int num_colors)
Create a PaletteGroup by dividing a large palette into sub-palettes.
constexpr int kTilesheetHeight
constexpr int kTilesheetWidth
std::string MakePanelTitle(const std::string &title)
absl::Status SaveAllChests(Rom *rom, absl::Span< const Room > rooms)
constexpr int kWaterFillTableEnd
constexpr int kCustomCollisionDataSoftEnd
std::string GetRoomLabel(int id)
Convenience function to get a room label.
constexpr int kWaterFillTableStart
absl::Status NormalizeWaterFillZoneMasks(std::vector< WaterFillZoneEntry > *zones)
absl::Status SaveAllPotItems(Rom *rom, absl::Span< const Room > rooms)
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadLegacyWaterGateZones(Rom *rom, const std::string &symbol_path)
absl::Status SaveAllTorches(Rom *rom, absl::Span< const Room > rooms)
absl::Status SaveAllPits(Rom *rom)
absl::Status SaveAllBlocks(Rom *rom)
constexpr int kCustomCollisionDataPosition
constexpr int kNumberOfRooms
constexpr int kRoomHeaderPointer
constexpr int kRoomHeaderPointerBank
constexpr int kCustomCollisionRoomPointers
absl::Status SaveAllCollision(Rom *rom, absl::Span< Room > rooms)
std::unique_ptr< DungeonEditorSystem > CreateDungeonEditorSystem(Rom *rom, GameData *game_data)
Factory function to create dungeon editor system.
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadWaterFillTable(Rom *rom)
constexpr int kRoomObjectPointer
absl::Status WriteWaterFillTable(Rom *rom, const std::vector< WaterFillZoneEntry > &zones)
uint32_t SnesToPc(uint32_t addr) noexcept
#define RETURN_IF_ERROR(expr)
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
project::YazeProject * project
ToastManager * toast_manager
gfx::IRenderer * renderer
PanelManager * panel_manager
bool * show_room_debug_info
bool * show_coordinate_overlay
bool * show_camera_quadrants
bool * show_custom_collision
bool * show_minecart_sprites
bool * show_minecart_tracks
bool * show_texture_debug
bool * show_collision_legend
bool * show_track_collision
bool * show_object_bounds
std::vector< uint16_t > offsets
PaletteGroup dungeon_main
std::unordered_map< int, std::vector< std::string > > custom_object_files
std::string custom_objects_folder
core::HackManifest hack_manifest
std::string GetAbsolutePath(const std::string &relative_path) const
std::string symbols_filename
gfx::PaletteGroupMap palette_groups
std::vector< uint16_t > fill_offsets