42 void Draw(
bool* p_open)
override {
48 ImGui::TextDisabled(
ICON_MD_INFO " No dungeon rooms loaded.");
54 const bool room_id_valid =
55 (room_id >= 0 && room_id < static_cast<int>(rooms->size()));
58 const bool reserved_region_present =
60 if (!reserved_region_present) {
62 " WaterFill reserved region missing (use an "
63 "expanded-collision Oracle ROM)");
65 "Expected ROM >= 0x%X bytes (WaterFill end). Current ROM is %zu "
72 if (ImGui::Checkbox(
"Show Water Fill Overlay", &show_overlay)) {
77 ImGui::TextUnformatted(
"Authoring");
80 json_options.
filters.push_back({
"Water Fill Zones",
"json"});
81 json_options.
filters.push_back({
"All Files",
"*"});
83 ImGui::BeginDisabled(!reserved_region_present);
95 auto zones = std::move(zones_or.value());
96 for (
const auto& z : zones) {
98 z.room_id >=
static_cast<int>(rooms->size())) {
105 zones.size(), path.c_str());
108 }
catch (
const std::exception& e) {
115 if (ImGui::Button(
ICON_MD_TUNE " Normalize Masks Now")) {
123 for (
const auto& z : zones) {
124 auto& r = (*rooms)[z.room_id];
125 if (r.water_fill_sram_bit_mask() != z.sram_bit_mask) {
126 r.set_water_fill_sram_bit_mask(z.sram_bit_mask);
134 absl::StrFormat(
"Normalized masks (%d room(s) updated)", changed);
138 ImGui::EndDisabled();
149 "water_fill_zones.json",
"json");
151 std::ofstream file(path);
152 if (!file.is_open()) {
154 absl::StrFormat(
"Cannot write file: %s", path.c_str());
160 zones.size(), path.c_str());
176 "Import/export uses a room-indexed JSON format. Normalize masks before "
177 "saving to avoid duplicate SRAM bits.");
180 if (!room_id_valid) {
183 auto& room = (*rooms)[room_id];
184 const bool room_loaded = room.IsLoaded();
188 " Room not loaded yet (open it to paint and validate sprites).");
192 ImGui::TextDisabled(
"Painting requires an active interaction context.");
196 int brush_radius = std::clamp(state.paint_brush_radius, 0, 8);
197 if (ImGui::SliderInt(
"Brush Radius", &brush_radius, 0, 8)) {
198 state.paint_brush_radius = brush_radius;
201 ImGui::TextDisabled(
"%dx%d", (brush_radius * 2) + 1,
202 (brush_radius * 2) + 1);
206 const bool can_paint = reserved_region_present && room_loaded;
207 ImGui::BeginDisabled(!can_paint);
208 if (ImGui::Checkbox(
"Paint Mode", &is_painting)) {
217 ImGui::EndDisabled();
220 ImGui::TextColored(theme.text_warning_yellow,
221 "Left-drag paints; Alt-drag erases");
225 const int tile_count = room.WaterFillTileCount();
227 ImGui::Text(
"Zone Tiles: %d", tile_count);
228 if (tile_count > 255) {
229 ImGui::TextColored(theme.status_error,
234 bool has_switch_sprite =
false;
235 for (
const auto& spr : room.GetSprites()) {
236 if (spr.id() == 0x04 || spr.id() == 0x21) {
237 has_switch_sprite =
true;
241 if (!has_switch_sprite) {
244 " No PullSwitch (0x04) / PushSwitch (0x21) sprite found");
247 ImGui::TextDisabled(
"Sprite checks require the room to be loaded.");
251 uint8_t mask = room.water_fill_sram_bit_mask();
252 std::string preview =
253 (mask == 0) ?
"Auto (0x00)" : absl::StrFormat(
"0x%02X", mask);
254 ImGui::BeginDisabled(!reserved_region_present);
255 if (ImGui::BeginCombo(
"SRAM Bit Mask ($7EF411)", preview.c_str())) {
256 auto option = [&](
const char* label, uint8_t val) {
257 const bool selected = (mask == val);
258 if (ImGui::Selectable(label, selected)) {
259 room.set_water_fill_sram_bit_mask(val);
264 option(
"Auto (0x00)", 0x00);
265 option(
"Bit 0 (0x01)", 0x01);
266 option(
"Bit 1 (0x02)", 0x02);
267 option(
"Bit 2 (0x04)", 0x04);
268 option(
"Bit 3 (0x08)", 0x08);
269 option(
"Bit 4 (0x10)", 0x10);
270 option(
"Bit 5 (0x20)", 0x20);
271 option(
"Bit 6 (0x40)", 0x40);
272 option(
"Bit 7 (0x80)", 0x80);
278 if (ImGui::Button(
"Clear Water Fill Zone")) {
279 room.ClearWaterFillZone();
281 ImGui::EndDisabled();
284 "Water fill zones are serialized as compact tile offset lists. "
285 "Keep zones under 255 tiles per room.");
291 if (ImGui::CollapsingHeader(
"Zone Overview",
292 ImGuiTreeNodeFlags_DefaultOpen)) {
300 std::vector<ZoneRow> rows;
302 std::unordered_map<uint8_t, int> mask_counts;
303 int rooms_over_tile_limit = 0;
304 int rooms_unassigned_mask = 0;
306 for (
int rid = 0; rid < static_cast<int>(rooms->size()); ++rid) {
307 auto& r = (*rooms)[rid];
308 const int tiles = r.WaterFillTileCount();
311 rows.push_back(ZoneRow{rid, tiles, r.water_fill_sram_bit_mask(),
312 r.water_fill_dirty()});
314 rooms_over_tile_limit++;
316 if (r.water_fill_sram_bit_mask() == 0) {
317 rooms_unassigned_mask++;
319 mask_counts[r.water_fill_sram_bit_mask()]++;
323 std::sort(rows.begin(), rows.end(),
324 [](
const ZoneRow& a,
const ZoneRow& b) {
325 return a.room_id < b.room_id;
328 int duplicate_masks = 0;
329 for (
const auto& [mask, count] : mask_counts) {
330 if (mask != 0 && count > 1) {
335 ImGui::Text(
"Rooms with zones: %zu / 8", rows.size());
336 if (rows.size() > 8) {
337 ImGui::TextColored(theme.status_error,
340 if (rooms_over_tile_limit > 0) {
341 ImGui::TextColored(theme.status_error,
343 rooms_over_tile_limit);
345 if (duplicate_masks > 0) {
346 ImGui::TextColored(theme.status_error,
348 " Duplicate SRAM bit masks detected (%d mask(s))",
351 if (rooms_unassigned_mask > 0) {
352 ImGui::TextColored(theme.text_warning_yellow,
354 " %d room(s) use Auto mask (assigned on save)",
355 rooms_unassigned_mask);
358 if (ImGui::BeginTable(
"##WaterFillZoneOverview", 6,
359 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
360 ImGuiTableFlags_SizingFixedFit)) {
361 ImGui::TableSetupColumn(
"Room");
362 ImGui::TableSetupColumn(
"Tiles");
363 ImGui::TableSetupColumn(
"Mask");
364 ImGui::TableSetupColumn(
"Dirty");
365 ImGui::TableSetupColumn(
"Dup?");
366 ImGui::TableSetupColumn(
"Action");
367 ImGui::TableHeadersRow();
369 for (
const auto& row : rows) {
370 const bool is_current = (row.room_id == room_id);
372 (row.mask != 0 && mask_counts.contains(row.mask) &&
373 mask_counts[row.mask] > 1);
375 ImGui::TableNextRow();
376 ImGui::TableNextColumn();
378 ImGui::TextColored(theme.text_info,
"0x%02X", row.room_id);
380 ImGui::Text(
"0x%02X", row.room_id);
383 ImGui::TableNextColumn();
384 ImGui::Text(
"%d", row.tiles);
386 ImGui::TableNextColumn();
388 ImGui::TextDisabled(
"Auto");
390 ImGui::Text(
"0x%02X", row.mask);
393 ImGui::TableNextColumn();
394 ImGui::TextUnformatted(row.dirty ?
"Yes" :
"No");
396 ImGui::TableNextColumn();
400 ImGui::TextDisabled(
"-");
403 ImGui::TableNextColumn();
405 ImGui::PushID(row.room_id);
406 if (ImGui::SmallButton(
"Open")) {
411 ImGui::TextDisabled(
"-");