From 7a1456da90362c4fc3ce89fa63ffa48d2c7ef86d Mon Sep 17 00:00:00 2001 From: grimsace Date: Mon, 27 Apr 2026 12:21:13 -0500 Subject: [PATCH] made stairs removable --- src/main.rs | 120 +++++++++++++++++++++++++++++++++++------------- src/startend.rs | 25 ++++++---- 2 files changed, 106 insertions(+), 39 deletions(-) diff --git a/src/main.rs b/src/main.rs index 042c931..ed49ebb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,10 +72,11 @@ impl Default for DungeonApp { // Build the app with persisted settings and a generated layout. fn default() -> Self { let settings = settings::load_settings().unwrap_or_default(); - let levels = generate_all_levels(&settings); - Self { + let layouts = generate_all_levels(&settings); + let results = populate_stairs(layouts, &settings); + let mut app = Self { settings, - levels, + levels: results.iter().map(|(l, _)| l.clone()).collect(), suppressed_auto_door_edges: HashSet::new(), undo_stack: Vec::new(), redo_stack: Vec::new(), @@ -91,7 +92,13 @@ impl Default for DungeonApp { hover_marker: None, hover_level_idx: None, hover_stair_idx: None, + }; + for (i, (_, modified)) in results.into_iter().enumerate() { + if modified { + app.refresh_level(i); + } } + app } } @@ -176,23 +183,18 @@ impl eframe::App for DungeonApp { ); let layout = populate_random_markers(layout, &self.settings); self.levels.push(layout); + let results = populate_stairs(self.levels.clone(), &self.settings); + self.levels = results.iter().map(|(l, _)| l.clone()).collect(); + for (i, (_, modified)) in results.into_iter().enumerate() { + if modified { + self.refresh_level(i); + } + } self.settings.active_level_index = self.levels.len() - 1; } - // Check for level deletion keybind when hovering over a level tab. - let level_delete_requested = ctx.input(|i| { - (i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace)) - && self.hover_level_idx.is_some() - }); - if ctx.input(|i| i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace)) { self.pending_delete = true; } - - if level_delete_requested { - if let Some(level_idx) = self.hover_level_idx { - self.delete_level(level_idx); - } - } if panel_result.export_clicked && self.export_rx.is_none() { let target = match exporter::select_export_target(&self.settings) { Ok(target) => target, @@ -320,6 +322,18 @@ impl eframe::App for DungeonApp { let canvas = response.rect.shrink(12.0); let geometry = draw_grid(&painter, canvas, self.settings.cols, self.settings.rows); self.update_hover_targets(ctx, &geometry); + + // Check for level deletion keybind when hovering over a level tab. + let level_delete_requested = ctx.input(|i| { + (i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace)) + && self.hover_level_idx.is_some() + }); + if level_delete_requested { + if let Some(level_idx) = self.hover_level_idx { + self.delete_level(level_idx); + } + } + let resizing = self.handle_resize(ctx, &response, &geometry); if !resizing { self.handle_drag(ctx, &response, &geometry); @@ -382,7 +396,6 @@ fn generate_all_levels(settings: &UiSettings) -> Vec { let layout = populate_random_markers(layout, settings); levels.push(layout); } - let levels = populate_stairs(levels, settings); levels } @@ -504,7 +517,14 @@ impl DungeonApp { fn regenerate_layout(&mut self) { self.drag_state = None; self.suppressed_auto_door_edges.clear(); - self.levels = generate_all_levels(&self.settings); + let layouts = generate_all_levels(&self.settings); + let results = populate_stairs(layouts, &self.settings); + self.levels = results.iter().map(|(l, _)| l.clone()).collect(); + for (i, (_, modified)) in results.into_iter().enumerate() { + if modified { + self.refresh_level(i); + } + } if self.settings.active_level_index >= self.levels.len() { self.settings.active_level_index = 0; } @@ -801,6 +821,58 @@ impl DungeonApp { } // Rebuild doors based on the current layout and settings. + fn refresh_level(&mut self, level_idx: usize) { + if level_idx >= self.levels.len() { + return; + } + + // Reroute all corridors to match resized/moved rooms. + let layout = &mut self.levels[level_idx]; + if !layout.packed_rooms { + let rooms = layout.rooms.clone(); + for corridor in &mut layout.corridors { + let start = rooms[corridor.start_room_id].center_cell(); + let end = rooms[corridor.end_room_id].center_cell(); + let blocked = + blocked_room_cells(&rooms, &[corridor.start_room_id, corridor.end_room_id]); + if let Some(path) = shortest_path_cells( + start, + end, + self.settings.cols, + self.settings.rows, + &blocked, + ) { + corridor.path = path; + } + } + } + + // Rebuild doors. + let door_settings = door_settings_from_ui(&self.settings); + layout::apply_doors( + layout, + self.settings.seed, + door_settings, + self.settings.cols, + self.settings.rows, + ); + layout.doors.retain(|door| { + !door_edges_for(door, self.settings.cols, self.settings.rows) + .iter() + .any(|edge| self.suppressed_auto_door_edges.contains(edge)) + }); + + // Rebuild windows. + let window_settings = window_settings_from_ui(&self.settings); + layout::apply_windows( + layout, + self.settings.seed, + window_settings, + self.settings.cols, + self.settings.rows, + ); + } + fn refresh_doors(&mut self) { if self.settings.active_level_index >= self.levels.len() { return; @@ -1369,13 +1441,6 @@ impl DungeonApp { stair.cell = new_cell; } } - // Ensure room coverage for each stair moved. - let stairs_to_check = layout.stairs.clone(); - for stair in &stairs_to_check { - if stair.cell == new_cell { - startend::ensure_stair_in_room(layout, stair); - } - } } } else { // Move only the linked stairs (current level and adjacent levels if positions match). @@ -1397,13 +1462,6 @@ impl DungeonApp { stair.cell = new_cell; } } - // Ensure room coverage for each stair moved. - let stairs_to_check = layout.stairs.clone(); - for stair in &stairs_to_check { - if stair.cell == new_cell { - startend::ensure_stair_in_room(layout, stair); - } - } } } diff --git a/src/startend.rs b/src/startend.rs index 89bbe07..0620c1c 100644 --- a/src/startend.rs +++ b/src/startend.rs @@ -354,12 +354,13 @@ fn pick_random_corridor_cell( const STAIR_MARKER_STREAM_BASE: u64 = 21_000; // Populate stairs across all levels after markers are placed. +// Returns a vector of tuples: (DungeonLayout, room_modified) pub fn populate_stairs( mut layouts: Vec, settings: &UiSettings, -) -> Vec { +) -> Vec<(DungeonLayout, bool)> { if layouts.is_empty() { - return layouts; + return Vec::new(); } // Clear existing stairs. @@ -368,7 +369,7 @@ pub fn populate_stairs( } if settings.min_stairs_per_level == 0 && settings.max_stairs_per_level == 0 { - return layouts; + return layouts.into_iter().map(|l| (l, false)).collect(); } let num_levels = layouts.len(); @@ -416,15 +417,20 @@ pub fn populate_stairs( layout.stairs.retain(|s| seen.insert(s.cell)); } + let mut result = Vec::new(); // Ensure every stair is inside a room on its level. - for layout in &mut layouts { + for mut layout in layouts { + let mut modified = false; let stairs_clone = layout.stairs.clone(); for stair in &stairs_clone { - ensure_stair_in_room(layout, stair); + if ensure_stair_in_room(&mut layout, stair) { + modified = true; + } } + result.push((layout, modified)); } - layouts + result } fn get_stair_count(settings: &UiSettings, gap_idx: usize) -> usize { @@ -438,7 +444,8 @@ fn get_stair_count(settings: &UiSettings, gap_idx: usize) -> usize { } // Ensure a staircase is fully contained within a room. If not, extend the nearest existing room or create a new one. -pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircase) { +// Returns true if a room was modified or created. +pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircase) -> bool { let stair_x = stair.cell.0; let stair_y = stair.cell.1; let stair_size = stair.size; @@ -450,7 +457,7 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas && (stair_x + stair_size) <= (room.x + room.width) && (stair_y + stair_size) <= (room.y + room.height) { - return; + return false; } } @@ -480,6 +487,7 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas room.y = new_y; room.width = new_right - new_x; room.height = new_bottom - new_y; + true } else { // Otherwise, create a new room for the staircase. layout.rooms.push(layout::Room { @@ -488,6 +496,7 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas width: stair_size, height: stair_size, }); + true } }