added logic for doors
This commit is contained in:
@@ -18,6 +18,11 @@ A Rust desktop app for generating simple tabletop dungeon layouts.
|
||||
- Corridor controls:
|
||||
- `Corridor Randomness (%)` from `0` to `100`
|
||||
- `Dead-End Rooms (%)` from `0` to `50`
|
||||
- Door controls:
|
||||
- `Door Frequency (%)`
|
||||
- `Room/Hallway Door Chance (%)`
|
||||
- `Locked Door Chance (%)`
|
||||
- `Allow Middle Corridor Doors` toggle
|
||||
- Generate control:
|
||||
- Master seed input (`u64`)
|
||||
- `Random` seed button
|
||||
@@ -28,6 +33,10 @@ A Rust desktop app for generating simple tabletop dungeon layouts.
|
||||
- Corridors are stored separately as vector line segments between grid cells.
|
||||
- Every generated room is connected into one navigable network when possible.
|
||||
- A master seed drives deterministic generation, and internal subsystems derive their own sub-seeds from it.
|
||||
- Doors are generated from corridor adjacency:
|
||||
- Open doors are rendered in red
|
||||
- Locked doors are rendered in green
|
||||
- Doors are rendered as line segments on grid boundaries
|
||||
|
||||
### Corridor Randomness
|
||||
|
||||
|
||||
+104
-4
@@ -25,10 +25,27 @@ pub struct Corridor {
|
||||
pub path: Vec<(usize, usize)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Door {
|
||||
pub from: (usize, usize),
|
||||
pub to: (usize, usize),
|
||||
pub locked: bool,
|
||||
pub archway: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DoorSettings {
|
||||
pub frequency_percent: usize,
|
||||
pub room_hallway_percent: usize,
|
||||
pub locked_percent: usize,
|
||||
pub allow_middle_corridor_doors: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct DungeonLayout {
|
||||
pub rooms: Vec<Room>,
|
||||
pub corridors: Vec<Corridor>,
|
||||
pub doors: Vec<Door>,
|
||||
}
|
||||
|
||||
pub fn generate_layout(
|
||||
@@ -41,6 +58,7 @@ pub fn generate_layout(
|
||||
square_rooms_only: bool,
|
||||
corridor_randomness_percent: usize,
|
||||
dead_end_room_percent: usize,
|
||||
door_settings: DoorSettings,
|
||||
) -> DungeonLayout {
|
||||
let layout_salt = ((cols as u64) << 48)
|
||||
^ ((rows as u64) << 32)
|
||||
@@ -58,7 +76,11 @@ pub fn generate_layout(
|
||||
let mut corridors = Vec::new();
|
||||
|
||||
if cols < 2 || rows < 2 || target_room_count == 0 {
|
||||
return DungeonLayout { rooms, corridors };
|
||||
return DungeonLayout {
|
||||
rooms,
|
||||
corridors,
|
||||
doors: Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
let mut min_size = min_room_size.max(2);
|
||||
@@ -74,7 +96,11 @@ pub fn generate_layout(
|
||||
}
|
||||
|
||||
if min_size == 0 || max_size < min_size {
|
||||
return DungeonLayout { rooms, corridors };
|
||||
return DungeonLayout {
|
||||
rooms,
|
||||
corridors,
|
||||
doors: Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
let max_attempts = target_room_count.saturating_mul(40).max(50);
|
||||
@@ -121,7 +147,11 @@ pub fn generate_layout(
|
||||
}
|
||||
|
||||
if rooms.len() < 2 {
|
||||
return DungeonLayout { rooms, corridors };
|
||||
return DungeonLayout {
|
||||
rooms,
|
||||
corridors,
|
||||
doors: Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
let randomness = (corridor_randomness_percent.min(100) as f32) / 100.0;
|
||||
@@ -162,7 +192,64 @@ pub fn generate_layout(
|
||||
}
|
||||
}
|
||||
|
||||
DungeonLayout { rooms, corridors }
|
||||
let mut layout = DungeonLayout {
|
||||
rooms,
|
||||
corridors,
|
||||
doors: Vec::new(),
|
||||
};
|
||||
apply_doors(&mut layout, seed, door_settings);
|
||||
layout
|
||||
}
|
||||
|
||||
pub fn apply_doors(layout: &mut DungeonLayout, seed: u64, settings: DoorSettings) {
|
||||
layout.doors.clear();
|
||||
|
||||
let mut rng = SimpleRng::new(seed::derive_seed(seed, 0xD005_5EED_u64));
|
||||
let base = (settings.frequency_percent.min(100) as f32) / 100.0;
|
||||
let room_hall = (settings.room_hallway_percent.min(100) as f32) / 100.0;
|
||||
let locked = (settings.locked_percent.min(100) as f32) / 100.0;
|
||||
|
||||
let mut seen_edges = HashSet::new();
|
||||
for corridor in &layout.corridors {
|
||||
for pair in corridor.path.windows(2) {
|
||||
if pair[0] == pair[1] {
|
||||
continue;
|
||||
}
|
||||
let edge = normalized_cell_edge(pair[0], pair[1]);
|
||||
if !seen_edges.insert(edge) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let a_in_room = room_index_at_cell(&layout.rooms, edge.0).is_some();
|
||||
let b_in_room = room_index_at_cell(&layout.rooms, edge.1).is_some();
|
||||
let is_room_hallway = a_in_room ^ b_in_room;
|
||||
let is_middle = !a_in_room && !b_in_room;
|
||||
|
||||
if is_room_hallway {
|
||||
let door_chance = base * room_hall;
|
||||
let place_door = door_chance > 0.0 && rng.next_f32() <= door_chance;
|
||||
|
||||
layout.doors.push(Door {
|
||||
from: edge.0,
|
||||
to: edge.1,
|
||||
locked: place_door && rng.next_f32() <= locked,
|
||||
archway: !place_door,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_middle && settings.allow_middle_corridor_doors {
|
||||
if base > 0.0 && rng.next_f32() <= base {
|
||||
layout.doors.push(Door {
|
||||
from: edge.0,
|
||||
to: edge.1,
|
||||
locked: rng.next_f32() <= locked,
|
||||
archway: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_room_connection_edges(
|
||||
@@ -470,6 +557,19 @@ fn manhattan_distance(a: (usize, usize), b: (usize, usize)) -> usize {
|
||||
a.0.abs_diff(b.0) + a.1.abs_diff(b.1)
|
||||
}
|
||||
|
||||
fn room_index_at_cell(rooms: &[Room], cell: (usize, usize)) -> Option<usize> {
|
||||
rooms.iter().position(|room| {
|
||||
cell.0 >= room.x
|
||||
&& cell.0 < room.x + room.width
|
||||
&& cell.1 >= room.y
|
||||
&& cell.1 < room.y + room.height
|
||||
})
|
||||
}
|
||||
|
||||
fn normalized_cell_edge(a: (usize, usize), b: (usize, usize)) -> ((usize, usize), (usize, usize)) {
|
||||
if a <= b { (a, b) } else { (b, a) }
|
||||
}
|
||||
|
||||
struct SimpleRng {
|
||||
state: u64,
|
||||
}
|
||||
|
||||
+65
-1
@@ -7,7 +7,7 @@ use std::collections::HashSet;
|
||||
|
||||
use eframe::egui;
|
||||
use egui::{Color32, Stroke};
|
||||
use layout::{DungeonLayout, shortest_path_cells};
|
||||
use layout::{DoorSettings, DungeonLayout, shortest_path_cells};
|
||||
use ui::{UiSettings, draw_side_panel};
|
||||
|
||||
fn main() -> eframe::Result<()> {
|
||||
@@ -39,6 +39,7 @@ impl Default for DungeonApp {
|
||||
settings.square_rooms_only,
|
||||
settings.corridor_randomness,
|
||||
settings.dead_end_rooms_percent,
|
||||
door_settings_from_ui(&settings),
|
||||
);
|
||||
Self {
|
||||
settings,
|
||||
@@ -92,6 +93,7 @@ impl DungeonApp {
|
||||
self.settings.square_rooms_only,
|
||||
self.settings.corridor_randomness,
|
||||
self.settings.dead_end_rooms_percent,
|
||||
door_settings_from_ui(&self.settings),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,6 +187,7 @@ impl DungeonApp {
|
||||
self.layout.rooms[drag.room_idx].x = new_x;
|
||||
self.layout.rooms[drag.room_idx].y = new_y;
|
||||
self.reroute_corridors_for_room(drag.room_idx);
|
||||
self.refresh_doors();
|
||||
}
|
||||
|
||||
fn reroute_corridors_for_room(&mut self, room_idx: usize) {
|
||||
@@ -253,6 +256,15 @@ impl DungeonApp {
|
||||
};
|
||||
|
||||
self.layout.corridors[drag.corridor_index].path = new_path;
|
||||
self.refresh_doors();
|
||||
}
|
||||
|
||||
fn refresh_doors(&mut self) {
|
||||
layout::apply_doors(
|
||||
&mut self.layout,
|
||||
self.settings.seed,
|
||||
door_settings_from_ui(&self.settings),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +417,23 @@ fn draw_layout(painter: &egui::Painter, geometry: &GridGeometry, layout: &Dungeo
|
||||
painter.rect_filled(room_rect, 0.0, room_fill);
|
||||
painter.rect_stroke(room_rect, 0.0, outline_stroke, egui::StrokeKind::Middle);
|
||||
}
|
||||
|
||||
for door in &layout.doors {
|
||||
let color = if door.archway {
|
||||
Color32::from_rgb(70, 130, 220)
|
||||
} else if door.locked {
|
||||
Color32::from_rgb(80, 200, 120)
|
||||
} else {
|
||||
Color32::from_rgb(220, 70, 70)
|
||||
};
|
||||
draw_door_line(
|
||||
painter,
|
||||
geometry,
|
||||
door.from,
|
||||
door.to,
|
||||
Stroke::new(3.0, color),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn cell_rect(geometry: &GridGeometry, col: usize, row: usize) -> egui::Rect {
|
||||
@@ -495,3 +524,38 @@ fn simplify_path_loops(path: &mut Vec<(usize, usize)>) {
|
||||
}
|
||||
*path = out;
|
||||
}
|
||||
|
||||
fn door_settings_from_ui(settings: &UiSettings) -> DoorSettings {
|
||||
DoorSettings {
|
||||
frequency_percent: settings.door_frequency_percent,
|
||||
room_hallway_percent: settings.room_hallway_door_percent,
|
||||
locked_percent: settings.locked_door_percent,
|
||||
allow_middle_corridor_doors: settings.allow_middle_corridor_doors,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_door_line(
|
||||
painter: &egui::Painter,
|
||||
geometry: &GridGeometry,
|
||||
a: (usize, usize),
|
||||
b: (usize, usize),
|
||||
stroke: Stroke,
|
||||
) {
|
||||
if a.0.abs_diff(b.0) + a.1.abs_diff(b.1) != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
if a.0 != b.0 {
|
||||
let x = geometry.rect.left() + (a.0.max(b.0) as f32) * geometry.cell_size;
|
||||
let row = a.1;
|
||||
let y0 = geometry.rect.top() + row as f32 * geometry.cell_size;
|
||||
let y1 = y0 + geometry.cell_size;
|
||||
painter.line_segment([egui::pos2(x, y0), egui::pos2(x, y1)], stroke);
|
||||
} else {
|
||||
let y = geometry.rect.top() + (a.1.max(b.1) as f32) * geometry.cell_size;
|
||||
let col = a.0;
|
||||
let x0 = geometry.rect.left() + col as f32 * geometry.cell_size;
|
||||
let x1 = x0 + geometry.cell_size;
|
||||
painter.line_segment([egui::pos2(x0, y), egui::pos2(x1, y)], stroke);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ pub struct UiSettings {
|
||||
pub square_rooms_only: bool,
|
||||
pub corridor_randomness: usize,
|
||||
pub dead_end_rooms_percent: usize,
|
||||
pub door_frequency_percent: usize,
|
||||
pub room_hallway_door_percent: usize,
|
||||
pub locked_door_percent: usize,
|
||||
pub allow_middle_corridor_doors: bool,
|
||||
active_tab: Tab,
|
||||
}
|
||||
|
||||
@@ -42,6 +46,10 @@ impl Default for UiSettings {
|
||||
square_rooms_only: false,
|
||||
corridor_randomness: 0,
|
||||
dead_end_rooms_percent: 0,
|
||||
door_frequency_percent: 30,
|
||||
room_hallway_door_percent: 70,
|
||||
locked_door_percent: 25,
|
||||
allow_middle_corridor_doors: false,
|
||||
active_tab: Tab::Generate,
|
||||
}
|
||||
}
|
||||
@@ -227,4 +235,65 @@ fn draw_layout_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut Si
|
||||
)
|
||||
.changed();
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new("Door Settings").strong());
|
||||
ui.add_space(8.0);
|
||||
|
||||
ui.label("Door Frequency (%)");
|
||||
ui.horizontal(|ui| {
|
||||
result.settings_changed |= ui
|
||||
.add(egui::Slider::new(&mut settings.door_frequency_percent, 0..=100).show_value(false))
|
||||
.changed();
|
||||
result.settings_changed |= ui
|
||||
.add(
|
||||
egui::DragValue::new(&mut settings.door_frequency_percent)
|
||||
.speed(1.0)
|
||||
.range(0..=100),
|
||||
)
|
||||
.changed();
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label("Room/Hallway Door Chance (%)");
|
||||
ui.horizontal(|ui| {
|
||||
result.settings_changed |= ui
|
||||
.add(
|
||||
egui::Slider::new(&mut settings.room_hallway_door_percent, 0..=100)
|
||||
.show_value(false),
|
||||
)
|
||||
.changed();
|
||||
result.settings_changed |= ui
|
||||
.add(
|
||||
egui::DragValue::new(&mut settings.room_hallway_door_percent)
|
||||
.speed(1.0)
|
||||
.range(0..=100),
|
||||
)
|
||||
.changed();
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.label("Locked Door Chance (%)");
|
||||
ui.horizontal(|ui| {
|
||||
result.settings_changed |= ui
|
||||
.add(egui::Slider::new(&mut settings.locked_door_percent, 0..=100).show_value(false))
|
||||
.changed();
|
||||
result.settings_changed |= ui
|
||||
.add(
|
||||
egui::DragValue::new(&mut settings.locked_door_percent)
|
||||
.speed(1.0)
|
||||
.range(0..=100),
|
||||
)
|
||||
.changed();
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
result.settings_changed |= ui
|
||||
.checkbox(
|
||||
&mut settings.allow_middle_corridor_doors,
|
||||
"Allow Middle Corridor Doors",
|
||||
)
|
||||
.changed();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user