21#include "absl/status/status.h"
22#include "absl/strings/str_format.h"
23#include "absl/types/span.h"
24#include "imgui/imgui.h"
88 return std::make_tuple(
object.id_,
object.x_,
object.y_,
object.size_,
89 object.GetLayerValue(),
object.all_bgs_,
object.lit_,
90 object.options(),
object.block_behavior_layer(),
91 object.torch_reserved_bit());
112 .
WithHost(
"Dungeon Workbench inspector");
120 room_viewers_.ForEach([](
int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
122 viewer->CancelMinecartGesture();
127 viewer->CancelMinecartGesture();
139 const absl::Status status =
142 LOG_WARN(
"DungeonEditorV2",
"Minecart project rebind failed: %s",
158 [
this, editor_manager, session_id](
const MinecartTrack& before,
161 if (editor_manager &&
162 !editor_manager->IsCurrentProjectContextOwnedBySession(session_id))
163 return absl::FailedPreconditionError(
164 "Activate this dungeon project before editing tracks");
172 [
this, editor_manager, session_id](
173 int room,
int x,
int y,
175 if (editor_manager &&
176 !editor_manager->IsCurrentProjectContextOwnedBySession(
178 return absl::FailedPreconditionError(
179 "Activate this dungeon project before placing track nodes");
184 [
this, editor_manager, session_id](
187 if (editor_manager &&
188 !editor_manager->IsCurrentProjectContextOwnedBySession(
190 return absl::FailedPreconditionError(
191 "Activate this dungeon project before editing rails");
196 [
this, editor_manager, session_id](
197 const std::vector<zelda3::TrackCollisionResult>& preview,
199 if (editor_manager !=
nullptr &&
200 !editor_manager->IsCurrentProjectContextOwnedBySession(
202 return absl::FailedPreconditionError(
203 "Minecart collision apply requires its project context to be "
208 if (editor_manager ==
nullptr) {
216 [editor_manager, session_id](
218 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
220 return absl::FailedPreconditionError(
221 "Minecart overlay edit requires its project context to be "
224 editor_manager->GetCurrentProject()->dungeon_overlay = overlay;
225 editor_manager->MarkCurrentProjectDirty();
226 return absl::OkStatus();
229 [editor_manager, session_id]() -> absl::Status {
230 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
232 return absl::FailedPreconditionError(
233 "Minecart overlay draft requires its project context to be "
236 editor_manager->MarkCurrentProjectDirty();
237 return absl::OkStatus();
240 [editor_manager, session_id]() -> absl::Status {
241 if (!editor_manager->IsCurrentProjectContextOwnedBySession(
243 return absl::FailedPreconditionError(
244 "Minecart project save requires its project context to be "
247 return editor_manager->SaveProject();
253 return absl::OkStatus();
256 return absl::FailedPreconditionError(
257 "Minecart Tracks requires hack_manifest.minecart_tracks.source");
262 "Custom Objects is disabled. Minecart Tracks remains available, but "
263 "custom-object ROM writes stay protected until the flag is enabled.",
269 return absl::FailedPreconditionError(
270 "Minecart Tracks can only be registered for the active project "
274 auto minecart_panel = std::make_unique<MinecartTrackEditorPanel>();
276 minecart_panel->SetRooms(&
rooms_);
277 minecart_panel->SetRoomNavigationCallback(
284 std::move(minecart_panel), WorkbenchTool(
"minecart_tracks"));
290 [
this](
int, std::unique_ptr<DungeonCanvasViewer>& viewer) {
291 if (viewer !=
nullptr) {
309 return absl::OkStatus();
313 const bool custom_objects_enabled =
315 const bool custom_objects_state_changed =
317 const uint64_t generation =
320 !custom_objects_state_changed) {
325 if (custom_objects_state_changed) {
336 if (viewer !=
nullptr) {
337 viewer->InvalidateExternalSpriteResources();
341 [&](
int,
auto& viewer) { invalidate_sprite_resources(viewer.get()); });
355 constexpr const char* kPrefix =
"dungeon.room_";
356 constexpr size_t kPrefixLength = 13;
357 if (card_id.rfind(kPrefix, 0) != 0 || card_id.size() <= kPrefixLength) {
360 return std::all_of(card_id.begin() + kPrefixLength, card_id.end(),
361 [](
unsigned char ch) { return std::isdigit(ch) != 0; });
365 constexpr std::array<const char*, 3> kWorkbenchNavigationPanelIds = {
367 "dungeon.entrance_properties",
372 std::any_of(kWorkbenchNavigationPanelIds.begin(),
373 kWorkbenchNavigationPanelIds.end(),
374 [&card_id](
const char* navigation_id) {
375 return card_id == navigation_id;
382 : rom_(rom), room_loader_(rom), room_selector_(rom), rooms_(rom) {
451 return absl::FailedPreconditionError(
"ROM not loaded");
457 std::array<zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots>
459 std::array<zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints>
460 refreshed_spawn_points;
464 struct RefreshedRoom {
468 std::vector<std::pair<int, bool>> materialized_rooms;
470 [&materialized_rooms](
int room_id,
const zelda3::Room& room) {
471 materialized_rooms.emplace_back(room_id, room.
IsLoaded());
474 std::vector<RefreshedRoom> refreshed_rooms;
475 refreshed_rooms.reserve(materialized_rooms.size());
476 for (
const auto& [room_id, was_loaded] : materialized_rooms) {
484 refreshed_rooms.push_back({room_id, std::move(refreshed_room)});
505 [&prepare_viewer](
int room_id,
506 std::unique_ptr<DungeonCanvasViewer>& viewer) {
507 prepare_viewer(viewer.get(), room_id);
510 const int compare_room_id =
520 (void)object_editor->ClearSelection();
521 object_editor->ClearHistory();
542 for (
auto& refreshed : refreshed_rooms) {
545 *room = std::move(refreshed.room);
574 const int resolved_palette_id = current_room->ResolveDungeonPaletteId();
576 resolved_palette_id >= 0 &&
577 resolved_palette_id <
580 ?
static_cast<uint64_t
>(resolved_palette_id)
603 [&bind_viewer](
int room_id,
604 std::unique_ptr<DungeonCanvasViewer>& viewer) {
605 bind_viewer(viewer.get(), room_id);
640 current_viewer = viewer->get();
642 if (current_viewer) {
649 return absl::OkStatus();
658 bool legacy_imported =
false;
659 const bool water_fill_save_enabled =
661 std::vector<zelda3::WaterFillZoneEntry> zones;
665 zones = std::move(zones_or.value());
667 LOG_WARN(
"DungeonEditorV2",
"WaterFillTable parse failed: %s",
668 zones_or.status().message().data());
671 absl::StrFormat(
"WaterFill table parse failed: %s",
672 zones_or.status().message()),
678 std::string sym_path;
685 if (legacy_or.ok()) {
686 zones = std::move(legacy_or.value());
687 legacy_imported = !zones.empty();
689 LOG_WARN(
"DungeonEditorV2",
"Legacy water gate import failed: %s",
690 legacy_or.status().message().data());
694 for (
const auto& z : zones) {
695 if (z.room_id < 0 || z.room_id >=
static_cast<int>(
rooms_.
size())) {
698 auto& room =
rooms_[z.room_id];
700 for (uint16_t off : z.fill_offsets) {
701 const int x =
static_cast<int>(off % 64);
702 const int y =
static_cast<int>(off / 64);
709 if (!legacy_imported || !water_fill_save_enabled) {
716 water_fill_save_enabled
717 ?
"Imported legacy water gate zones (save to write new table)"
718 :
"Loaded legacy water gate zones read-only (WaterFill saving is "
749 const auto shortcut_hint =
750 [
this](
const char* id,
751 std::vector<ImGuiKey> default_keys) -> std::string {
753 const std::string live =
764 window_manager->RegisterPanelAlias(
"dungeon.entrances",
765 "dungeon.entrance_properties");
767 "dungeon.entrance_properties");
773 window_manager->RegisterPanel(
774 {.card_id =
"dungeon.workbench",
775 .display_name =
"Dungeon Workbench",
776 .window_title =
" Dungeon Workbench",
778 .category =
"Dungeon",
779 .workflow_group =
"Core",
782 .visibility_flag =
nullptr,
785 .disabled_tooltip =
"Load a ROM to edit dungeon rooms"});
787 window_manager->RegisterPanel(
789 .display_name =
"Room List",
790 .window_title =
" Room List",
792 .category =
"Dungeon",
793 .workflow_group =
"Core",
796 .list_in_window_browser =
false,
797 .allow_popout =
true,
799 .shortcut_hint = shortcut_hint(
800 "dungeon.open_room_list", {ImGuiMod_Ctrl, ImGuiMod_Alt, ImGuiKey_L}),
802 .visibility_flag =
nullptr,
805 .disabled_tooltip =
"Load a ROM to browse dungeon rooms"});
807 window_manager->RegisterPanel(
808 {.card_id =
"dungeon.entrance_properties",
809 .display_name =
"Entrances",
810 .window_title =
" Entrances",
812 .category =
"Dungeon",
813 .workflow_group =
"Core",
816 shortcut_hint(
"dungeon.open_entrances",
817 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_E}),
819 .visibility_flag =
nullptr,
822 .disabled_tooltip =
"Load a ROM to browse and edit dungeon entrances"});
824 window_manager->RegisterPanel(
826 .display_name =
"Room Matrix",
827 .window_title =
" Room Matrix",
829 .category =
"Dungeon",
830 .workflow_group =
"Core",
832 .shortcut_hint = shortcut_hint(
833 "dungeon.open_matrix", {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_M}),
835 .visibility_flag =
nullptr,
838 .disabled_tooltip =
"Load a ROM to view the room matrix"});
840 window_manager->RegisterPanel(
842 .display_name =
"Room Graphics",
843 .window_title =
" Room Graphics",
845 .category =
"Dungeon",
846 .workflow_group =
"Editors",
847 .presentation = WorkbenchTool(),
849 shortcut_hint(
"dungeon.open_room_graphics",
850 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiKey_G}),
852 .visibility_flag =
nullptr,
855 .disabled_tooltip =
"Load a ROM to view room graphics"});
857 window_manager->RegisterPanel(
859 .display_name =
"Object Selector",
860 .window_title =
" Object Selector",
862 .category =
"Dungeon",
863 .workflow_group =
"Editors",
864 .presentation = WorkbenchTool(),
866 .visibility_flag =
nullptr,
869 .disabled_tooltip =
"Load a ROM to browse dungeon objects"});
871 window_manager->RegisterPanel(
873 .display_name =
"Door Editor",
874 .window_title =
" Door Editor",
876 .category =
"Dungeon",
877 .workflow_group =
"Editors",
878 .presentation = WorkbenchTool(),
880 .visibility_flag =
nullptr,
883 .disabled_tooltip =
"Load a ROM to edit dungeon doors"});
885 window_manager->RegisterPanel(
887 .display_name =
"Palette Editor",
888 .window_title =
" Palette Editor",
890 .category =
"Dungeon",
891 .workflow_group =
"Editors",
892 .presentation = WorkbenchTool(),
894 .shortcut_hint = shortcut_hint(
895 "dungeon.open_palette",
896 {ImGuiMod_Ctrl, ImGuiMod_Shift, ImGuiMod_Alt, ImGuiKey_P}),
898 .visibility_flag =
nullptr,
901 .disabled_tooltip =
"Load a ROM to edit dungeon palettes"});
916 window_manager->RegisterWindowContent(std::make_unique<RoomBrowserContent>(
920 auto matrix_panel = std::make_unique<RoomMatrixContent>(
923 [
this](
int old_room,
int new_room) {
927 matrix_panel->SetRoomIntentCallback(
931 matrix_panel->SetCensusManifestProvider(
937 window_manager->RegisterWindowContent(std::move(matrix_panel));
941 auto workbench = std::make_unique<DungeonWorkbenchContent>(
947 [
this](
int room_id) {
950 LOG_ERROR(
"DungeonEditorV2",
"Apply Room failed: %s",
951 status.message().data());
954 absl::StrFormat(
"Apply Room failed: %s", status.message()),
966 [
this]() ->
const std::deque<int>& {
return recent_rooms_; },
967 [
this](
int room_id) {
981 [
this]() {
Undo().IgnoreError(); }, [
this]() {
Redo().IgnoreError(); },
986 [window_manager](
const std::string& window_id) {
987 const size_t session_id = window_manager->GetActiveSessionId();
988 return window_manager->IsWindowOpen(session_id, window_id);
990 [
this, window_manager](
const std::string& window_id) {
991 if (window_id.empty()) {
995 window_manager->GetActiveSessionId();
1001 [window_manager]() { window_manager->TriggerShowShortcuts(); });
1007 window_manager->RegisterWindowContent(std::move(workbench));
1011 auto entrances_panel = std::make_unique<DungeonEntrancesPanel>(
1015 entrances_panel->SetGraphicsPreview(
1023 window_manager->RegisterWindowContent(std::move(entrances_panel));
1031 [
this](
int room_id, std::optional<std::string> label) {
1047 return absl::FailedPreconditionError(
"ROM not loaded");
1053 if (!dim_table.IsLoaded()) {
1062 return absl::FailedPreconditionError(
"GameData not available");
1096 auto graphics_panel = std::make_unique<RoomGraphicsContent>(
1098 graphics_panel->SetAssignmentCallback(
1099 [
this](
int room,
int slot,
int sheet) {
1102 graphics_panel->SetUsageCallback(
1106 std::move(graphics_panel), WorkbenchTool());
1107 auto palette_panel =
1111 std::move(palette_panel), WorkbenchTool());
1119 auto object_selector = std::make_unique<ObjectSelectorContent>(
1127 rooms_[current_room_id_].RenderRoomGraphics();
1132 object_selector->SetRooms(&
rooms_);
1135 auto open_workbench_selection_inspector = [
this]() {
1142 object_selector->SetOpenObjectEditorCallback(
1143 open_workbench_selection_inspector);
1148 auto jump_to_reciprocal_door = [
this](
int neighbor_room_id,
1149 size_t door_index) {
1150 if (neighbor_room_id < 0 ||
1151 neighbor_room_id >=
static_cast<int>(
rooms_.
size())) {
1157 viewer->object_interaction().SetCurrentRoom(&
rooms_, neighbor_room_id);
1158 viewer->object_interaction()
1159 .entity_coordinator()
1161 .SelectDoor(door_index);
1164 auto door_editor = std::make_unique<DoorEditorContent>();
1165 door_editor->SetRooms(&
rooms_);
1166 door_editor->SetOpenSelectionInspectorCallback(
1167 open_workbench_selection_inspector);
1168 door_editor->SetJumpToReciprocalDoorCallback(jump_to_reciprocal_door);
1180 auto custom_collision_panel =
1181 std::make_unique<CustomCollisionPanel>(
nullptr,
nullptr);
1184 auto water_fill_panel = std::make_unique<WaterFillPanel>(
nullptr,
nullptr);
1187 auto room_tag_panel = std::make_unique<RoomTagEditorPanel>();
1189 room_tag_panel->SetRooms(&
rooms_);
1191 room_tag_panel->SetMetadataEditCallback(
1204 std::move(object_selector), WorkbenchTool());
1212 sprite_panel->SetOpenSelectionInspectorCallback(
1213 open_workbench_selection_inspector);
1220 item_panel->SetOpenSelectionInspectorCallback(
1221 open_workbench_selection_inspector);
1227 std::move(custom_collision_panel), WorkbenchTool());
1229 std::move(water_fill_panel), WorkbenchTool());
1231 std::move(room_tag_panel), WorkbenchTool());
1233 std::move(object_coverage_panel),
1235 "Canvas context menu > Check in Object Coverage"));
1238 auto tile_editor_panel =
1241 tile_editor_panel->SetStandardWritePreflightCallback(
1242 [
this](
const std::vector<std::pair<uint32_t, uint32_t>>& ranges) {
1245 return absl::OkStatus();
1250 "dungeon object tile data",
"ObjectTileEditorPanel",
1253 tile_editor_panel->SetTilesAppliedCallback([
this]() {
1261 std::move(tile_editor_panel),
1263 "Object navigator or canvas selection menu > Edit tiles"));
1293 auto overlay_panel = std::make_unique<OverlayManagerPanel>();
1296 std::move(overlay_panel),
1297 WorkbenchTool().WithHost(
"Command palette"));
1333 return absl::OkStatus();
1342 [
this](
const std::string& group_name,
int palette_index) {
1348 int palette_index) {
1353 if (group_name ==
"hud") {
1356 }
else if (group_name ==
"dungeon_main") {
1366 std::array<bool, zelda3::kNumberOfRooms> rendered_rooms{};
1367 auto render_once = [
this, &rendered_rooms](
int room_id) {
1368 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size()) ||
1369 room_id >=
static_cast<int>(rendered_rooms.size()) ||
1370 rendered_rooms[room_id]) {
1373 rooms_[room_id].RenderRoomGraphics();
1374 rendered_rooms[room_id] =
true;
1385 bool force_shared_palette_refresh) {
1392 force_shared_palette_refresh &&
1398 const bool force_shared_palette_refresh =
1402 static_cast<uint64_t
>(dungeon_main_pal_group.size())) {
1413 &dungeon_main_pal_group, &render_once,
1414 force_shared_palette_refresh](
1416 std::unique_ptr<DungeonCanvasViewer>& viewer) {
1417 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
1420 const int palette_id =
rooms_[room_id].ResolveDungeonPaletteId();
1421 if (palette_id < 0 ||
1422 palette_id >=
static_cast<int>(dungeon_main_pal_group.size())) {
1429 const auto room_palette_group =
1431 dungeon_main_pal_group.palette_ref(palette_id),
game_data());
1432 if (viewer && viewer->object_interaction().IsObjectLoaded()) {
1433 render_once(room_id);
1435 apply_palette(viewer.get(), palette_id, room_palette_group,
1436 force_shared_palette_refresh);
1460 if (tile_palette_id < 0 ||
1461 tile_palette_id >=
static_cast<int>(dungeon_palettes.size()) ||
1469 dungeon_palettes.palette_ref(tile_palette_id),
game_data()));
1487 room.MarkGraphicsDirty();
1506 if (loading_card.
Begin()) {
1507 ImGui::TextColored(theme.text_secondary_gray,
1508 tr(
"Loading dungeon data..."));
1510 tr(
"Independent editor cards will appear once ROM data is loaded."));
1513 return absl::OkStatus();
1528 return absl::OkStatus();
1532 if (ImGui::GetCurrentContext() ==
nullptr) {
1535 bool canvas_owns_focus =
false;
1537 [&canvas_owns_focus](
int,
1538 const std::unique_ptr<DungeonCanvasViewer>& viewer) {
1540 canvas_owns_focus ||
1541 (viewer !=
nullptr && viewer->OwnsRoomNavigationShortcutFocus());
1544 canvas_owns_focus ||
1549 const bool navigator_owns_focus =
1554 return canvas_owns_focus || navigator_owns_focus;
1559 constexpr int kColumns = 16;
1561 switch (direction) {
1584 if (next_room < 0) {
1596 if (direction == 0) {
1603 const auto current =
1608 const int current_index =
1609 static_cast<int>(std::distance(
recent_rooms_.begin(), current));
1611 const int next_index =
1612 (current_index + (direction > 0 ? 1 : -1) + count) % count;
1620 int current_index = -1;
1621 for (
int index = 0; index <
active_rooms_.size(); ++index) {
1623 current_index = index;
1627 if (current_index < 0) {
1631 const int next_index =
1632 (current_index + (direction > 0 ? 1 : -1) + count) % count;
1642 if (!room || room->GetTileObjects().empty()) {
1646 if (!viewer || viewer->rooms() != &
rooms_ ||
1648 !viewer->IsObjectInteractionEnabled() || viewer->header_read_only()) {
1651 auto& interaction = viewer->object_interaction();
1653 interaction.entity_coordinator().tile_handler().context();
1657 const auto selected = interaction.GetSelectedObjectIndices();
1658 const auto& objects = room->GetTileObjects();
1659 const size_t count = objects.size();
1660 const size_t current = selected.empty() ? 0 : selected.front();
1662 ((current < count ? current : 0) + (direction > 0 ? 1 : count - 1)) %
1664 interaction.SetSelectedObjects({next});
1665 viewer->ScrollToTile(objects[next].x_, objects[next].y_);
1705 return entrance.dirty();
1711 return spawn.dirty();
1735 return room->HasUnsavedChanges();
1752 if (!window_manager) {
1760 window_manager->OpenWindow(session_id,
"dungeon.workbench");
1769 for (
const auto& descriptor :
1770 window_manager->GetWindowsInCategory(session_id,
"Dungeon")) {
1771 const std::string& card_id = descriptor.card_id;
1772 if (!IsWorkbenchNavigationWindowId(card_id) ||
1773 !window_manager->IsWindowOpen(session_id, card_id) ||
1774 window_manager->IsWindowPinned(session_id, card_id)) {
1778 window_manager->CloseWindow(session_id, card_id);
1782 window_manager->CloseWindow(session_id,
"dungeon.workbench");
1784 window_manager->OpenWindow(session_id,
"dungeon.entrance_properties");
1788 for (
const std::string& card_id :
1790 window_manager->OpenWindow(session_id, card_id);
1796 if (
is_loaded_ && was_enabled != now_enabled &&
1806 now_enabled ?
"Dungeon workflow: Workbench"
1807 :
"Dungeon workflow: Standalone Panels",
1826 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
1827 bool panel_visible =
true;
1832 if (!panel_visible) {
1845 std::string active_category =
1850 if (active_category !=
"Dungeon" && !is_pinned) {
1866 ImGui::SetNextWindowDockID(
room_dock_id_, ImGuiCond_FirstUseEver);
1868 if (room_card->Begin(&open)) {
1869 if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
1906 if (room_id < 0 || room_id >= 0x128) {
1907 ImGui::Text(tr(
"Invalid room ID: %d"), room_id);
1911 auto& room =
rooms_[room_id];
1913 if (!room.IsLoaded()) {
1916 ImGui::TextColored(theme.text_error_red, tr(
"Failed to load room: %s"),
1917 status.message().data());
1923 if (!sys_status.ok()) {
1924 LOG_ERROR(
"DungeonEditorV2",
"Failed to load system data: %s",
1925 sys_status.message().data());
1930 if (room.IsLoaded()) {
1931 bool needs_render =
false;
1933 if (room.blocks().empty()) {
1935 room.LoadRoomGraphics();
1936 needs_render =
true;
1939 if (!room.AreObjectsLoaded()) {
1941 needs_render =
true;
1944 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1945 if (needs_render || !bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
1946 room.RenderRoomGraphics();
1950 if (room.IsLoaded()) {
1951 ImGui::TextColored(theme.text_success_green,
ICON_MD_CHECK " Loaded");
1953 ImGui::TextColored(theme.text_error_red,
ICON_MD_PENDING " Not Loaded");
1956 ImGui::TextDisabled(tr(
"Objects: %zu"), room.GetTileObjects().size());
1961 const std::string return_label = absl::StrFormat(
1963 if (ImGui::SmallButton(return_label.c_str())) {
1966 if (ImGui::IsItemHovered()) {
1968 tr(
"Switch back to the integrated Dungeon Workbench workflow."));
1976 bool connected = client && client->IsConnected();
1978 ImGui::BeginDisabled();
1980 std::string warp_label =
1982 if (ImGui::SmallButton(warp_label.c_str())) {
1983 auto status = client->WriteWord(0x7E00A0,
static_cast<uint16_t
>(room_id));
1987 absl::StrFormat(
"Warped to room 0x%03X", room_id),
1993 absl::StrFormat(
"Warp failed: %s", status.message()),
1999 ImGui::EndDisabled();
2000 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
2001 ImGui::SetTooltip(tr(
"Connect to Mesen2 first"));
2004 if (ImGui::IsItemHovered()) {
2005 ImGui::SetTooltip(tr(
"Warp to room 0x%03X in Mesen2"), room_id);
2016 const float status_bar_reserved =
2017 std::max(ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f,
2019 ImGui::GetStyle().ItemSpacing.y;
2020 if (ImGui::BeginChild(
"##StandaloneRoomBody",
2021 ImVec2(0.0f, -status_bar_reserved),
false)) {
2022 viewer->DrawDungeonCanvas(room_id);
2027 bool room_dirty =
false;
2028 if (room_id >= 0 && room_id <
static_cast<int>(
rooms_.
size())) {
2029 room_dirty =
rooms_[room_id].HasUnsavedChanges();
2031 const char* tool_mode =
2032 viewer->object_interaction().mode_manager().GetModeName();
2035 status.workflow_primary =
false;
2039 status.on_undo = [
this]() {
2040 Undo().IgnoreError();
2042 status.on_redo = [
this]() {
2043 Redo().IgnoreError();
2050 if (ImGui::GetCurrentContext() ==
nullptr) {
2061 if (ImGui::GetCurrentContext() ==
nullptr ||
2071 ImGui::GetIO().WantTextInput || ImGui::IsAnyItemActive() ||
2088 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2107 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2108 LOG_WARN(
"DungeonEditorV2",
"Ignoring invalid room selection: %d", room_id);
2117 if (
context.uses_entrance()) {
2143 if (room_id >= 0 && room_id < (
int)
rooms_.
size()) {
2144 auto& room =
rooms_[room_id];
2145 if (!room.IsLoaded()) {
2149 if (room.IsLoaded()) {
2161 auto dungeon_main_pal_group =
2206 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2209 if (request_focus) {
2221 std::string room_name = absl::StrFormat(
2224 std::string base_card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2227 {.card_id = base_card_id,
2228 .display_name = room_name,
2231 .category =
"Dungeon",
2232 .workflow_group =
"Rooms",
2234 .shortcut_hint =
"",
2235 .visibility_flag =
nullptr,
2236 .priority = 200 + room_id});
2243 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
2252 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2259 if (entrance_id < -1 || entrance_id >=
static_cast<int>(
entrances_.size()))
2269 if (entrance_id < 0 || entrance_id >=
static_cast<int>(
entrances_.size())) {
2274 return spawn.spawn_id() == entrance_id ? spawn.room_id : -1;
2280 int room_id)
const {
2283 return context.effective_blockset();
2287 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2290 auto& room =
rooms_[room_id];
2291 if (!room.IsLoaded()) {
2294 room.SetRenderEntranceBlockset(
2300 if (viewer ==
nullptr) {
2306 context.effective_blockset());
2311 if (camera.has_value() && camera->room_id == room_id) {
2331 it->second->Focus();
2341 return absl::FailedPreconditionError(
"Dungeon Workbench is not loaded");
2348 return absl::OkStatus();
2387 LOG_ERROR(
"DungeonEditorV2",
"Cannot place object: Invalid room ID %d",
2389 const std::string message = absl::StrFormat(
2402 "Placing object ID=0x%02X at position (%d,%d) in room %03X", obj.
id_,
2405 room.RenderRoomGraphics();
2407 "Object placed and room re-rendered successfully");
2412 absl::StrFormat(
"Placed 0x%02X in room %03X", obj.
id_,
2419 const std::function<
void(
int,
zelda3::Room&,
const std::string&,
bool,
2423 const auto previous_blocks = room.
blocks();
2426 if (room.
blocks() != previous_blocks ||
2440 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2441 LOG_WARN(
"DungeonEditorV2",
"Edit Graphics ignored (invalid room id %d)",
2447 if (!editor_manager) {
2449 "Edit Graphics ignored (editor manager unavailable)");
2453 auto& room =
rooms_[room_id];
2455 room.PrepareForRender();
2456 const auto object_stamp = GraphicsObjectStamp(
object);
2457 std::optional<size_t> placed_index;
2458 bool ambiguous_placement =
false;
2463 const auto& placed_objects = room.GetTileObjects();
2464 for (
size_t i = 0; i < placed_objects.size(); ++i) {
2465 if (GraphicsObjectStamp(placed_objects[i]) != object_stamp)
2468 ambiguous_placement =
true;
2475 LOG_WARN(
"DungeonEditorV2",
"Edit Graphics: %s",
2476 std::string(
context.status().message()).c_str());
2486 auto resolve_live_source =
2487 [
this, room_id, object, object_stamp, placed_index, ambiguous_placement,
2492 return absl::FailedPreconditionError(
"Dungeon source session changed");
2494 if (ambiguous_placement) {
2495 return absl::FailedPreconditionError(
2496 "Dungeon object placement is ambiguous; reopen Graphics");
2499 if (!live_room || !live_room->IsLoaded()) {
2500 return absl::FailedPreconditionError(
"Dungeon source room changed");
2502 if (!SameGraphicsEntrance(entrance,
2504 return absl::FailedPreconditionError(
2505 "Dungeon entrance or blockset source changed; reopen Graphics");
2509 const auto& objects = live_room->GetTileObjects();
2510 if (*placed_index >= objects.size() ||
2511 GraphicsObjectStamp(objects[*placed_index]) != object_stamp) {
2512 return absl::FailedPreconditionError(
2513 "Dungeon object placement changed; reopen Graphics");
2515 live_object = &objects[*placed_index];
2520 live_room->PrepareForRender();
2523 if (!refreshed.ok())
2524 return refreshed.status();
2525 fresh = std::move(*refreshed);
2526 return absl::OkStatus();
2528 context->resolve_live_source = resolve_live_source;
2535 return absl::FailedPreconditionError(
"Dungeon source session changed");
2537 auto& live_room =
rooms_[room_id];
2539 live_room.MarkGraphicsDirty();
2540 live_room.PrepareForRender();
2542 if (!refreshed.ok())
2543 return refreshed.status();
2547 [
this, room_id]() -> absl::StatusOr<usage_preview::PreviewImage> {
2548 auto& source =
rooms_[room_id];
2552 source.MarkGraphicsDirty();
2553 source.PrepareForRender();
2559 source.RenderComposite(layers, composite);
2562 context->return_to_source = [
this, editor_manager, room_id]() {
2567 if (
auto* editor_set = editor_manager->GetCurrentEditorSet()) {
2568 if (
auto* graphics = editor_set->GetGraphicsEditor()) {
2578 const auto status = graphics->OpenInContext(std::move(*
context));
2580 LOG_WARN(
"DungeonEditorV2",
"Edit Graphics: %s",
2581 std::string(status.message()).c_str());
2596 return absl::FailedPreconditionError(
2597 "Object Tile Editor panel is unavailable");
2600 return absl::FailedPreconditionError(
2601 "Workspace window manager is unavailable");
2604 return absl::FailedPreconditionError(
"ROM not loaded");
2606 if (room_id < 0 || room_id >=
static_cast<int>(
rooms_.
size())) {
2607 return absl::OutOfRangeError(
"Dungeon room id is outside the room store");
2610 return absl::UnimplementedError(
2611 "Tile editing is not yet supported for this object layout");
2615 return absl::FailedPreconditionError(
"Dungeon palette data is unavailable");
2618 auto& room =
rooms_[room_id];
2619 if (!room.IsLoaded()) {
2623 room.PrepareForRender();
2625 const int palette_id = room.ResolveDungeonPaletteId();
2627 if (palette_id < 0 ||
2628 palette_id >=
static_cast<int>(dungeon_palettes.size())) {
2629 return absl::OutOfRangeError(
2630 "Room resolves to an unavailable dungeon palette");
2632 auto room_palette = dungeon_palettes[palette_id];
2637 object.id_, room_id, &
rooms_, palette_group);
2638 if (!open_status.ok()) {
2651 return absl::NotFoundError(
2652 "Object Tile Editor window is not registered in this session");
2654 return absl::OkStatus();
2660 if (new_room_id < 0 || new_room_id >=
static_cast<int>(
rooms_.
size())) {
2678 int swap_index = -1;
2686 if (swap_index < 0) {
2706 slot_id = it->second;
2723 std::string old_card_id = absl::StrFormat(
"dungeon.room_%d", old_room_id);
2724 const bool old_pinned =
2730 std::string new_room_name = absl::StrFormat(
2733 std::string new_card_id = absl::StrFormat(
"dungeon.room_%d", new_room_id);
2736 {.card_id = new_card_id,
2737 .display_name = new_room_name,
2740 .category =
"Dungeon",
2741 .workflow_group =
"Rooms",
2743 .shortcut_hint =
"",
2744 .visibility_flag =
nullptr,
2745 .priority = 200 + new_room_id});
2781 if (!window_manager) {
2789 if (ImGui::GetCurrentContext() !=
nullptr) {
2792 if (!window_name.empty()) {
2793 ImGui::SetWindowFocus(window_name.c_str());
2892 const absl::Status status =
2897 LOG_ERROR(
"DungeonEditorV2",
"Edit Object Tiles failed: %s",
2898 status.message().data());
2901 absl::StrFormat(
"Edit Object Tiles failed: %s", status.message()),
2906 auto status =
SaveRoom(target_room_id);
2908 LOG_ERROR(
"DungeonEditorV2",
"Apply Room failed: %s",
2909 status.message().data());
2912 absl::StrFormat(
"Apply Room failed: %s", status.message()),
2935 room_viewers_.SetEvictionPredicate([
this](
const int& candidate) {
2945 auto* viewer_ptr = existing->get();
2951 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2954 viewer_ptr->
SetPinCallback([
this, card_id, room_id](
bool pinned) {
2956 dependencies_.window_manager->SetWindowPinned(card_id, pinned);
2957 if (auto* existing_viewer = room_viewers_.Get(room_id)) {
2958 (*existing_viewer)->SetPinned(pinned);
2969 auto viewer = std::make_unique<DungeonCanvasViewer>(rom_);
2970 viewer->SetCompactHeaderMode(
false);
2971 viewer->SetRoomDetailsExpanded(
true);
2973 ConfigureEditableViewer(viewer_ptr);
2974 viewer->SetRoomNavigationCallback([
this](
int target_room) {
2975 if (target_room >= 0 && target_room <
static_cast<int>(rooms_.size())) {
2976 OnRoomSelected(target_room);
2980 viewer->SetRoomSwapCallback([
this](
int old_room,
int new_room) {
2981 SwapRoomInPanel(old_room, new_room);
2984 if (dependencies_.window_manager) {
2985 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
2986 viewer->SetPinned(dependencies_.window_manager->IsWindowPinned(card_id));
2987 viewer->SetPinCallback([
this, card_id, room_id](
bool pinned) {
2988 if (dependencies_.window_manager) {
2989 dependencies_.window_manager->SetWindowPinned(card_id, pinned);
2991 if (auto* existing_viewer = room_viewers_.Get(room_id)) {
2992 (*existing_viewer)->SetPinned(pinned);
2998 auto* stored = room_viewers_.Insert(room_id, std::move(viewer));
2999 RefreshViewerRuntimeContext(stored->get(), room_id);
3000 ApplyEntranceRenderContext(room_id);
3001 return stored->get();
3006 ConfigureViewerUndoHooks(viewer);
3009 WireViewerPanelCallbacks(viewer);
3014 bool rebind_project) {
3019 if (viewer->
rom() != rom_) {
3022 if (viewer->
rooms() != &rooms_) {
3025 if (viewer->
game_data() != game_data_) {
3031 ConfigureViewerRenderContext(viewer, room_id);
3034 if (rebind_project || viewer->
project() != dependencies_.project) {
3040 if (!workbench_viewer_) {
3041 workbench_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
3042 auto* viewer = workbench_viewer_.get();
3043 viewer->SetCompactHeaderMode(
true);
3044 viewer->SetRoomDetailsExpanded(
false);
3045 viewer->SetHeaderVisible(
false);
3046 viewer->SetHeaderHiddenMetadataHudVisible(
false);
3047 ConfigureEditableViewer(viewer);
3051 viewer->SetRoomSwapCallback([
this](
int ,
int new_room) {
3052 OnRoomSelected(new_room,
false);
3054 viewer->SetRoomNavigationCallback([
this](
int target_room) {
3055 OnRoomSelected(target_room,
false);
3059 auto* viewer = workbench_viewer_.get();
3060 RefreshViewerRuntimeContext(viewer, current_room_id_);
3065 if (!workbench_compare_viewer_) {
3066 workbench_compare_viewer_ = std::make_unique<DungeonCanvasViewer>(rom_);
3067 auto* viewer = workbench_compare_viewer_.get();
3068 viewer->SetCompactHeaderMode(
true);
3069 viewer->SetRoomDetailsExpanded(
false);
3070 viewer->SetHeaderHiddenMetadataHudVisible(
false);
3073 viewer->SetObjectInteractionEnabled(
false);
3074 viewer->SetHeaderReadOnly(
true);
3075 viewer->SetHeaderVisible(
false);
3078 auto* viewer = workbench_compare_viewer_.get();
3079 RefreshViewerRuntimeContext(viewer, room_id);
3080 viewer->SetObjectInteractionEnabled(
false);
3081 viewer->SetHeaderReadOnly(
true);
3082 viewer->SetHeaderVisible(
false);
3086void DungeonEditorV2::SyncPanelsToRoom(
int room_id) {
3087 if (minecart_track_editor_panel_) {
3088 minecart_track_editor_panel_->SetActiveRoom(room_id);
3091 if (object_selector_panel_) {
3092 object_selector_panel_->SetCanvasViewerProvider(
3093 [
this]() {
return GetViewerForRoom(current_room_id_); });
3094 object_selector_panel_->SetCurrentRoom(room_id);
3095 object_selector_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
3097 if (door_editor_panel_) {
3098 door_editor_panel_->SetCanvasViewerProvider(
3099 [
this]() {
return GetViewerForRoom(current_room_id_); });
3100 door_editor_panel_->SetCurrentRoom(room_id);
3101 door_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
3105 if (sprite_editor_panel_) {
3106 sprite_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
3108 if (item_editor_panel_) {
3109 item_editor_panel_->SetCanvasViewer(GetViewerForRoom(room_id));
3111 if (custom_collision_panel_) {
3112 auto* viewer = GetViewerForRoom(room_id);
3113 custom_collision_panel_->SetCanvasViewer(viewer);
3115 custom_collision_panel_->SetInteraction(&viewer->object_interaction());
3118 if (water_fill_panel_) {
3119 auto* viewer = GetViewerForRoom(room_id);
3120 water_fill_panel_->SetCanvasViewer(viewer);
3122 water_fill_panel_->SetInteraction(&viewer->object_interaction());
3126 if (room_tag_editor_panel_) {
3127 room_tag_editor_panel_->SetCurrentRoomId(room_id);
3130 if (overlay_manager_panel_) {
3131 auto* viewer = GetViewerForRoom(room_id);
3134 overlay_state.
show_grid = viewer->mutable_show_grid();
3137 viewer->mutable_show_coordinate_overlay();
3139 viewer->mutable_show_room_debug_info();
3143 viewer->mutable_show_minecart_tracks();
3145 viewer->mutable_show_custom_collision_overlay();
3147 viewer->mutable_show_track_collision_overlay();
3149 viewer->mutable_show_camera_quadrant_overlay();
3151 viewer->mutable_show_minecart_sprite_overlay();
3153 viewer->mutable_show_track_collision_legend();
3154 overlay_manager_panel_->SetState(overlay_state);
3159void DungeonEditorV2::ShowRoomPanel(
int room_id) {
3160 if (room_id < 0 || room_id >=
static_cast<int>(rooms_.size())) {
3164 bool already_active =
false;
3165 for (
int i = 0; i < active_rooms_.Size; ++i) {
3166 if (active_rooms_[i] == room_id) {
3167 already_active =
true;
3171 if (!already_active) {
3172 active_rooms_.push_back(room_id);
3173 room_selector_.set_active_rooms(active_rooms_);
3176 std::string card_id = absl::StrFormat(
"dungeon.room_%d", room_id);
3178 if (dependencies_.window_manager) {
3179 if (!dependencies_.window_manager->GetWindowDescriptor(
3180 dependencies_.window_manager->GetActiveSessionId(), card_id)) {
3181 std::string room_name = absl::StrFormat(
3183 dependencies_.window_manager->RegisterWindow(
3184 {.card_id = card_id,
3185 .display_name = room_name,
3188 .category =
"Dungeon",
3189 .workflow_group =
"Rooms",
3190 .presentation = WindowPresentationPolicy::OptionalPopOut(),
3191 .shortcut_hint =
"",
3192 .visibility_flag =
nullptr,
3193 .priority = 200 + room_id});
3195 dependencies_.window_manager->OpenWindow(card_id);
3199 if (room_cards_.find(room_id) == room_cards_.end()) {
3200 std::string base_name = absl::StrFormat(
3202 const int slot_id = GetOrCreateRoomPanelSlotId(room_id);
3203 std::string card_name_str = absl::StrFormat(
3204 "%s###RoomPanelSlot%d",
MakePanelTitle(base_name).c_str(), slot_id);
3206 auto card = std::make_shared<gui::PanelWindow>(card_name_str.c_str(),
3208 card->SetDefaultSize(620, 700);
3209 room_cards_[room_id] = card;
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Loads and queries the hack manifest JSON for yaze-ASM integration.
const MinecartTrackLayout & minecart_track_layout() const
bool loaded() const
Check if the manifest has been loaded.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetInteraction(DungeonObjectInteraction *interaction)
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetCheckObjectCoverageCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
void SetCurrentPaletteId(uint64_t id)
void SetShowEntranceListCallback(std::function< void()> callback)
DungeonRoomStore * rooms() const
void SetEditorSystem(zelda3::DungeonEditorSystem *system)
void SetMinecartTrackPanel(MinecartTrackEditorPanel *panel)
void SetShowItemPanelCallback(std::function< void()> callback)
void SetPinned(bool pinned)
void SetShowRoomGraphicsCallback(std::function< void()> callback)
void SetProject(const project::YazeProject *project)
void SetRenderer(gfx::IRenderer *renderer)
void SetEditGraphicsCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
zelda3::GameData * game_data() const
void SetShowRoomListCallback(std::function< void()> callback)
void SetEntranceBlocksetResolver(std::function< uint8_t(int)> resolver)
void RefreshRomBackedState(Rom *rom, zelda3::GameData *game_data, DungeonRoomStore *rooms, int room_id)
DungeonObjectInteraction & object_interaction()
void SetShowRoomMatrixCallback(std::function< void()> callback)
void SetShowSpritePanelCallback(std::function< void()> callback)
void ClearEntranceCameraOverlay()
void SetShowObjectPanelCallback(std::function< void()> callback)
void SetEntranceRenderContext(int entrance_id, uint8_t entrance_blockset)
const project::YazeProject * project() const
void SetEditObjectTilesCallback(std::function< void(int, const zelda3::RoomObject &)> callback)
void SetSaveRoomCallback(std::function< void(int)> callback)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group, bool force_refresh=false)
void SetShowDungeonSettingsCallback(std::function< void()> callback)
void DeleteSelectedObjects()
void SetEntranceCameraOverlay(int entrance_slot, const DungeonEntranceCameraState &state)
void SetRooms(DungeonRoomStore *rooms)
bool CanHandleRoomCanvasShortcut() const
void SetGameData(zelda3::GameData *game_data)
void SetShowDoorEditorCallback(std::function< void()> callback)
void SetPinCallback(std::function< void(bool)> callback)
class MinecartTrackEditorPanel * minecart_track_editor_panel_
int GetOrCreateRoomPanelSlotId(int room_id)
void ConfigureMinecartProjectCallbacks()
std::deque< int > recent_rooms_
absl::Status SaveRoom(int room_id)
void SetAgentMode(bool enabled)
std::string proposal_review_path() const
DungeonRenderContext ResolveDungeonRenderContextForRoom(int room_id) const
absl::Status EditRoomGraphicsSheet(int room_id, int slot, int sheet_id)
std::unique_ptr< CustomCollisionPanel > owned_custom_collision_panel_
class CustomCollisionPanel * custom_collision_panel_
bool CanHandleDungeonNavigationShortcut() const
std::optional< int > room_canvas_delete_shortcut_frame_
bool HasPendingRoomChanges() const
bool observed_custom_objects_enabled_
void RefreshViewerRuntimeContext(DungeonCanvasViewer *viewer, int room_id, bool rebind_project=false)
uint64_t current_palette_group_id_
void ToggleWorkbenchWorkflowMode(bool show_toast=true)
void add_room(int room_id)
void OpenGraphicsEditorForObject(int room_id, const zelda3::RoomObject &object)
bool CycleRoomSelection(int direction)
absl::Status OpenObjectTileEditorForObject(int room_id, const zelda3::RoomObject &object)
absl::Status EnsureMinecartTrackEditorPanel()
class ItemEditorPanel * item_editor_panel_
PendingUndo pending_undo_
std::unique_ptr< RoomTagEditorPanel > owned_room_tag_editor_panel_
class ObjectCoveragePanel * object_coverage_panel_
void ShowRoomPanel(int room_id)
std::unique_ptr< ObjectCoveragePanel > CreateObjectCoveragePanel()
int next_room_panel_slot_id_
absl::StatusOr< zelda3::RoomsetUsageReport > GetRoomGraphicsUsage(int roomset) const
absl::Status ApplyRoomLabelEdit(int room_id, std::optional< std::string > label)
void SetDependencies(const EditorDependencies &deps) override
std::unique_ptr< ObjectCoveragePanel > owned_object_coverage_panel_
util::LruCache< int, std::unique_ptr< DungeonCanvasViewer > > room_viewers_
gfx::PaletteGroup current_palette_group_
void OnEntranceSelected(int entrance_id)
static constexpr const char * kEntranceListId
gfx::IRenderer * renderer_
void TouchViewerLru(int room_id)
PendingStandaloneToolWindow pending_standalone_tool_window_
class SpriteEditorPanel * sprite_editor_panel_
void FocusRoom(int room_id)
void HandleObjectPlaced(const zelda3::RoomObject &obj)
std::unique_ptr< zelda3::DungeonEditorSystem > dungeon_editor_system_
std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > entrances_
void OpenWindow(const std::string &window_id)
void SyncPanelsToRoom(int room_id)
DoorEditorContent * door_editor_panel_
absl::Status LoadProposalForReview(const std::string &path)
void Initialize() override
std::unordered_map< int, int > room_panel_slot_ids_
void OpenRegularEntrance(int entrance_id)
class DungeonWorkbenchContent * workbench_panel_
bool automatic_entrance_preview_
int ResolveEntranceRoomId(int entrance_id) const
void SynchronizeCustomObjectAssets()
bool IsWorkbenchWorkflowEnabled() const
void OnRoomSelected(int room_id, bool request_focus=true)
absl::Status ApplyMinecartCollisionStamp(int room_id, const zelda3::CustomCollisionMap &expected, const MinecartCollisionStamp &stamp)
std::optional< DungeonEntranceCameraState > GetEntranceCameraState(int slot_index) const
~DungeonEditorV2() override
PendingCollisionUndo pending_collision_undo_
void FocusObjectCoverage(int room_id, const zelda3::RoomObject &object)
void ConfigureEntrancePropertyEditCallbacks(DungeonEntrancesPanel *panel)
void RefreshEntranceCameraOverlays()
ObjectTileEditorPanel * object_tile_editor_panel_
ObjectSelectorContent * object_selector_panel_
OverlayManagerPanel * overlay_manager_panel_
absl::Status PrepareProjectSave()
void ProcessDeferredTextures()
uint8_t ResolveSelectedEntranceBlocksetForRoom(int room_id) const
DungeonCanvasViewer * GetWorkbenchCompareViewer(int room_id)
void ConfigureStagedGraphicsPreview(GraphicsEditContext &context, int room_id, std::function< absl::Status(GraphicsEditContext &)> resolve_live_source)
bool CycleObjectSelection(int direction)
void WireViewerPanelCallbacks(DungeonCanvasViewer *viewer)
gfx::SnesPalette current_palette_
absl::Status EditRoomMetadata(int room_id, const RoomMetadataEdit &edit)
ImVector< int > active_rooms_
std::unique_ptr< DungeonCanvasViewer > workbench_compare_viewer_
PendingWaterFillUndo pending_water_fill_undo_
RoomMatrixContent * room_matrix_panel_
std::unique_ptr< MinecartTrackEditorPanel > owned_minecart_track_editor_panel_
uint64_t observed_custom_object_generation_
absl::Status ApplyMinecartTrackEdit(const MinecartTrack &before, const MinecartTrack &after, bool move_cart)
void SetGraphicsPreviewEntrance(int entrance_id)
std::array< PendingEntityUndo, 3 > pending_entity_undo_
gui::PaletteEditorWidget palette_editor_
std::unique_ptr< DoorEditorContent > owned_door_editor_panel_
int PendingRoomCount() const
bool ConsumeRoomCanvasDeleteShortcut(DungeonCanvasViewer &viewer)
static bool IsValidRoomId(int room_id)
void VisitGraphicsUsageRooms(const std::function< void(int, zelda3::Room &, const std::string &, bool, int)> &visitor)
void SwapRoomInPanel(int old_room_id, int new_room_id)
RoomGraphicsContent * room_graphics_panel_
void ExpireStaleRoomCanvasDeleteShortcut()
static constexpr size_t kMaxRecentRooms
void RegisterPaletteListener()
void DrawRoomTab(int room_id)
std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > spawn_points_
void SetWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
class RoomTagEditorPanel * room_tag_editor_panel_
void ProcessPendingSwap()
std::optional< SaveTransactionSnapshot > save_transaction_snapshot_
std::unique_ptr< ObjectSelectorContent > owned_object_selector_panel_
PendingSwap pending_swap_
static constexpr const char * kDoorEditorId
void ProcessPendingWorkflowMode()
DungeonCanvasViewer * GetViewerForRoom(int room_id)
PendingSelectionUndo pending_selection_undo_
absl::Status Update() override
class WaterFillPanel * water_fill_panel_
absl::Status Undo() override
void InvalidateDungeonRenderContextCensus()
int LoadedRoomCount() const
bool NavigateToAdjacentRoom(DungeonRoomNavigationDirection direction)
void ReloadWaterFillZones()
void InvalidateConnectedRoomGraphs()
ImGuiWindowClass room_window_class_
void SelectObject(int obj_id)
DungeonRoomLoader room_loader_
DungeonCanvasViewer * GetWorkbenchViewer()
std::unordered_map< int, std::shared_ptr< gui::PanelWindow > > room_cards_
absl::Status Redo() override
bool dungeon_render_context_census_shutdown_
void ApplyEntranceRenderContext(int room_id)
bool HasPendingProjectDraftChanges() const
void RemoveViewerFromLru(int room_id)
static constexpr const char * kObjectSelectorId
void ConfigureViewerRenderContext(DungeonCanvasViewer *viewer, int room_id)
void PrepareForSessionTeardown() override
static constexpr const char * kRoomGraphicsId
void QueueRoomCanvasDeleteShortcut()
void QueueWorkbenchWorkflowMode(bool enabled, bool show_toast=true)
static constexpr const char * kRoomMatrixId
void HandleDungeonPaletteChanged(gui::DungeonPaletteChange change)
uint64_t minecart_collision_generation_
static constexpr const char * kRoomSelectorId
DungeonRoomSelector room_selector_
zelda3::GameData * game_data_
PaletteEditorContent * palette_editor_panel_
DungeonSelectionClipboard selection_clipboard_
bool HasPendingDungeonChanges() const
std::unique_ptr< DungeonCanvasViewer > workbench_viewer_
absl::Status PlaceMinecartNode(int room_id, int tile_x, int tile_y, const MinecartNodeBrush &brush)
bool CurrentRoomHasPendingChanges() const
absl::Status RefreshRomBackedState()
bool undo_restore_triggered_ping_
void ReleaseRoomPanelSlotId(int room_id)
absl::Status Load() override
DungeonCanvasViewer * GetStandaloneViewerForRoom(int room_id)
std::unordered_map< int, zelda3::MinecartTrackCollisionOwnership > minecart_collision_ownership_
std::unique_ptr< WaterFillPanel > owned_water_fill_panel_
absl::Status ApplyMinecartCollisionBatch(const std::vector< zelda3::TrackCollisionResult > &preview, const zelda3::GeneratorOptions &options)
DungeonEntrancesPanel * entrance_navigator_panel_
void ProcessPendingStandaloneToolWindow()
static constexpr const char * kPaletteEditorId
DungeonEditorV2(Rom *rom=nullptr)
PendingWorkflowMode pending_workflow_mode_
void InvalidateDungeonPaletteUsers(gui::DungeonPaletteChange change)
void HandleArenaPaletteChanged(const std::string &group_name, int palette_index)
uint64_t current_palette_id_
std::vector< std::string > workbench_suspended_navigation_window_ids_
bool HasUnpublishedMinecartTrackChanges() const
bool OwnsNavigationShortcutFocus() const
void SetProject(const project::YazeProject *project)
void SetObjectPlacedCallback(std::function< void(const zelda3::RoomObject &)> callback)
bool IsObjectLoaded() const
void SetOpenMinecartEditorWindowCallback(std::function< bool()> callback)
void InvalidatePreviewCache()
void SetOpenTileEditorWindowCallback(std::function< bool()> callback)
void SetTileEditorPanel(ObjectTileEditorPanel *panel)
void SynchronizeCustomObjectGeneration()
void SetEditObjectTilesCallback(std::function< absl::Status(int, const zelda3::RoomObject &)> callback)
absl::Status LoadRoom(int room_id, zelda3::Room &room)
absl::Status LoadRoomEntrances(std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > &entrances)
absl::Status LoadDungeonSpawnPoints(std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > &spawn_points)
void SetGameData(zelda3::GameData *game_data)
void SetRoomSelectedWithIntentCallback(std::function< void(int, RoomSelectionIntent)> callback)
void SetEntranceSelectedCallback(std::function< void(int)> callback)
void set_current_room_id(uint16_t room_id)
void set_entrances(std::array< zelda3::RoomEntrance, zelda3::kNumDungeonEntranceSlots > *entrances)
void set_current_entrance_id(int entrance_id)
void set_rooms(DungeonRoomStore *rooms)
void SetRoomSelectedCallback(std::function< void(int)> callback)
void set_spawn_points(std::array< zelda3::DungeonSpawnPoint, zelda3::kNumDungeonSpawnPoints > *spawn_points)
void SetProject(const project::YazeProject *project)
void set_active_rooms(const ImVector< int > &rooms)
zelda3::Room * GetIfMaterialized(int room_id)
zelda3::Room * GetIfLoaded(int room_id)
void SetGameData(zelda3::GameData *game_data)
void ForEachMaterialized(Fn &&fn)
void ForEachLoaded(Fn &&fn)
static void Draw(const DungeonStatusBarState &state)
static DungeonStatusBarState BuildState(const DungeonCanvasViewer &viewer, const char *tool_mode, bool room_dirty)
void SetOpenKeyboardShortcutsCallback(std::function< void()> callback)
void FocusEntranceBrowser()
const std::string & loaded_proposal_path() const
void RequestProposalPreview()
void SetRoomLabelEditCallback(RoomLabelEditCallback callback)
void FocusSelectionInspector()
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)
void SetPrimaryCanvasDrawnCallback(std::function< void(DungeonCanvasViewer &)> cb)
void SetRoomRenderContextCallback(std::function< void(int)> callback)
void SetObjectCoverageContent(WindowContent *object_coverage)
void SetPitDamageTableProvider(std::function< zelda3::PitDamageTable *()> provider, std::function< void()> on_change={})
void OpenRoomGraphicsTool()
void FocusRoomInspector()
void SetStandaloneToolCallbacks(std::function< bool(const std::string &)> is_window_open, std::function< bool(const std::string &)> open_and_focus_window)
void SetEmbeddedEditorPanels(WindowContent *object_selector, WindowContent *door_editor, WindowContent *sprite_editor, WindowContent *item_editor, WindowContent *room_graphics, WindowContent *palette_editor)
void SetEmbeddedToolPanels(RoomTagEditorPanel *room_tags, CustomCollisionPanel *custom_collision, WaterFillPanel *water_fill, MinecartTrackEditorPanel *minecart_tracks)
void NotifyRoomChanged(int previous_room_id)
Called by the editor when the current room changes.
bool LoadProposalOverlay(const std::string &path)
void OpenObjectSelectorTool()
const std::string & proposal_error() const
void SetNavigationPanels(RoomMatrixContent *room_matrix, DungeonEntrancesPanel *entrances)
The EditorManager controls the main editor window and manages the various editor classes.
EditorContext context() const
UndoManager undo_manager_
virtual void SetDependencies(const EditorDependencies &deps)
zelda3::GameData * game_data() const
EditorDependencies dependencies_
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void SetRooms(DungeonRoomStore *rooms)
std::string GetId() const override
Unique identifier for this panel.
void CancelNodePlacement()
void SetNodePlacementCallback(NodePlacementCallback callback)
void SetActiveRoom(int room)
void SetProjectChangedCallback(ProjectChangedCallback callback)
void SetRailPaletteCallback(std::function< bool()> draw)
void DetachRuntimeContext()
absl::Status RebindProjectContext(project::YazeProject *project)
void SetTrackEditCallback(TrackEditCallback callback)
bool HasUnpublishedChanges() const
void SetCollisionBatchApplyCallback(CollisionBatchApplyCallback callback)
absl::Status PrepareProjectSave()
void SetProjectDraftChangedCallback(ProjectDraftChangedCallback callback)
void SetProjectSaveCallback(ProjectSaveCallback callback)
void SetCollisionStampCallback(MinecartCollisionApply apply)
bool HasPendingProjectDraftChanges() const
void SetRooms(DungeonRoomStore *rooms)
void SetProject(project::YazeProject *project)
void SetPlacementError(const std::string &message)
void SetContext(EditorContext ctx)
void SetCurrentRoom(int room_id)
void SetRooms(DungeonRoomStore *rooms)
void SetGameData(zelda3::GameData *game_data)
DungeonObjectSelector & object_selector()
void DetachRuntimeContext()
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
bool DrawMinecartPalette()
void SelectObject(int obj_id)
void SetAgentOptimizedLayout(bool enabled)
void SetStandardWritePreflightCallback(StandardWritePreflightCallback callback)
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
void SetCurrentPaletteGroupForRoom(int room_id, const gfx::PaletteGroup &group)
void SetTilesAppliedCallback(TilesAppliedCallback callback)
int current_room_id() const
absl::Status OpenForObject(int16_t object_id, int room_id, DungeonRoomStore *rooms)
bool HasUnappliedChanges() const
std::string GetId() const override
Unique identifier for this panel.
void DetachRuntimeContext()
void SetCurrentPaletteGroup(const gfx::PaletteGroup &group)
Set the current palette group for graphics rendering.
void SetPreviewEntranceBlocksetResolver(std::function< uint8_t(int)> resolver)
void DetachRuntimeContext()
bool OwnsNavigationShortcutFocus() const
void InvalidatePreviewCache()
void SetRooms(DungeonRoomStore *rooms)
std::string GetDisplayString(const std::string &name) const
Live, platform-formatted binding for a shortcut name.
void SetCanvasViewer(DungeonCanvasViewer *viewer)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f, std::string action_label={}, std::function< void()> on_action=nullptr)
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)
std::string GetWorkspaceWindowName(size_t session_id, const std::string &base_window_id) const
Resolve the exact ImGui window name for a panel by base ID.
size_t GetActiveSessionId() const
void SetWindowPinned(size_t session_id, const std::string &base_window_id, bool pinned)
std::string GetActiveCategory() const
void RegisterWindowContent(std::unique_ptr< WindowContent > window)
Register a WindowContent instance for central drawing.
void RegisterWindow(size_t session_id, const WindowDescriptor &descriptor)
bool IsWindowOpen(size_t session_id, const std::string &base_window_id) const
bool OpenWindow(size_t session_id, const std::string &base_window_id)
bool IsWindowPinned(size_t session_id, const std::string &base_window_id) const
void UnregisterWindow(size_t session_id, const std::string &base_window_id)
static std::shared_ptr< MesenSocketClient > & GetClient()
int RegisterPaletteListener(PaletteChangeCallback callback)
Register a callback for palette change notifications.
void ProcessTextureQueue(IRenderer *renderer)
void UnregisterPaletteListener(int listener_id)
Unregister a palette change listener.
Represents a bitmap image optimized for SNES ROM hacking.
void Initialize(zelda3::GameData *game_data)
Initialize the palette manager with GameData.
static PaletteManager & Get()
Get the singleton instance.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
uint64_t asset_generation() const
static CustomObjectManager & Get()
void RefreshFeatureFlagMappings()
static DrawRoutineRegistry & Get()
static ObjectDimensionTable & Get()
static bool IsEditableStandardObject(int16_t object_id)
Dungeon Room Entrance or Spawn Point.
RoomLayerManager - Manages layer visibility and compositing.
void ApplyGameLayerRegisters(const RoomLayerRegisters ®isters)
void ApplyLayerMerging(const LayerMergeType &merge_type)
void ApplyRoomEffect(EffectKey effect)
void set_water_fill_sram_bit_mask(uint8_t mask)
bool HasUnsavedChanges() const
void ClearWaterFillZone()
void LoadRoomGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
void ClearWaterFillDirty()
int ResolveDungeonPaletteId() const
void SetGameData(GameData *data)
uint8_t resolved_main_blockset() const
void SetWaterFillTile(int x, int y, bool filled)
#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,...)
const AgentUITheme & GetTheme()
bool ProjectAdvertisesMinecartTracks(const project::YazeProject *project)
WindowPresentationPolicy WorkbenchTool(std::string capability=std::string())
auto GraphicsObjectStamp(const zelda3::RoomObject &object)
bool IsTransientDungeonRoomWindowId(const std::string &card_id)
bool SameGraphicsEntrance(const DungeonRenderContext &a, const DungeonRenderContext &b)
bool IsWorkbenchNavigationWindowId(const std::string &card_id)
PreviewImage SnapshotBitmap(const gfx::Bitmap &bitmap)
Copies a palettized Bitmap (for example a room composite).
constexpr const char * kStandalone
Editors are the view controllers for the application.
absl::Status ValidateHackManifestSaveConflicts(const core::HackManifest &manifest, project::RomWritePolicy write_policy, const std::vector< std::pair< uint32_t, uint32_t > > &ranges, absl::string_view save_scope, const char *log_tag, ToastManager *toast_manager)
absl::Status UpdateGraphicsContextPalettes(GraphicsEditContext &context, GraphicsEditContext refreshed)
const char * DungeonRenderContextSourceName(DungeonRenderContextSource source)
std::string PrintShortcut(const std::vector< ImGuiKey > &keys)
absl::StatusOr< GraphicsEditContext > MakeDungeonGraphicsEditContext(const zelda3::Room &room, const zelda3::RoomObject &object, const zelda3::GameData *owner)
RoomSelectionIntent
Intent for room selection in the dungeon editor.
DungeonRoomNavigationDirection
std::string MakePanelTitle(const std::string &title)
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
std::string GetRoomLabel(int id)
Convenience function to get a room label.
absl::StatusOr< std::vector< WaterFillZoneEntry > > LoadLegacyWaterGateZones(Rom *rom, const std::string &symbol_path)
gfx::PaletteGroup BuildDungeonRenderPaletteGroupFromGameData(const gfx::SnesPalette &dungeon_palette, const GameData *game_data)
constexpr int kNumRegularDungeonEntrances
constexpr int kNumDungeonSpawnPoints
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)
#define RETURN_IF_ERROR(expr)
bool kEnableCustomObjects
struct yaze::core::FeatureFlags::Flags::Dungeon dungeon
std::optional< Source > source
DungeonRenderContextSource source
uint8_t room_header_blockset
uint8_t effective_blockset() const
Unified dependency container for all editor types.
project::YazeProject * project
ToastManager * toast_manager
gfx::IRenderer * renderer
ShortcutManager * shortcut_manager
WorkspaceWindowManager * window_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
static WindowPresentationPolicy Diagnostic()
static WindowPresentationPolicy CoreWorkspace()
static WindowPresentationPolicy EmbeddedTool(std::string capability=std::string())
WindowPresentationPolicy WithHost(std::string host_name) const
Returns a copy that names the hosting surface.
static WindowPresentationPolicy OptionalPopOut()
PaletteGroup dungeon_main
Represents a group of palettes.
DungeonRenderPaletteSource source
static constexpr float kStatusBarHeight
Dungeon overlay configuration (per-project).
Modern project structure with comprehensive settings consolidation.
core::HackManifest hack_manifest
std::string GetAbsolutePath(const std::string &relative_path) const
std::string symbols_filename
gfx::PaletteGroupMap palette_groups
PitDamageTable pit_damage_table