30 ImGui::TextDisabled(
ICON_MD_INFO " No dungeon rooms loaded.");
37 const bool room_id_valid =
38 (room_id >= 0 && room_id < static_cast<int>(rooms->size()));
41 const bool reserved_region_present =
43 const bool save_enabled =
45 if (!reserved_region_present) {
47 " WaterFill reserved region missing (use an "
48 "expanded-collision Oracle ROM)");
50 tr(
"Expected ROM >= 0x%X bytes (WaterFill end). Current ROM is %zu "
57 ImGui::TextColored(theme.text_warning_yellow,
ICON_MD_LOCK
58 " Read-only: Water Fill saving is disabled");
60 tr(
"Set save_dungeon_water_fill_zones=true in the .yaze project and "
61 "reopen it before authoring zones."));
70 if (ImGui::Checkbox(tr(
"Show Water Fill Overlay"), &show_overlay)) {
75 ImGui::TextUnformatted(tr(
"Authoring"));
78 json_options.
filters.push_back({
"Water Fill Zones",
"json"});
79 json_options.
filters.push_back({
"All Files",
"*"});
81 ImGui::BeginDisabled(!reserved_region_present || !save_enabled);
93 auto zones = std::move(zones_or.value());
94 for (
const auto& z : zones) {
95 if (z.room_id < 0 || z.room_id >=
static_cast<int>(rooms->size())) {
102 zones.size(), path.c_str());
105 }
catch (
const std::exception& e) {
112 if (ImGui::Button(
ICON_MD_TUNE " Normalize Masks Now")) {
120 for (
const auto& z : zones) {
121 auto& r = (*rooms)[z.room_id];
122 if (r.water_fill_sram_bit_mask() != z.sram_bit_mask) {
123 r.set_water_fill_sram_bit_mask(z.sram_bit_mask);
131 absl::StrFormat(
"Normalized masks (%d room(s) updated)", changed);
135 ImGui::EndDisabled();
146 "water_fill_zones.json",
"json");
148 std::ofstream file(path);
149 if (!file.is_open()) {
151 absl::StrFormat(
"Cannot write file: %s", path.c_str());
157 zones.size(), path.c_str());
172 ImGui::TextWrapped(tr(
173 "Import/export uses a room-indexed JSON format. Normalize masks before "
174 "saving to avoid duplicate SRAM bits."));
177 if (!room_id_valid) {
180 auto& room = (*rooms)[room_id];
181 const bool room_loaded = room.IsLoaded();
185 " Room not loaded yet (open it to paint and validate sprites).");
190 tr(
"Painting requires an active interaction context."));
194 int brush_radius = std::clamp(state.paint_brush_radius, 0, 8);
195 if (ImGui::SliderInt(tr(
"Brush Radius"), &brush_radius, 0, 8)) {
196 state.paint_brush_radius = brush_radius;
199 ImGui::TextDisabled(tr(
"%dx%d"), (brush_radius * 2) + 1,
200 (brush_radius * 2) + 1);
204 const bool can_paint =
205 reserved_region_present && room_loaded && save_enabled;
206 ImGui::BeginDisabled(!can_paint);
207 if (ImGui::Checkbox(tr(
"Paint Mode"), &is_painting)) {
215 ImGui::EndDisabled();
218 ImGui::TextColored(theme.text_warning_yellow,
219 tr(
"Left-drag paints; Alt-drag erases"));
223 const int tile_count = room.WaterFillTileCount();
225 ImGui::Text(tr(
"Zone Tiles: %d"), tile_count);
226 if (tile_count > 255) {
227 ImGui::TextColored(theme.status_error,
232 bool has_switch_sprite =
false;
233 for (
const auto& spr : room.GetSprites()) {
234 if (spr.id() == 0x04 || spr.id() == 0x21) {
235 has_switch_sprite =
true;
239 if (!has_switch_sprite) {
242 " No PullSwitch (0x04) / PushSwitch (0x21) sprite found");
245 ImGui::TextDisabled(tr(
"Sprite checks require the room to be loaded."));
249 uint8_t mask = room.water_fill_sram_bit_mask();
250 std::string preview =
251 (mask == 0) ?
"Auto (0x00)" : absl::StrFormat(
"0x%02X", mask);
252 ImGui::BeginDisabled(!reserved_region_present || !save_enabled ||
254 const bool mask_open =
255 ImGui::BeginCombo(tr(
"SRAM Bit Mask ($7EF411)"), preview.c_str());
258 auto option = [&](
const char* label, uint8_t val) {
259 const bool selected = (mask == val);
260 if (ImGui::Selectable(label, selected) && mask != val) {
267 absl::StrFormat(
"WaterFillMask%02X", val));
270 option(
"Auto (0x00)", 0x00);
271 option(
"Bit 0 (0x01)", 0x01);
272 option(
"Bit 1 (0x02)", 0x02);
273 option(
"Bit 2 (0x04)", 0x04);
274 option(
"Bit 3 (0x08)", 0x08);
275 option(
"Bit 4 (0x10)", 0x10);
276 option(
"Bit 5 (0x20)", 0x20);
277 option(
"Bit 6 (0x40)", 0x40);
278 option(
"Bit 7 (0x80)", 0x80);
284 if (ImGui::Button(tr(
"Clear Water Fill Zone")) &&
285 (room.WaterFillTileCount() != 0 ||
286 room.water_fill_sram_bit_mask() != 0)) {
291 ImGui::EndDisabled();
294 tr(
"Water fill zones are serialized as compact tile offset lists. "
295 "Keep zones under 255 tiles per room."));
301 if (ImGui::CollapsingHeader(tr(
"Zone Overview"),
302 ImGuiTreeNodeFlags_DefaultOpen)) {
310 std::vector<ZoneRow> rows;
312 std::unordered_map<uint8_t, int> mask_counts;
313 int rooms_over_tile_limit = 0;
314 int rooms_unassigned_mask = 0;
316 for (
int rid = 0; rid < static_cast<int>(rooms->size()); ++rid) {
317 const auto* r = rooms->GetIfMaterialized(rid);
321 const int tiles = r->WaterFillTileCount();
325 rows.push_back(ZoneRow{rid, tiles, r->water_fill_sram_bit_mask(),
326 r->water_fill_dirty()});
328 rooms_over_tile_limit++;
330 if (r->water_fill_sram_bit_mask() == 0) {
331 rooms_unassigned_mask++;
333 mask_counts[r->water_fill_sram_bit_mask()]++;
337 std::sort(rows.begin(), rows.end(), [](
const ZoneRow& a,
const ZoneRow& b) {
338 return a.room_id < b.room_id;
341 int duplicate_masks = 0;
342 for (
const auto& [mask, count] : mask_counts) {
343 if (mask != 0 && count > 1) {
348 ImGui::Text(tr(
"Rooms with zones: %zu / 8"), rows.size());
349 if (rows.size() > 8) {
350 ImGui::TextColored(theme.status_error,
353 if (rooms_over_tile_limit > 0) {
354 ImGui::TextColored(theme.status_error,
356 rooms_over_tile_limit);
358 if (duplicate_masks > 0) {
359 ImGui::TextColored(theme.status_error,
361 " Duplicate SRAM bit masks detected (%d mask(s))",
364 if (rooms_unassigned_mask > 0) {
365 ImGui::TextColored(theme.text_warning_yellow,
367 " %d room(s) use Auto mask (assigned on save)",
368 rooms_unassigned_mask);
371 if (ImGui::BeginTable(
"##WaterFillZoneOverview", 6,
372 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
373 ImGuiTableFlags_SizingFixedFit)) {
374 ImGui::TableSetupColumn(
"Room");
375 ImGui::TableSetupColumn(
"Tiles");
376 ImGui::TableSetupColumn(
"Mask");
377 ImGui::TableSetupColumn(
"Dirty");
378 ImGui::TableSetupColumn(
"Dup?");
379 ImGui::TableSetupColumn(
"Action");
380 ImGui::TableHeadersRow();
382 for (
const auto& row : rows) {
383 const bool is_current = (row.room_id == room_id);
384 const bool is_dup = (row.mask != 0 && mask_counts.contains(row.mask) &&
385 mask_counts[row.mask] > 1);
387 ImGui::TableNextRow();
388 ImGui::TableNextColumn();
390 ImGui::TextColored(theme.text_info, tr(
"0x%02X"), row.room_id);
392 ImGui::Text(tr(
"0x%02X"), row.room_id);
395 ImGui::TableNextColumn();
396 ImGui::Text(
"%d", row.tiles);
398 ImGui::TableNextColumn();
400 ImGui::TextDisabled(tr(
"Auto"));
402 ImGui::Text(tr(
"0x%02X"), row.mask);
405 ImGui::TableNextColumn();
406 ImGui::TextUnformatted(row.dirty ?
"Yes" :
"No");
408 ImGui::TableNextColumn();
412 ImGui::TextDisabled(
"-");
415 ImGui::TableNextColumn();
417 ImGui::PushID(row.room_id);
418 if (ImGui::SmallButton(tr(
"Open"))) {
423 ImGui::TextDisabled(
"-");