syncronized staircases on multiple levels and removed elevators as redundant
This commit is contained in:
+1
-20
@@ -826,31 +826,13 @@ pub fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
|
|||||||
settings.colorblind_mode,
|
settings.colorblind_mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw stairs and elevators.
|
// Draw stairs.
|
||||||
for stair in &layout.stairs {
|
for stair in &layout.stairs {
|
||||||
let x = g.left() + stair.cell.0 as f32 * g.cell;
|
let x = g.left() + stair.cell.0 as f32 * g.cell;
|
||||||
let y = g.top() + stair.cell.1 as f32 * g.cell;
|
let y = g.top() + stair.cell.1 as f32 * g.cell;
|
||||||
let w = stair.size as f32 * g.cell;
|
let w = stair.size as f32 * g.cell;
|
||||||
let h = stair.size as f32 * g.cell;
|
let h = stair.size as f32 * g.cell;
|
||||||
|
|
||||||
if stair.is_elevator {
|
|
||||||
let fill = if settings.colorblind_mode {
|
|
||||||
"rgb(180,180,180)"
|
|
||||||
} else {
|
|
||||||
"rgb(100,150,200)"
|
|
||||||
};
|
|
||||||
let _ = writeln!(
|
|
||||||
s,
|
|
||||||
"<rect x='{x}' y='{y}' width='{w}' height='{h}' fill='{fill}' fill-opacity='0.35' stroke='rgb(100,150,200)' stroke-width='2'/>"
|
|
||||||
);
|
|
||||||
let _ = writeln!(
|
|
||||||
s,
|
|
||||||
"<text x='{cx}' y='{cy}' text-anchor='middle' dominant-baseline='middle' fill='white' font-size='{fs}'>E</text>",
|
|
||||||
cx = x + w / 2.0,
|
|
||||||
cy = y + h / 2.0,
|
|
||||||
fs = (g.cell * stair.size as f32 * 0.4).clamp(14.0, 48.0)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let fill = if settings.colorblind_mode {
|
let fill = if settings.colorblind_mode {
|
||||||
"rgb(180,180,180)"
|
"rgb(180,180,180)"
|
||||||
} else {
|
} else {
|
||||||
@@ -872,7 +854,6 @@ pub fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
s.push_str("</svg>\n");
|
s.push_str("</svg>\n");
|
||||||
s
|
s
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ pub struct AreaMarker {
|
|||||||
pub struct Staircase {
|
pub struct Staircase {
|
||||||
pub cell: (usize, usize),
|
pub cell: (usize, usize),
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
pub is_elevator: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|||||||
+1
-24
@@ -2323,11 +2323,9 @@ fn draw_staircase(
|
|||||||
let bottom = top + stair.size as f32 * geometry.cell_size;
|
let bottom = top + stair.size as f32 * geometry.cell_size;
|
||||||
let rect = egui::Rect::from_min_max(egui::pos2(left, top), egui::pos2(right, bottom));
|
let rect = egui::Rect::from_min_max(egui::pos2(left, top), egui::pos2(right, bottom));
|
||||||
|
|
||||||
// Fill with a distinctive color for stairs/elevators.
|
// Fill with a distinctive color for stairs.
|
||||||
let fill = if colorblind_mode {
|
let fill = if colorblind_mode {
|
||||||
Color32::from_rgb(180, 180, 180).gamma_multiply(0.4)
|
Color32::from_rgb(180, 180, 180).gamma_multiply(0.4)
|
||||||
} else if stair.is_elevator {
|
|
||||||
Color32::from_rgb(100, 150, 200).gamma_multiply(0.35)
|
|
||||||
} else {
|
} else {
|
||||||
Color32::from_rgb(200, 150, 50).gamma_multiply(0.35)
|
Color32::from_rgb(200, 150, 50).gamma_multiply(0.35)
|
||||||
};
|
};
|
||||||
@@ -2336,8 +2334,6 @@ fn draw_staircase(
|
|||||||
// Draw border.
|
// Draw border.
|
||||||
let stroke_color = if colorblind_mode {
|
let stroke_color = if colorblind_mode {
|
||||||
Color32::BLACK
|
Color32::BLACK
|
||||||
} else if stair.is_elevator {
|
|
||||||
Color32::from_rgb(100, 150, 200)
|
|
||||||
} else {
|
} else {
|
||||||
Color32::from_rgb(200, 150, 50)
|
Color32::from_rgb(200, 150, 50)
|
||||||
};
|
};
|
||||||
@@ -2348,24 +2344,6 @@ fn draw_staircase(
|
|||||||
egui::StrokeKind::Middle,
|
egui::StrokeKind::Middle,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw stair lines or elevator symbol.
|
|
||||||
if stair.is_elevator {
|
|
||||||
// Draw elevator symbol: a rectangle with arrows.
|
|
||||||
let center = rect.center();
|
|
||||||
let text = "E";
|
|
||||||
let font_size = (geometry.cell_size * stair.size as f32 * 0.4).clamp(14.0, 48.0);
|
|
||||||
painter.text(
|
|
||||||
center,
|
|
||||||
egui::Align2::CENTER_CENTER,
|
|
||||||
text,
|
|
||||||
egui::FontId::proportional(font_size),
|
|
||||||
if colorblind_mode {
|
|
||||||
Color32::BLACK
|
|
||||||
} else {
|
|
||||||
Color32::WHITE
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Draw stair lines (horizontal steps).
|
// Draw stair lines (horizontal steps).
|
||||||
let steps = (stair.size as f32 * 2.0).round() as usize;
|
let steps = (stair.size as f32 * 2.0).round() as usize;
|
||||||
let step_height = (rect.height() / (steps as f32)).max(1.0);
|
let step_height = (rect.height() / (steps as f32)).max(1.0);
|
||||||
@@ -2384,7 +2362,6 @@ fn draw_staircase(
|
|||||||
Stroke::new(1.5, line_color),
|
Stroke::new(1.5, line_color),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the rectangle for a given grid cell.
|
// Compute the rectangle for a given grid cell.
|
||||||
|
|||||||
+149
-43
@@ -353,70 +353,171 @@ fn pick_random_corridor_cell(
|
|||||||
// Constant for stair marker stream base
|
// Constant for stair marker stream base
|
||||||
const STAIR_MARKER_STREAM_BASE: u64 = 21_000;
|
const STAIR_MARKER_STREAM_BASE: u64 = 21_000;
|
||||||
|
|
||||||
// Populate stairs (and optionally elevators) across all levels after markers are placed.
|
// Populate stairs across all levels after markers are placed.
|
||||||
pub fn populate_stairs(
|
pub fn populate_stairs(
|
||||||
mut layouts: Vec<DungeonLayout>,
|
mut layouts: Vec<DungeonLayout>,
|
||||||
settings: &UiSettings,
|
settings: &UiSettings,
|
||||||
) -> Vec<DungeonLayout> {
|
) -> Vec<DungeonLayout> {
|
||||||
|
if layouts.is_empty() {
|
||||||
|
return layouts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing stairs.
|
||||||
|
for layout in &mut layouts {
|
||||||
|
layout.stairs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
let stair_count = if settings.min_stairs_per_level == settings.max_stairs_per_level {
|
let num_levels = layouts.len();
|
||||||
settings.min_stairs_per_level
|
let num_gaps = num_levels.saturating_sub(1);
|
||||||
} else {
|
|
||||||
let range_seed = seed::derive_seed(settings.seed, 0x2A_3B_4_u64);
|
|
||||||
(range_seed as usize % (settings.max_stairs_per_level - settings.min_stairs_per_level + 1))
|
|
||||||
+ settings.min_stairs_per_level
|
|
||||||
};
|
|
||||||
|
|
||||||
if stair_count == 0 {
|
// If we only have 1 level, we still generate one set of stairs (e.g. to a hypothetical level below).
|
||||||
return layouts;
|
let sets_to_gen = if num_gaps == 0 { 1 } else { num_gaps };
|
||||||
|
let mut all_stair_sets: Vec<Vec<layout::Staircase>> = Vec::new();
|
||||||
|
|
||||||
|
if settings.sync_stairs_across_levels {
|
||||||
|
// One set of positions for all gaps.
|
||||||
|
let stair_count = get_stair_count(settings, 0);
|
||||||
|
let base_layout = &layouts[0];
|
||||||
|
let synced_stairs = pick_stair_positions(base_layout, settings, stair_count, 0);
|
||||||
|
for _ in 0..sets_to_gen {
|
||||||
|
all_stair_sets.push(synced_stairs.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Independent positions for each gap.
|
||||||
|
for i in 0..sets_to_gen {
|
||||||
|
let stair_count = get_stair_count(settings, i);
|
||||||
|
// Use the layout of the upper level of the gap as a guide.
|
||||||
|
let layout_idx = i.min(num_levels - 1);
|
||||||
|
let stairs =
|
||||||
|
pick_stair_positions(&layouts[layout_idx], settings, stair_count, i as u64);
|
||||||
|
all_stair_sets.push(stairs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_elevator = settings.windows_enabled;
|
// Assign stairs to levels.
|
||||||
|
if num_levels == 1 {
|
||||||
// If syncing stairs, pick the same stair positions for all levels.
|
layouts[0].stairs.extend(all_stair_sets[0].clone());
|
||||||
let mut sync_stair_cells: Vec<layout::Staircase> = Vec::new();
|
} else {
|
||||||
if settings.sync_stairs_across_levels && !layouts.is_empty() {
|
for i in 0..num_gaps {
|
||||||
let base_settings = settings.clone();
|
let stairs = &all_stair_sets[i];
|
||||||
let base_layout = layouts[0].clone();
|
// These stairs connect level i to i+1.
|
||||||
sync_stair_cells =
|
layouts[i].stairs.extend(stairs.clone());
|
||||||
pick_stair_positions(&base_layout, &base_settings, stair_count, is_elevator);
|
layouts[i + 1].stairs.extend(stairs.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (level_idx, layout) in layouts.iter_mut().enumerate() {
|
// De-duplicate stairs at the same location on the same level (can happen if synced).
|
||||||
if settings.sync_stairs_across_levels && !sync_stair_cells.is_empty() {
|
for layout in &mut layouts {
|
||||||
// Use the synced stair positions.
|
let mut seen = HashSet::new();
|
||||||
layout.stairs = sync_stair_cells
|
layout.stairs.retain(|s| seen.insert(s.cell));
|
||||||
.iter()
|
}
|
||||||
.map(|stair| layout::Staircase {
|
|
||||||
cell: stair.cell,
|
// Ensure every stair is inside a room on its level.
|
||||||
size: random_stair_size(
|
for layout in &mut layouts {
|
||||||
settings,
|
let stairs_clone = layout.stairs.clone();
|
||||||
seed::derive_seed(
|
for stair in &stairs_clone {
|
||||||
settings.seed,
|
ensure_stair_in_room(layout, stair);
|
||||||
STAIR_MARKER_STREAM_BASE + level_idx as u64,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
is_elevator,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
} else {
|
|
||||||
// Generate independent stair positions for this level.
|
|
||||||
layout.stairs = pick_stair_positions(layout, settings, stair_count, is_elevator);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layouts
|
layouts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_stair_count(settings: &UiSettings, gap_idx: usize) -> usize {
|
||||||
|
if settings.min_stairs_per_level == settings.max_stairs_per_level {
|
||||||
|
settings.min_stairs_per_level
|
||||||
|
} else {
|
||||||
|
let range_seed = seed::derive_seed(settings.seed, 0x2A_3B_4_u64 + gap_idx as u64);
|
||||||
|
(range_seed as usize % (settings.max_stairs_per_level - settings.min_stairs_per_level + 1))
|
||||||
|
+ settings.min_stairs_per_level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure a staircase is fully contained within a room. If not, extend the nearest existing room or create a new one.
|
||||||
|
fn ensure_stair_in_room(layout: &mut DungeonLayout, stair: &layout::Staircase) {
|
||||||
|
let stair_x = stair.cell.0;
|
||||||
|
let stair_y = stair.cell.1;
|
||||||
|
let stair_size = stair.size;
|
||||||
|
|
||||||
|
// Check if any room already contains the staircase.
|
||||||
|
for room in &layout.rooms {
|
||||||
|
if stair_x >= room.x
|
||||||
|
&& stair_y >= room.y
|
||||||
|
&& (stair_x + stair_size) <= (room.x + room.width)
|
||||||
|
&& (stair_y + stair_size) <= (room.y + room.height)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the nearest room to the staircase.
|
||||||
|
let mut nearest_room_idx = None;
|
||||||
|
let mut min_dist = usize::MAX;
|
||||||
|
|
||||||
|
for (idx, room) in layout.rooms.iter().enumerate() {
|
||||||
|
let dist = room_to_stair_min_dist(room, stair);
|
||||||
|
if dist < min_dist {
|
||||||
|
min_dist = dist;
|
||||||
|
nearest_room_idx = Some(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a room is nearby (within 5 cells), extend it.
|
||||||
|
if let Some(idx) = nearest_room_idx
|
||||||
|
&& min_dist <= 5
|
||||||
|
{
|
||||||
|
let room = &mut layout.rooms[idx];
|
||||||
|
let new_x = room.x.min(stair_x);
|
||||||
|
let new_y = room.y.min(stair_y);
|
||||||
|
let new_right = (room.x + room.width).max(stair_x + stair_size);
|
||||||
|
let new_bottom = (room.y + room.height).max(stair_y + stair_size);
|
||||||
|
|
||||||
|
room.x = new_x;
|
||||||
|
room.y = new_y;
|
||||||
|
room.width = new_right - new_x;
|
||||||
|
room.height = new_bottom - new_y;
|
||||||
|
} else {
|
||||||
|
// Otherwise, create a new room for the staircase.
|
||||||
|
layout.rooms.push(layout::Room {
|
||||||
|
x: stair_x,
|
||||||
|
y: stair_y,
|
||||||
|
width: stair_size,
|
||||||
|
height: stair_size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the minimum Manhattan distance between a room and a staircase.
|
||||||
|
fn room_to_stair_min_dist(room: &layout::Room, stair: &layout::Staircase) -> usize {
|
||||||
|
let dx = if stair.cell.0 + stair.size <= room.x {
|
||||||
|
room.x - (stair.cell.0 + stair.size)
|
||||||
|
} else if stair.cell.0 >= room.x + room.width {
|
||||||
|
stair.cell.0 - (room.x + room.width)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let dy = if stair.cell.1 + stair.size <= room.y {
|
||||||
|
room.y - (stair.cell.1 + stair.size)
|
||||||
|
} else if stair.cell.1 >= room.y + room.height {
|
||||||
|
stair.cell.1 - (room.y + room.height)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
dx + dy
|
||||||
|
}
|
||||||
|
|
||||||
// Pick random cells within rooms that are valid for stair placement and return Staircase objects.
|
// Pick random cells within rooms that are valid for stair placement and return Staircase objects.
|
||||||
fn pick_stair_positions(
|
fn pick_stair_positions(
|
||||||
layout: &DungeonLayout,
|
layout: &DungeonLayout,
|
||||||
settings: &UiSettings,
|
settings: &UiSettings,
|
||||||
count: usize,
|
count: usize,
|
||||||
is_elevator: bool,
|
seed_offset: u64,
|
||||||
) -> Vec<layout::Staircase> {
|
) -> Vec<layout::Staircase> {
|
||||||
if layout.rooms.is_empty() || count == 0 {
|
if layout.rooms.is_empty() || count == 0 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -444,7 +545,10 @@ fn pick_stair_positions(
|
|||||||
let mut shuffled = valid_cells.clone();
|
let mut shuffled = valid_cells.clone();
|
||||||
shuffle_with_seed(
|
shuffle_with_seed(
|
||||||
&mut shuffled,
|
&mut shuffled,
|
||||||
settings.seed.wrapping_add(STAIR_MARKER_STREAM_BASE),
|
settings
|
||||||
|
.seed
|
||||||
|
.wrapping_add(STAIR_MARKER_STREAM_BASE)
|
||||||
|
.wrapping_add(seed_offset),
|
||||||
);
|
);
|
||||||
|
|
||||||
let max_size = settings.max_stair_width.max(settings.max_stair_height);
|
let max_size = settings.max_stair_width.max(settings.max_stair_height);
|
||||||
@@ -486,9 +590,11 @@ fn pick_stair_positions(
|
|||||||
cell,
|
cell,
|
||||||
size: random_stair_size(
|
size: random_stair_size(
|
||||||
settings,
|
settings,
|
||||||
seed::derive_seed(settings.seed, STAIR_MARKER_STREAM_BASE + i as u64),
|
seed::derive_seed(
|
||||||
|
settings.seed,
|
||||||
|
STAIR_MARKER_STREAM_BASE + i as u64 + seed_offset * 1000,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
is_elevator,
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ impl Default for UiSettings {
|
|||||||
max_stair_height: 3,
|
max_stair_height: 3,
|
||||||
min_stairs_per_level: 1,
|
min_stairs_per_level: 1,
|
||||||
max_stairs_per_level: 1,
|
max_stairs_per_level: 1,
|
||||||
sync_stairs_across_levels: false,
|
sync_stairs_across_levels: true,
|
||||||
active_tab: Tab::Generate,
|
active_tab: Tab::Generate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,6 +267,7 @@ pub fn draw_side_panel(
|
|||||||
.default_width(280.0)
|
.default_width(280.0)
|
||||||
.show_separator_line(true)
|
.show_separator_line(true)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let is_generate = settings.active_tab == Tab::Generate;
|
let is_generate = settings.active_tab == Tab::Generate;
|
||||||
let is_layout = settings.active_tab == Tab::Layout;
|
let is_layout = settings.active_tab == Tab::Layout;
|
||||||
@@ -302,13 +303,16 @@ pub fn draw_side_panel(
|
|||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
match settings.active_tab {
|
match settings.active_tab {
|
||||||
Tab::Generate => draw_generate_tab(ui, settings, &mut result, export_in_progress),
|
Tab::Generate => {
|
||||||
|
draw_generate_tab(ui, settings, &mut result, export_in_progress)
|
||||||
|
}
|
||||||
Tab::Layout => draw_layout_tab(ui, settings, &mut result),
|
Tab::Layout => draw_layout_tab(ui, settings, &mut result),
|
||||||
Tab::StartAndEnd => draw_start_and_end_tab(ui, settings, &mut result),
|
Tab::StartAndEnd => draw_start_and_end_tab(ui, settings, &mut result),
|
||||||
Tab::MonstersAndTraps => draw_monsters_and_traps_tab(ui, settings, &mut result),
|
Tab::MonstersAndTraps => draw_monsters_and_traps_tab(ui, settings, &mut result),
|
||||||
Tab::Add => draw_add_tab(ui, settings, &mut result),
|
Tab::Add => draw_add_tab(ui, settings, &mut result),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@@ -927,11 +931,6 @@ fn draw_layout_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut Si
|
|||||||
)
|
)
|
||||||
.changed();
|
.changed();
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
|
||||||
result.settings_changed |= ui
|
|
||||||
.checkbox(&mut settings.windows_enabled, "Enable Elevators")
|
|
||||||
.changed();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the Add tab controls.
|
// Render the Add tab controls.
|
||||||
|
|||||||
Reference in New Issue
Block a user