96 if (!room || room->rom() != viewer.
rom()) {
97 ImGui::TextWrapped(
"Open a room to edit its destinations.");
101 ImGui::PushID(&viewer);
102 ImGui::PushID(room_id);
103 auto* storage = ImGui::GetStateStorage();
104 const auto route_key = ImGui::GetID(
"DestinationRoute");
105 int route = storage->GetInt(route_key, 0);
107 const auto inspect_key = ImGui::GetID(
"StaircaseInspectResult");
108 const auto selected_key = ImGui::GetID(
"StaircaseInspectSelection");
109 const auto revision_low_key = ImGui::GetID(
"StaircaseInspectRevisionLow");
110 const auto revision_high_key = ImGui::GetID(
"StaircaseInspectRevisionHigh");
111 const auto auto_route_key = ImGui::GetID(
"StaircaseInspectAutoRoute");
113 selection.size() == 1 ?
static_cast<int>(selection.front()) : -1;
114 const uint64_t source = StairLookupSourceFingerprint(*room);
115 const int revision_low =
static_cast<int>(
static_cast<uint32_t
>(source));
116 const int revision_high =
static_cast<int>(source >> 32);
117 if (storage->GetInt(selected_key, -1) != selected ||
118 storage->GetInt(revision_low_key, -1) != revision_low ||
119 storage->GetInt(revision_high_key, -1) != revision_high) {
120 storage->SetInt(selected_key, selected);
121 storage->SetInt(revision_low_key, revision_low);
122 storage->SetInt(revision_high_key, revision_high);
123 storage->SetInt(inspect_key,
124 storage->GetBool(auto_route_key,
false) ? 4 : 0);
126 ImGui::BeginDisabled(selection.size() != 1 || !room->rom() ||
127 room->has_custom_collision());
128 const bool inspect = ImGui::Button(
"Find selected stair slot", ImVec2(-1, 0));
130 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
132 "Select a staircase object to find its header slot. Editing a "
133 "destination does not place a stair or create a return link.");
135 ImGui::EndDisabled();
141 const auto& layout = room->GetLayout().GetObjects();
142 input.objects.insert(input.objects.begin(), layout.begin(), layout.end());
143 const bool custom_objects =
144 std::any_of(input.objects.begin(), input.objects.end(),
146 const auto mappings =
148 ? std::vector<zelda3::StaircaseSlotResolution>{}
151 for (
const auto& mapping : mappings) {
152 if (mapping.object_index == selection.front() + layout.size() &&
154 found = *mapping.slot;
158 storage->SetInt(inspect_key, custom_objects ? 3 : (found >= 0 ? 1 : 2));
161 storage->SetInt(route_key, route);
162 storage->SetBool(auto_route_key,
true);
165 if (room->has_custom_collision()) {
166 ImGui::TextWrapped(
"Stair lookup is unavailable with custom collision.");
167 }
else if (storage->GetInt(inspect_key, 0) == 3) {
169 "Stair lookup is unavailable with active custom object overrides.");
170 }
else if (storage->GetInt(inspect_key, 0) == 1) {
171 ImGui::TextWrapped(
"Selected stair: header slot %d (vanilla preview).",
173 }
else if (storage->GetInt(inspect_key, 0) == 2) {
175 "No unambiguous staircase trigger was found. The selected "
176 "object may not be a supported stair, or its trigger may "
177 "be overwritten, overlapping, outside the room or overflowing.");
178 }
else if (storage->GetInt(inspect_key, 0) == 4) {
180 "Selected stair mapping is stale. Find the slot again. "
181 "The rows below still edit their explicit header slots.");
185 const auto error_key = ImGui::GetID(
"DestinationError");
186 const bool editable =
188 int navigate_to = -1;
189 constexpr const char* planes[] = {
"Upper BG1",
"Lower BG1",
"Lower BG2"};
190 if (ImGui::BeginTable(
191 "##DestinationRows", 4,
192 ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
193 ImGui::TableSetupColumn(
"Route", ImGuiTableColumnFlags_WidthFixed, 49.0f);
194 ImGui::TableSetupColumn(
"Room", ImGuiTableColumnFlags_WidthFixed, 38.0f);
195 ImGui::TableSetupColumn(
"Arrival", ImGuiTableColumnFlags_WidthStretch,
197 ImGui::TableSetupColumn(
"##Open", ImGuiTableColumnFlags_WidthFixed, 22.0f);
198 ImGui::TableHeadersRow();
199 for (
int row = 0; row < 5; ++row) {
201 const bool pit = row == 0;
202 const int index = pit ? 0 : row - 1;
207 int destination = pit ? room->holewarp() : room->staircase_room(index);
208 const int plane = pit ? room->CaptureMetadataSnapshot().pit_target_layer
209 : room->staircase_plane(index);
213 storage->SetBool(error_key, !status.ok());
216 ImGui::TableNextRow();
217 if (row == route && storage->GetInt(inspect_key, 0) == 1) {
218 ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,
219 ImGui::GetColorU32(ImGuiCol_Header));
221 ImGui::TableNextColumn();
222 ImGui::AlignTextToFramePadding();
224 ImGui::TextUnformatted(
"Pit");
226 ImGui::Text(
"Stair %d", row);
227 ImGui::TableNextColumn();
228 ImGui::BeginDisabled(!editable);
229 ImGui::SetNextItemWidth(-1);
232 "##DestinationRoom", ImGuiDataType_S32, &requested,
"%02X",
233 ImGuiInputTextFlags_CharsHexadecimal,
234 {
reinterpret_cast<uintptr_t
>(viewer.
rooms()),
235 static_cast<uint64_t
>(room_id),
static_cast<uint64_t
>(row)})) {
236 if (edit(room_field, requested))
240 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
242 "Destination room byte (hex 00-FF); stays on page %s.",
243 room_id >= 0x100 ?
"1xx" :
"0xx");
245 ImGui::TableNextColumn();
246 const auto plane_label = plane >= 0 && plane < 3
247 ? std::string(planes[plane])
248 : absl::StrFormat(
"%02X Unmapped", plane);
249 ImGui::SetNextItemWidth(-1);
251 ImGui::BeginCombo(
"##DestinationPlane", plane_label.c_str());
254 for (
int value = 0; value < 3; ++value) {
255 if (ImGui::Selectable(planes[value], plane == value))
256 edit(plane_field, value);
258 absl::StrFormat(
"Plane/%d/%d", row, value));
262 ImGui::EndDisabled();
263 ImGui::TableNextColumn();
265 const int destination_room =
268 if (ImGui::Button(
">", ImVec2(-1, 0)))
269 navigate_to = destination_room;
271 ImGui::EndDisabled();
272 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
273 ImGui::SetTooltip(
"Open %03X · %s", destination_room,
282 if (storage->GetBool(error_key,
false)) {
284 "Destination edit rejected. Use room 00–FF and a supported arrival "
288 ImGui::TextWrapped(
"This view is read-only.");
293 if (navigate_to >= 0)