41 void Draw(
bool* p_open)
override {
47 ImGui::TextDisabled(
ICON_MD_INFO " No dungeon rooms loaded.");
53 const bool room_id_valid =
54 (room_id >= 0 && room_id < static_cast<int>(rooms->size()));
57 const bool reserved_region_present =
59 if (!reserved_region_present) {
61 " WaterFill reserved region missing (use an "
62 "expanded-collision Oracle ROM)");
64 "Expected ROM >= 0x%X bytes (WaterFill end). Current ROM is %zu "
71 if (ImGui::Checkbox(
"Show Water Fill Overlay", &show_overlay)) {
76 ImGui::TextUnformatted(
"Authoring");
79 json_options.
filters.push_back({
"Water Fill Zones",
"json"});
80 json_options.
filters.push_back({
"All Files",
"*"});
82 ImGui::BeginDisabled(!reserved_region_present);
94 auto zones = std::move(zones_or.value());
95 for (
const auto& z : zones) {
97 z.room_id >=
static_cast<int>(rooms->size())) {
104 zones.size(), path.c_str());
107 }
catch (
const std::exception& e) {
114 if (ImGui::Button(
ICON_MD_TUNE " Normalize Masks Now")) {
122 for (
const auto& z : zones) {
123 auto& r = (*rooms)[z.room_id];
124 if (r.water_fill_sram_bit_mask() != z.sram_bit_mask) {
125 r.set_water_fill_sram_bit_mask(z.sram_bit_mask);
133 absl::StrFormat(
"Normalized masks (%d room(s) updated)", changed);
137 ImGui::EndDisabled();
148 "water_fill_zones.json",
"json");
150 std::ofstream file(path);
151 if (!file.is_open()) {
153 absl::StrFormat(
"Cannot write file: %s", path.c_str());
159 zones.size(), path.c_str());
175 "Import/export uses a room-indexed JSON format. Normalize masks before "
176 "saving to avoid duplicate SRAM bits.");
179 if (!room_id_valid) {
182 auto& room = (*rooms)[room_id];
183 const bool room_loaded = room.IsLoaded();
187 " Room not loaded yet (open it to paint and validate sprites).");
191 ImGui::TextDisabled(
"Painting requires an active interaction context.");
195 int brush_radius = std::clamp(state.paint_brush_radius, 0, 8);
196 if (ImGui::SliderInt(
"Brush Radius", &brush_radius, 0, 8)) {
197 state.paint_brush_radius = brush_radius;
200 ImGui::TextDisabled(
"%dx%d", (brush_radius * 2) + 1,
201 (brush_radius * 2) + 1);
205 const bool can_paint = reserved_region_present && room_loaded;
206 ImGui::BeginDisabled(!can_paint);
207 if (ImGui::Checkbox(
"Paint Mode", &is_painting)) {
216 ImGui::EndDisabled();
219 ImGui::TextColored(theme.text_warning_yellow,
220 "Left-drag paints; Alt-drag erases");
224 const int tile_count = room.WaterFillTileCount();
226 ImGui::Text(
"Zone Tiles: %d", tile_count);
227 if (tile_count > 255) {
228 ImGui::TextColored(theme.status_error,
233 bool has_switch_sprite =
false;
234 for (
const auto& spr : room.GetSprites()) {
235 if (spr.id() == 0x04 || spr.id() == 0x21) {
236 has_switch_sprite =
true;
240 if (!has_switch_sprite) {
243 " No PullSwitch (0x04) / PushSwitch (0x21) sprite found");
246 ImGui::TextDisabled(
"Sprite checks require the room to be loaded.");
250 uint8_t mask = room.water_fill_sram_bit_mask();
251 std::string preview =
252 (mask == 0) ?
"Auto (0x00)" : absl::StrFormat(
"0x%02X", mask);
253 ImGui::BeginDisabled(!reserved_region_present);
254 if (ImGui::BeginCombo(
"SRAM Bit Mask ($7EF411)", preview.c_str())) {
255 auto option = [&](
const char* label, uint8_t val) {
256 const bool selected = (mask == val);
257 if (ImGui::Selectable(label, selected)) {
258 room.set_water_fill_sram_bit_mask(val);
263 option(
"Auto (0x00)", 0x00);
264 option(
"Bit 0 (0x01)", 0x01);
265 option(
"Bit 1 (0x02)", 0x02);
266 option(
"Bit 2 (0x04)", 0x04);
267 option(
"Bit 3 (0x08)", 0x08);
268 option(
"Bit 4 (0x10)", 0x10);
269 option(
"Bit 5 (0x20)", 0x20);
270 option(
"Bit 6 (0x40)", 0x40);
271 option(
"Bit 7 (0x80)", 0x80);
277 if (ImGui::Button(
"Clear Water Fill Zone")) {
278 room.ClearWaterFillZone();
280 ImGui::EndDisabled();
283 "Water fill zones are serialized as compact tile offset lists. "
284 "Keep zones under 255 tiles per room.");
290 if (ImGui::CollapsingHeader(
"Zone Overview",
291 ImGuiTreeNodeFlags_DefaultOpen)) {
299 std::vector<ZoneRow> rows;
301 std::unordered_map<uint8_t, int> mask_counts;
302 int rooms_over_tile_limit = 0;
303 int rooms_unassigned_mask = 0;
305 for (
int rid = 0; rid < static_cast<int>(rooms->size()); ++rid) {
306 auto& r = (*rooms)[rid];
307 const int tiles = r.WaterFillTileCount();
310 rows.push_back(ZoneRow{rid, tiles, r.water_fill_sram_bit_mask(),
311 r.water_fill_dirty()});
313 rooms_over_tile_limit++;
315 if (r.water_fill_sram_bit_mask() == 0) {
316 rooms_unassigned_mask++;
318 mask_counts[r.water_fill_sram_bit_mask()]++;
322 std::sort(rows.begin(), rows.end(),
323 [](
const ZoneRow& a,
const ZoneRow& b) {
324 return a.room_id < b.room_id;
327 int duplicate_masks = 0;
328 for (
const auto& [mask, count] : mask_counts) {
329 if (mask != 0 && count > 1) {
334 ImGui::Text(
"Rooms with zones: %zu / 8", rows.size());
335 if (rows.size() > 8) {
336 ImGui::TextColored(theme.status_error,
339 if (rooms_over_tile_limit > 0) {
340 ImGui::TextColored(theme.status_error,
342 rooms_over_tile_limit);
344 if (duplicate_masks > 0) {
345 ImGui::TextColored(theme.status_error,
347 " Duplicate SRAM bit masks detected (%d mask(s))",
350 if (rooms_unassigned_mask > 0) {
351 ImGui::TextColored(theme.text_warning_yellow,
353 " %d room(s) use Auto mask (assigned on save)",
354 rooms_unassigned_mask);
357 if (ImGui::BeginTable(
"##WaterFillZoneOverview", 6,
358 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
359 ImGuiTableFlags_SizingFixedFit)) {
360 ImGui::TableSetupColumn(
"Room");
361 ImGui::TableSetupColumn(
"Tiles");
362 ImGui::TableSetupColumn(
"Mask");
363 ImGui::TableSetupColumn(
"Dirty");
364 ImGui::TableSetupColumn(
"Dup?");
365 ImGui::TableSetupColumn(
"Action");
366 ImGui::TableHeadersRow();
368 for (
const auto& row : rows) {
369 const bool is_current = (row.room_id == room_id);
371 (row.mask != 0 && mask_counts.contains(row.mask) &&
372 mask_counts[row.mask] > 1);
374 ImGui::TableNextRow();
375 ImGui::TableNextColumn();
377 ImGui::TextColored(theme.text_info,
"0x%02X", row.room_id);
379 ImGui::Text(
"0x%02X", row.room_id);
382 ImGui::TableNextColumn();
383 ImGui::Text(
"%d", row.tiles);
385 ImGui::TableNextColumn();
387 ImGui::TextDisabled(
"Auto");
389 ImGui::Text(
"0x%02X", row.mask);
392 ImGui::TableNextColumn();
393 ImGui::TextUnformatted(row.dirty ?
"Yes" :
"No");
395 ImGui::TableNextColumn();
399 ImGui::TextDisabled(
"-");
402 ImGui::TableNextColumn();
404 ImGui::PushID(row.room_id);
405 if (ImGui::SmallButton(
"Open")) {
410 ImGui::TextDisabled(
"-");