41 void Draw(
bool* p_open)
override {
47 ImGui::TextDisabled(
ICON_MD_INFO " No dungeon rooms loaded.");
52 const int ptr_table_end =
54 const bool collision_table_present =
56 const bool collision_data_region_present =
58 const bool collision_save_supported =
60 if (!collision_table_present) {
62 " Custom collision table missing (use an "
63 "expanded-collision Oracle ROM)");
65 "Expected ROM >= 0x%X bytes (custom collision pointer table end). "
66 "Current ROM is %zu bytes.",
67 ptr_table_end, rom_size);
69 }
else if (!collision_data_region_present) {
71 " Custom collision data region missing/truncated");
73 "Expected ROM >= 0x%X bytes (custom collision data soft end). "
74 "Current ROM is %zu bytes.",
81 if (room_id < 0 || room_id >= 296) {
86 auto& room = (*rooms)[room_id];
87 bool has_collision = room.has_custom_collision();
89 ImGui::BeginDisabled(!collision_save_supported);
90 if (ImGui::Button(has_collision ?
"Disable Custom Collision"
91 :
"Enable Custom Collision")) {
92 room.set_has_custom_collision(!has_collision);
99 ImGui::TextUnformatted(
"Authoring");
102 json_options.
filters.push_back({
"Custom Collision",
"json"});
103 json_options.
filters.push_back({
"All Files",
"*"});
105 ImGui::BeginDisabled(!collision_save_supported);
114 if (!rooms_or.ok()) {
118 const auto imported = std::move(rooms_or.value());
119 for (
const auto& entry : imported) {
120 if (entry.room_id < 0 ||
121 entry.room_id >=
static_cast<int>(rooms->size())) {
128 imported.size(), path.c_str());
131 }
catch (
const std::exception& e) {
146 "custom_collision.json",
"json");
148 std::ofstream file(path);
149 if (!file.is_open()) {
151 absl::StrFormat(
"Cannot write file: %s", path.c_str());
157 exported.size(), path.c_str());
163 ImGui::EndDisabled();
176 if (!collision_save_supported) {
178 " This ROM cannot save custom collision edits "
179 "(expanded collision region missing).");
183 if (ImGui::Checkbox(
"Show Collision Overlay", &show_overlay)) {
188 ImGui::TextDisabled(
"Painting requires an active interaction context.");
194 ImGui::BeginDisabled(!collision_save_supported);
195 if (ImGui::Checkbox(
"Paint Mode", &is_painting)) {
202 ImGui::EndDisabled();
205 ImGui::TextColored(theme.text_warning_yellow,
206 "Click/Drag on canvas to paint");
211 int brush_radius = std::clamp(state.paint_brush_radius, 0, 8);
212 if (ImGui::SliderInt(
"Brush Radius", &brush_radius, 0, 8)) {
213 state.paint_brush_radius = brush_radius;
216 ImGui::TextDisabled(
"%dx%d", (brush_radius * 2) + 1,
217 (brush_radius * 2) + 1);
221 if (ImGui::BeginCombo(
"Collision Type",
222 absl::StrFormat(
"%02X: %s", current_val,
223 (current_val < tile_types.size()
224 ? tile_types[current_val]
227 for (
int i = 0; i < tile_types.size(); ++i) {
228 bool selected = (current_val == i);
229 if (ImGui::Selectable(
230 absl::StrFormat(
"%02X: %s", i, tile_types[i]).c_str(),
232 state.paint_collision_value =
static_cast<uint8_t
>(i);
239 ImGui::Text(
"Quick Select:");
240 auto quick_button = [&](
const char* label, uint8_t val) {
241 if (ImGui::Button(label)) {
242 state.paint_collision_value = val;
245 quick_button(
"Floor (00)", 0x00);
247 quick_button(
"Solid (02)", 0x02);
249 quick_button(
"D.Water (08)", 0x08);
250 quick_button(
"S.Water (09)", 0x09);
252 quick_button(
"Pit (1B)", 0x1B);
254 quick_button(
"Spikes (0E)", 0x0E);
258 ImGui::BeginDisabled(!collision_save_supported);
259 if (ImGui::Button(
"Clear All Custom Collision")) {
260 room.custom_collision().tiles.fill(0);
262 room.custom_collision().has_data =
false;
263 room.MarkCustomCollisionDirty();
266 ImGui::EndDisabled();
269 "Custom collision allows you to override the physics of individual "
270 "8x8 tiles in the room. This is useful for creating water, pits, or "
271 "other effects that don't match the background tiles.");