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.
|
// Build the app with persisted settings and a generated layout.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let settings = settings::load_settings().unwrap_or_default();
|
let settings = settings::load_settings().unwrap_or_default();
|
||||||
let levels = generate_all_levels(&settings);
|
let layouts = generate_all_levels(&settings);
|
||||||
Self {
|
let results = populate_stairs(layouts, &settings);
|
||||||
|
let mut app = Self {
|
||||||
settings,
|
settings,
|
||||||
levels,
|
levels: results.iter().map(|(l, _)| l.clone()).collect(),
|
||||||
suppressed_auto_door_edges: HashSet::new(),
|
suppressed_auto_door_edges: HashSet::new(),
|
||||||
undo_stack: Vec::new(),
|
undo_stack: Vec::new(),
|
||||||
redo_stack: Vec::new(),
|
redo_stack: Vec::new(),
|
||||||
@@ -91,7 +92,13 @@ impl Default for DungeonApp {
|
|||||||
hover_marker: None,
|
hover_marker: None,
|
||||||
hover_level_idx: None,
|
hover_level_idx: None,
|
||||||
hover_stair_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);
|
let layout = populate_random_markers(layout, &self.settings);
|
||||||
self.levels.push(layout);
|
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;
|
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)) {
|
if ctx.input(|i| i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace)) {
|
||||||
self.pending_delete = true;
|
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() {
|
if panel_result.export_clicked && self.export_rx.is_none() {
|
||||||
let target = match exporter::select_export_target(&self.settings) {
|
let target = match exporter::select_export_target(&self.settings) {
|
||||||
Ok(target) => target,
|
Ok(target) => target,
|
||||||
@@ -320,6 +322,18 @@ impl eframe::App for DungeonApp {
|
|||||||
let canvas = response.rect.shrink(12.0);
|
let canvas = response.rect.shrink(12.0);
|
||||||
let geometry = draw_grid(&painter, canvas, self.settings.cols, self.settings.rows);
|
let geometry = draw_grid(&painter, canvas, self.settings.cols, self.settings.rows);
|
||||||
self.update_hover_targets(ctx, &geometry);
|
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);
|
let resizing = self.handle_resize(ctx, &response, &geometry);
|
||||||
if !resizing {
|
if !resizing {
|
||||||
self.handle_drag(ctx, &response, &geometry);
|
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);
|
let layout = populate_random_markers(layout, settings);
|
||||||
levels.push(layout);
|
levels.push(layout);
|
||||||
}
|
}
|
||||||
let levels = populate_stairs(levels, settings);
|
|
||||||
levels
|
levels
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +517,14 @@ impl DungeonApp {
|
|||||||
fn regenerate_layout(&mut self) {
|
fn regenerate_layout(&mut self) {
|
||||||
self.drag_state = None;
|
self.drag_state = None;
|
||||||
self.suppressed_auto_door_edges.clear();
|
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() {
|
if self.settings.active_level_index >= self.levels.len() {
|
||||||
self.settings.active_level_index = 0;
|
self.settings.active_level_index = 0;
|
||||||
}
|
}
|
||||||
@@ -801,6 +821,58 @@ impl DungeonApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild doors based on the current layout and settings.
|
// 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) {
|
fn refresh_doors(&mut self) {
|
||||||
if self.settings.active_level_index >= self.levels.len() {
|
if self.settings.active_level_index >= self.levels.len() {
|
||||||
return;
|
return;
|
||||||
@@ -1369,13 +1441,6 @@ impl DungeonApp {
|
|||||||
stair.cell = new_cell;
|
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 {
|
} else {
|
||||||
// Move only the linked stairs (current level and adjacent levels if positions match).
|
// Move only the linked stairs (current level and adjacent levels if positions match).
|
||||||
@@ -1397,13 +1462,6 @@ impl DungeonApp {
|
|||||||
stair.cell = new_cell;
|
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;
|
const STAIR_MARKER_STREAM_BASE: u64 = 21_000;
|
||||||
|
|
||||||
// Populate stairs across all levels after markers are placed.
|
// Populate stairs across all levels after markers are placed.
|
||||||
|
// Returns a vector of tuples: (DungeonLayout, room_modified)
|
||||||
pub fn populate_stairs(
|
pub fn populate_stairs(
|
||||||
mut layouts: Vec<DungeonLayout>,
|
mut layouts: Vec<DungeonLayout>,
|
||||||
settings: &UiSettings,
|
settings: &UiSettings,
|
||||||
) -> Vec<DungeonLayout> {
|
) -> Vec<(DungeonLayout, bool)> {
|
||||||
if layouts.is_empty() {
|
if layouts.is_empty() {
|
||||||
return layouts;
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear existing stairs.
|
// Clear existing stairs.
|
||||||
@@ -368,7 +369,7 @@ pub fn populate_stairs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if settings.min_stairs_per_level == 0 && settings.max_stairs_per_level == 0 {
|
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();
|
let num_levels = layouts.len();
|
||||||
@@ -416,15 +417,20 @@ pub fn populate_stairs(
|
|||||||
layout.stairs.retain(|s| seen.insert(s.cell));
|
layout.stairs.retain(|s| seen.insert(s.cell));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
// Ensure every stair is inside a room on its level.
|
// 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();
|
let stairs_clone = layout.stairs.clone();
|
||||||
for stair in &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 {
|
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.
|
// 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_x = stair.cell.0;
|
||||||
let stair_y = stair.cell.1;
|
let stair_y = stair.cell.1;
|
||||||
let stair_size = stair.size;
|
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_x + stair_size) <= (room.x + room.width)
|
||||||
&& (stair_y + stair_size) <= (room.y + room.height)
|
&& (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.y = new_y;
|
||||||
room.width = new_right - new_x;
|
room.width = new_right - new_x;
|
||||||
room.height = new_bottom - new_y;
|
room.height = new_bottom - new_y;
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, create a new room for the staircase.
|
// Otherwise, create a new room for the staircase.
|
||||||
layout.rooms.push(layout::Room {
|
layout.rooms.push(layout::Room {
|
||||||
@@ -488,6 +496,7 @@ pub fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircas
|
|||||||
width: stair_size,
|
width: stair_size,
|
||||||
height: stair_size,
|
height: stair_size,
|
||||||
});
|
});
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user