made stairs removable
This commit is contained in:
+89
-31
@@ -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,8 +92,14 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DungeonApp {
|
||||
@@ -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<DungeonLayout> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-8
@@ -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<DungeonLayout>,
|
||||
settings: &UiSettings,
|
||||
) -> Vec<DungeonLayout> {
|
||||
) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user