added basic monster and trap functionality
This commit is contained in:
+158
-9
@@ -22,6 +22,8 @@ const WINDOW: (u8, u8, u8, u8) = (70, 130, 220, 255);
|
||||
const WHITE: (u8, u8, u8, u8) = (255, 255, 255, 255);
|
||||
const START_MARKER: (u8, u8, u8, u8) = (60, 220, 200, 255);
|
||||
const END_MARKER: (u8, u8, u8, u8) = (240, 90, 90, 255);
|
||||
const TRAP_MARKER: (u8, u8, u8, u8) = (150, 80, 230, 255);
|
||||
const MONSTER_MARKER: (u8, u8, u8, u8) = (200, 50, 50, 255);
|
||||
|
||||
pub enum ExportTarget {
|
||||
File(PathBuf),
|
||||
@@ -292,6 +294,22 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
||||
END_MARKER,
|
||||
settings.colorblind_mode,
|
||||
);
|
||||
draw_area_marker_group(
|
||||
&mut pixmap,
|
||||
&g,
|
||||
&layout.trap_markers,
|
||||
"T",
|
||||
TRAP_MARKER,
|
||||
settings.colorblind_mode,
|
||||
);
|
||||
draw_area_marker_group(
|
||||
&mut pixmap,
|
||||
&g,
|
||||
&layout.monster_markers,
|
||||
"M",
|
||||
MONSTER_MARKER,
|
||||
settings.colorblind_mode,
|
||||
);
|
||||
|
||||
Ok(pixmap)
|
||||
}
|
||||
@@ -348,6 +366,8 @@ fn export_composite_masks(
|
||||
let mut windows = blank_mask(&g, "windows")?;
|
||||
let mut start_markers = blank_mask(&g, "start markers")?;
|
||||
let mut end_markers = blank_mask(&g, "end markers")?;
|
||||
let mut trap_markers = blank_mask(&g, "trap markers")?;
|
||||
let mut monster_markers = blank_mask(&g, "monster markers")?;
|
||||
for door in &layout.doors {
|
||||
if door.secret {
|
||||
draw_door(&mut secret_doors, &g, door, door_w, WHITE, DoorStyle::Solid);
|
||||
@@ -364,6 +384,8 @@ fn export_composite_masks(
|
||||
}
|
||||
fill_marker_mask(&mut start_markers, &g, &layout.start_markers);
|
||||
fill_marker_mask(&mut end_markers, &g, &layout.end_markers);
|
||||
fill_marker_mask(&mut trap_markers, &g, &layout.trap_markers);
|
||||
fill_marker_mask(&mut monster_markers, &g, &layout.monster_markers);
|
||||
|
||||
let composite = render_pixmap(layout, settings)?;
|
||||
let composite_img = raster_image_from_pixmap(&composite, settings)?;
|
||||
@@ -382,6 +404,8 @@ fn export_composite_masks(
|
||||
save_mask_image(folder, "windows_mask", &windows, settings)?;
|
||||
save_mask_image(folder, "start_markers_mask", &start_markers, settings)?;
|
||||
save_mask_image(folder, "end_markers_mask", &end_markers, settings)?;
|
||||
save_mask_image(folder, "trap_markers_mask", &trap_markers, settings)?;
|
||||
save_mask_image(folder, "monster_markers_mask", &monster_markers, settings)?;
|
||||
if settings.export_show_grid {
|
||||
let mut grid = Pixmap::new(g.width, g.height)
|
||||
.ok_or_else(|| "Failed to allocate grid mask canvas".to_string())?;
|
||||
@@ -712,6 +736,22 @@ fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
|
||||
END_MARKER,
|
||||
settings.colorblind_mode,
|
||||
);
|
||||
append_svg_area_marker_group(
|
||||
&mut s,
|
||||
&g,
|
||||
&layout.trap_markers,
|
||||
"T",
|
||||
TRAP_MARKER,
|
||||
settings.colorblind_mode,
|
||||
);
|
||||
append_svg_area_marker_group(
|
||||
&mut s,
|
||||
&g,
|
||||
&layout.monster_markers,
|
||||
"M",
|
||||
MONSTER_MARKER,
|
||||
settings.colorblind_mode,
|
||||
);
|
||||
|
||||
s.push_str("</svg>\n");
|
||||
s
|
||||
@@ -818,7 +858,12 @@ fn draw_area_marker(
|
||||
color: (u8, u8, u8, u8),
|
||||
colorblind_mode: bool,
|
||||
) {
|
||||
let overlay = (color.0, color.1, color.2, if colorblind_mode { 56 } else { 90 });
|
||||
let overlay = (
|
||||
color.0,
|
||||
color.1,
|
||||
color.2,
|
||||
if colorblind_mode { 56 } else { 90 },
|
||||
);
|
||||
fill_rect_marker(pixmap, g, marker, overlay);
|
||||
|
||||
let x = g.left() + marker.cell.0 as f32 * g.cell;
|
||||
@@ -852,7 +897,7 @@ fn draw_area_marker(
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
} else {
|
||||
} else if label == "E" {
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.25,
|
||||
@@ -873,6 +918,70 @@ fn draw_area_marker(
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
} else if label == "T" {
|
||||
// Horizontal bar
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.2,
|
||||
y + h * 0.25,
|
||||
x + w * 0.8,
|
||||
y + h * 0.25,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
// Vertical bar
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.5,
|
||||
y + h * 0.25,
|
||||
x + w * 0.5,
|
||||
y + h * 0.75,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
} else if label == "M" {
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.2,
|
||||
y + h * 0.75,
|
||||
x + w * 0.2,
|
||||
y + h * 0.25,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.2,
|
||||
y + h * 0.25,
|
||||
x + w * 0.5,
|
||||
y + h * 0.5,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.5,
|
||||
y + h * 0.5,
|
||||
x + w * 0.8,
|
||||
y + h * 0.25,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
draw_line(
|
||||
pixmap,
|
||||
x + w * 0.8,
|
||||
y + h * 0.25,
|
||||
x + w * 0.8,
|
||||
y + h * 0.75,
|
||||
4.0,
|
||||
symbol,
|
||||
DoorStyle::Solid,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,30 +1281,70 @@ fn append_svg_area_marker(
|
||||
if label == "S" {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<polyline points='{},{} {},{} {},{}' fill='none' stroke='{symbol}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/>",
|
||||
"<polyline points='{},{} {},{} {},{}' fill='none' stroke='{}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/>",
|
||||
x + w * 0.25,
|
||||
y + h * 0.75,
|
||||
x + w * 0.5,
|
||||
y + h * 0.25,
|
||||
x + w * 0.75,
|
||||
y + h * 0.75
|
||||
y + h * 0.75,
|
||||
symbol
|
||||
);
|
||||
} else {
|
||||
} else if label == "E" {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{symbol}' stroke-width='4' stroke-linecap='round'/>",
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{}' stroke-width='4' stroke-linecap='round'/>",
|
||||
x + w * 0.25,
|
||||
y + h * 0.25,
|
||||
x + w * 0.75,
|
||||
y + h * 0.75
|
||||
y + h * 0.75,
|
||||
symbol
|
||||
);
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{symbol}' stroke-width='4' stroke-linecap='round'/>",
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{}' stroke-width='4' stroke-linecap='round'/>",
|
||||
x + w * 0.75,
|
||||
y + h * 0.25,
|
||||
x + w * 0.25,
|
||||
y + h * 0.75
|
||||
y + h * 0.75,
|
||||
symbol
|
||||
);
|
||||
} else if label == "T" {
|
||||
// Horizontal bar
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{}' stroke-width='4' stroke-linecap='round'/>",
|
||||
x + w * 0.2,
|
||||
y + h * 0.25,
|
||||
x + w * 0.8,
|
||||
y + h * 0.25,
|
||||
symbol
|
||||
);
|
||||
// Vertical bar
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<line x1='{}' y1='{}' x2='{}' y2='{}' stroke='{}' stroke-width='4' stroke-linecap='round'/>",
|
||||
x + w * 0.5,
|
||||
y + h * 0.25,
|
||||
x + w * 0.5,
|
||||
y + h * 0.75,
|
||||
symbol
|
||||
);
|
||||
} else if label == "M" {
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"<polyline points='{},{} {},{} {},{} {},{} {},{}' fill='none' stroke='{}' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/>",
|
||||
x + w * 0.2,
|
||||
y + h * 0.75,
|
||||
x + w * 0.2,
|
||||
y + h * 0.25,
|
||||
x + w * 0.5,
|
||||
y + h * 0.5,
|
||||
x + w * 0.8,
|
||||
y + h * 0.25,
|
||||
x + w * 0.8,
|
||||
y + h * 0.75,
|
||||
symbol
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,8 @@ pub struct DungeonLayout {
|
||||
pub text_labels: Vec<TextLabel>,
|
||||
pub start_markers: Vec<AreaMarker>,
|
||||
pub end_markers: Vec<AreaMarker>,
|
||||
pub trap_markers: Vec<AreaMarker>,
|
||||
pub monster_markers: Vec<AreaMarker>,
|
||||
pub packed_rooms: bool,
|
||||
}
|
||||
|
||||
@@ -109,6 +111,8 @@ impl Default for DungeonLayout {
|
||||
text_labels: Vec::new(),
|
||||
start_markers: Vec::new(),
|
||||
end_markers: Vec::new(),
|
||||
trap_markers: Vec::new(),
|
||||
monster_markers: Vec::new(),
|
||||
packed_rooms: false,
|
||||
}
|
||||
}
|
||||
@@ -178,6 +182,8 @@ pub fn generate_layout(
|
||||
text_labels: Vec::new(),
|
||||
start_markers: Vec::new(),
|
||||
end_markers: Vec::new(),
|
||||
trap_markers: Vec::new(),
|
||||
monster_markers: Vec::new(),
|
||||
packed_rooms: pack_rooms_without_corridors,
|
||||
};
|
||||
}
|
||||
@@ -203,6 +209,8 @@ pub fn generate_layout(
|
||||
text_labels: Vec::new(),
|
||||
start_markers: Vec::new(),
|
||||
end_markers: Vec::new(),
|
||||
trap_markers: Vec::new(),
|
||||
monster_markers: Vec::new(),
|
||||
packed_rooms: pack_rooms_without_corridors,
|
||||
};
|
||||
}
|
||||
@@ -233,6 +241,8 @@ pub fn generate_layout(
|
||||
text_labels: Vec::new(),
|
||||
start_markers: Vec::new(),
|
||||
end_markers: Vec::new(),
|
||||
trap_markers: Vec::new(),
|
||||
monster_markers: Vec::new(),
|
||||
packed_rooms: true,
|
||||
};
|
||||
apply_doors(&mut layout, seed, door_settings, cols, rows);
|
||||
@@ -292,6 +302,8 @@ pub fn generate_layout(
|
||||
text_labels: Vec::new(),
|
||||
start_markers: Vec::new(),
|
||||
end_markers: Vec::new(),
|
||||
trap_markers: Vec::new(),
|
||||
monster_markers: Vec::new(),
|
||||
packed_rooms: false,
|
||||
};
|
||||
}
|
||||
@@ -348,6 +360,8 @@ pub fn generate_layout(
|
||||
text_labels: Vec::new(),
|
||||
start_markers: Vec::new(),
|
||||
end_markers: Vec::new(),
|
||||
trap_markers: Vec::new(),
|
||||
monster_markers: Vec::new(),
|
||||
packed_rooms: false,
|
||||
};
|
||||
apply_doors(&mut layout, seed, door_settings, cols, rows);
|
||||
|
||||
+103
-5
@@ -15,7 +15,10 @@ use layout::{
|
||||
DoorSettings, DungeonLayout, WindowSettings, blocked_room_cells, corridor_cells,
|
||||
shortest_path_cells,
|
||||
};
|
||||
use startend::{manual_marker_size, populate_random_markers};
|
||||
use startend::{
|
||||
manual_marker_size, manual_monster_marker_size, manual_trap_marker_size,
|
||||
populate_random_markers,
|
||||
};
|
||||
use ui::{AddTool, UiSettings, draw_side_panel};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -199,6 +202,8 @@ impl eframe::App for DungeonApp {
|
||||
draw_legend_entry_colorblind(ui, "Windows", LegendStyle::DashDotLine);
|
||||
draw_legend_entry_colorblind(ui, "Start Marker", LegendStyle::SolidLine);
|
||||
draw_legend_entry_colorblind(ui, "End Marker", LegendStyle::SolidLine);
|
||||
draw_legend_entry_colorblind(ui, "Trap", LegendStyle::SolidLine);
|
||||
draw_legend_entry_colorblind(ui, "Monster", LegendStyle::SolidLine);
|
||||
} else {
|
||||
draw_legend_entry(ui, Color32::from_rgb(70, 120, 160), "Rooms");
|
||||
draw_legend_entry(ui, Color32::from_rgb(210, 190, 120), "Corridors");
|
||||
@@ -210,13 +215,17 @@ impl eframe::App for DungeonApp {
|
||||
draw_legend_entry(ui, Color32::from_rgb(70, 130, 220), "Windows");
|
||||
draw_legend_entry(ui, Color32::from_rgb(60, 220, 200), "Start Marker");
|
||||
draw_legend_entry(ui, Color32::from_rgb(240, 90, 90), "End Marker");
|
||||
draw_legend_entry(ui, Color32::from_rgb(150, 80, 230), "Trap");
|
||||
draw_legend_entry(ui, Color32::from_rgb(200, 50, 50), "Monster");
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
ui.separator();
|
||||
ui.add_space(8.0);
|
||||
ui.heading("Keybinds");
|
||||
ui.add_space(6.0);
|
||||
ui.label("• Delete / Backspace: Remove hovered room, corridor, door, text, or marker");
|
||||
ui.label(
|
||||
"• Delete / Backspace: Remove hovered room, corridor, door, text, or marker",
|
||||
);
|
||||
ui.label("• Ctrl+Z: Undo");
|
||||
ui.label("• Ctrl+Y: Redo");
|
||||
ui.label("• Left click + drag: Move hovered room");
|
||||
@@ -282,6 +291,12 @@ fn clamp_dependent_settings(settings: &mut UiSettings) {
|
||||
if settings.min_end_marker_count > settings.max_end_marker_count {
|
||||
settings.max_end_marker_count = settings.min_end_marker_count;
|
||||
}
|
||||
if settings.min_traps_per_area > settings.max_traps_per_area {
|
||||
settings.max_traps_per_area = settings.min_traps_per_area;
|
||||
}
|
||||
if settings.min_monsters_per_area > settings.max_monsters_per_area {
|
||||
settings.max_monsters_per_area = settings.min_monsters_per_area;
|
||||
}
|
||||
}
|
||||
|
||||
impl DungeonApp {
|
||||
@@ -706,7 +721,7 @@ impl DungeonApp {
|
||||
self.settings.add_tool = AddTool::None;
|
||||
}
|
||||
}
|
||||
AddTool::StartMarker | AddTool::EndMarker => {
|
||||
AddTool::StartMarker | AddTool::EndMarker | AddTool::TrapMarker => {
|
||||
self.add_corridor_drag = None;
|
||||
if response.clicked()
|
||||
&& let Some(pointer_pos) = response.interact_pointer_pos()
|
||||
@@ -716,6 +731,7 @@ impl DungeonApp {
|
||||
let kind = match self.settings.add_tool {
|
||||
AddTool::StartMarker => MarkerKind::Start,
|
||||
AddTool::EndMarker => MarkerKind::End,
|
||||
AddTool::TrapMarker => MarkerKind::Trap,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
self.add_marker_at_cell(cell, kind);
|
||||
@@ -854,7 +870,12 @@ impl DungeonApp {
|
||||
return;
|
||||
}
|
||||
|
||||
let size = manual_marker_size(&self.settings, matches!(kind, MarkerKind::Start));
|
||||
let size = match kind {
|
||||
MarkerKind::Start => manual_marker_size(&self.settings, true),
|
||||
MarkerKind::End => manual_marker_size(&self.settings, false),
|
||||
MarkerKind::Trap => manual_trap_marker_size(&self.settings),
|
||||
MarkerKind::Monster => manual_monster_marker_size(&self.settings),
|
||||
};
|
||||
let origin = centered_marker_origin(cell, size, self.settings.cols, self.settings.rows);
|
||||
let marker = layout::AreaMarker { cell: origin, size };
|
||||
|
||||
@@ -862,6 +883,8 @@ impl DungeonApp {
|
||||
match kind {
|
||||
MarkerKind::Start => self.layout.start_markers.push(marker),
|
||||
MarkerKind::End => self.layout.end_markers.push(marker),
|
||||
MarkerKind::Trap => self.layout.trap_markers.push(marker),
|
||||
MarkerKind::Monster => self.layout.monster_markers.push(marker),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,6 +976,22 @@ impl DungeonApp {
|
||||
.find(|(_, marker)| marker_contains_cell(Some(marker), clicked))
|
||||
.map(|(idx, _)| HoverMarker::End(idx))
|
||||
})
|
||||
.or_else(|| {
|
||||
self.layout
|
||||
.trap_markers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, marker)| marker_contains_cell(Some(marker), clicked))
|
||||
.map(|(idx, _)| HoverMarker::Trap(idx))
|
||||
})
|
||||
.or_else(|| {
|
||||
self.layout
|
||||
.monster_markers
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, marker)| marker_contains_cell(Some(marker), clicked))
|
||||
.map(|(idx, _)| HoverMarker::Monster(idx))
|
||||
})
|
||||
}
|
||||
|
||||
fn marker_drag_at_pointer(
|
||||
@@ -980,6 +1019,24 @@ impl DungeonApp {
|
||||
offset_y: grid_y - marker.cell.1 as f32,
|
||||
})
|
||||
}
|
||||
HoverMarker::Trap(marker_idx) => {
|
||||
let marker = self.layout.trap_markers.get(marker_idx)?;
|
||||
Some(MarkerDragState {
|
||||
kind: MarkerKind::Trap,
|
||||
marker_idx,
|
||||
offset_x: grid_x - marker.cell.0 as f32,
|
||||
offset_y: grid_y - marker.cell.1 as f32,
|
||||
})
|
||||
}
|
||||
HoverMarker::Monster(marker_idx) => {
|
||||
let marker = self.layout.monster_markers.get(marker_idx)?;
|
||||
Some(MarkerDragState {
|
||||
kind: MarkerKind::Monster,
|
||||
marker_idx,
|
||||
offset_x: grid_x - marker.cell.0 as f32,
|
||||
offset_y: grid_y - marker.cell.1 as f32,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -996,6 +1053,8 @@ impl DungeonApp {
|
||||
let markers = match drag.kind {
|
||||
MarkerKind::Start => &mut self.layout.start_markers,
|
||||
MarkerKind::End => &mut self.layout.end_markers,
|
||||
MarkerKind::Trap => &mut self.layout.trap_markers,
|
||||
MarkerKind::Monster => &mut self.layout.monster_markers,
|
||||
};
|
||||
let Some(marker) = markers.get_mut(drag.marker_idx) else {
|
||||
self.drag_state = None;
|
||||
@@ -1336,6 +1395,16 @@ impl DungeonApp {
|
||||
self.layout.end_markers.remove(idx);
|
||||
}
|
||||
}
|
||||
HoverMarker::Trap(idx) => {
|
||||
if idx < self.layout.trap_markers.len() {
|
||||
self.layout.trap_markers.remove(idx);
|
||||
}
|
||||
}
|
||||
HoverMarker::Monster(idx) => {
|
||||
if idx < self.layout.monster_markers.len() {
|
||||
self.layout.monster_markers.remove(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1584,12 +1653,16 @@ enum ResizeCorner {
|
||||
enum MarkerKind {
|
||||
Start,
|
||||
End,
|
||||
Trap,
|
||||
Monster,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum HoverMarker {
|
||||
Start(usize),
|
||||
End(usize),
|
||||
Trap(usize),
|
||||
Monster(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -1957,6 +2030,26 @@ fn draw_layout(
|
||||
hover_marker,
|
||||
MarkerKind::End,
|
||||
);
|
||||
draw_area_marker_group(
|
||||
painter,
|
||||
geometry,
|
||||
&layout.trap_markers,
|
||||
"T",
|
||||
Color32::from_rgb(150, 80, 230), // Purple
|
||||
colorblind_mode,
|
||||
hover_marker,
|
||||
MarkerKind::Trap,
|
||||
);
|
||||
draw_area_marker_group(
|
||||
painter,
|
||||
geometry,
|
||||
&layout.monster_markers,
|
||||
"M",
|
||||
Color32::from_rgb(200, 50, 50), // Red
|
||||
colorblind_mode,
|
||||
hover_marker,
|
||||
MarkerKind::Monster,
|
||||
);
|
||||
}
|
||||
|
||||
// Compute the rectangle for a given grid cell.
|
||||
@@ -2001,7 +2094,10 @@ fn centered_marker_origin(
|
||||
let half = size / 2;
|
||||
let max_x = cols.saturating_sub(size);
|
||||
let max_y = rows.saturating_sub(size);
|
||||
(cell.0.saturating_sub(half).min(max_x), cell.1.saturating_sub(half).min(max_y))
|
||||
(
|
||||
cell.0.saturating_sub(half).min(max_x),
|
||||
cell.1.saturating_sub(half).min(max_y),
|
||||
)
|
||||
}
|
||||
|
||||
fn draw_area_marker(
|
||||
@@ -2059,6 +2155,8 @@ fn draw_area_marker_group(
|
||||
let hovered = match (kind, hover_marker) {
|
||||
(MarkerKind::Start, Some(HoverMarker::Start(hover_idx))) => hover_idx == idx,
|
||||
(MarkerKind::End, Some(HoverMarker::End(hover_idx))) => hover_idx == idx,
|
||||
(MarkerKind::Trap, Some(HoverMarker::Trap(hover_idx))) => hover_idx == idx,
|
||||
(MarkerKind::Monster, Some(HoverMarker::Monster(hover_idx))) => hover_idx == idx,
|
||||
_ => false,
|
||||
};
|
||||
draw_area_marker(
|
||||
|
||||
+130
-5
@@ -10,14 +10,17 @@ const EXTRA_START_ROOM_STREAM_BASE: u64 = 13_000;
|
||||
const EXTRA_START_MARKER_STREAM_BASE: u64 = 14_000;
|
||||
const EXTRA_END_ROOM_STREAM_BASE: u64 = 15_000;
|
||||
const EXTRA_END_MARKER_STREAM_BASE: u64 = 16_000;
|
||||
const TRAP_ROOM_STREAM_BASE: u64 = 17_000;
|
||||
const TRAP_CORRIDOR_STREAM_BASE: u64 = 18_000;
|
||||
const MONSTER_ROOM_STREAM_BASE: u64 = 19_000;
|
||||
const MONSTER_CORRIDOR_STREAM_BASE: u64 = 20_000;
|
||||
|
||||
// Populate generated start/end markers after the core dungeon layout exists.
|
||||
pub fn populate_random_markers(
|
||||
mut layout: DungeonLayout,
|
||||
settings: &UiSettings,
|
||||
) -> DungeonLayout {
|
||||
pub fn populate_random_markers(mut layout: DungeonLayout, settings: &UiSettings) -> DungeonLayout {
|
||||
layout.start_markers.clear();
|
||||
layout.end_markers.clear();
|
||||
layout.trap_markers.clear();
|
||||
layout.monster_markers.clear();
|
||||
|
||||
if layout.rooms.is_empty() {
|
||||
return layout;
|
||||
@@ -84,6 +87,9 @@ pub fn populate_random_markers(
|
||||
EXTRA_END_MARKER_STREAM_BASE,
|
||||
);
|
||||
|
||||
layout = populate_random_traps(layout, settings);
|
||||
layout = populate_random_monsters(layout, settings);
|
||||
|
||||
layout
|
||||
}
|
||||
|
||||
@@ -94,11 +100,21 @@ pub fn manual_marker_size(settings: &UiSettings, is_start: bool) -> usize {
|
||||
.min_start_marker_size
|
||||
.min(settings.max_start_marker_size)
|
||||
} else {
|
||||
settings.min_end_marker_size.min(settings.max_end_marker_size)
|
||||
settings
|
||||
.min_end_marker_size
|
||||
.min(settings.max_end_marker_size)
|
||||
}
|
||||
.clamp(1, 10)
|
||||
}
|
||||
|
||||
pub fn manual_trap_marker_size(_settings: &UiSettings) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn manual_monster_marker_size(_settings: &UiSettings) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
// Pick a deterministic inclusive random value from a seed-derived stream.
|
||||
fn random_range_inclusive(min: usize, max: usize, seed_value: u64) -> usize {
|
||||
let min = min.max(1);
|
||||
@@ -220,3 +236,112 @@ fn pick_room_index(available_rooms: &[usize], room_count: usize, seed_value: u64
|
||||
seed_value as usize % room_count.max(1)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn populate_random_traps(mut layout: DungeonLayout, settings: &UiSettings) -> DungeonLayout {
|
||||
if settings.trap_frequency_percent == 0 {
|
||||
return layout;
|
||||
}
|
||||
|
||||
let mut trap_markers = Vec::new();
|
||||
|
||||
// Rooms
|
||||
for (idx, room) in layout.rooms.iter().enumerate() {
|
||||
let seed = seed::derive_seed(settings.seed, TRAP_ROOM_STREAM_BASE + idx as u64);
|
||||
if (seed % 100) < settings.trap_frequency_percent as u64 {
|
||||
let count = random_range_inclusive(
|
||||
settings.min_traps_per_area,
|
||||
settings.max_traps_per_area,
|
||||
seed.rotate_left(13),
|
||||
);
|
||||
for i in 0..count {
|
||||
trap_markers.push(marker_in_room(
|
||||
room,
|
||||
1,
|
||||
1,
|
||||
seed.wrapping_add(i as u64).rotate_right(7),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Corridors
|
||||
for (idx, corridor) in layout.corridors.iter().enumerate() {
|
||||
let seed = seed::derive_seed(settings.seed, TRAP_CORRIDOR_STREAM_BASE + idx as u64);
|
||||
if (seed % 100) < settings.trap_frequency_percent as u64 {
|
||||
let count = random_range_inclusive(
|
||||
settings.min_traps_per_area,
|
||||
settings.max_traps_per_area,
|
||||
seed.rotate_left(13),
|
||||
);
|
||||
for i in 0..count {
|
||||
if let Some(cell) = pick_random_corridor_cell(corridor, seed.wrapping_add(i as u64))
|
||||
{
|
||||
trap_markers.push(layout::AreaMarker { cell, size: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout.trap_markers = trap_markers;
|
||||
layout
|
||||
}
|
||||
|
||||
pub fn populate_random_monsters(mut layout: DungeonLayout, settings: &UiSettings) -> DungeonLayout {
|
||||
if settings.monster_frequency_percent == 0 {
|
||||
return layout;
|
||||
}
|
||||
|
||||
let mut monster_markers = Vec::new();
|
||||
|
||||
// Rooms
|
||||
for (idx, room) in layout.rooms.iter().enumerate() {
|
||||
let seed = seed::derive_seed(settings.seed, MONSTER_ROOM_STREAM_BASE + idx as u64);
|
||||
if (seed % 100) < settings.monster_frequency_percent as u64 {
|
||||
let count = random_range_inclusive(
|
||||
settings.min_monsters_per_area,
|
||||
settings.max_monsters_per_area,
|
||||
seed.rotate_left(13),
|
||||
);
|
||||
for i in 0..count {
|
||||
monster_markers.push(marker_in_room(
|
||||
room,
|
||||
1,
|
||||
1,
|
||||
seed.wrapping_add(i as u64).rotate_right(7),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Corridors
|
||||
for (idx, corridor) in layout.corridors.iter().enumerate() {
|
||||
let seed = seed::derive_seed(settings.seed, MONSTER_CORRIDOR_STREAM_BASE + idx as u64);
|
||||
if (seed % 100) < settings.monster_frequency_percent as u64 {
|
||||
let count = random_range_inclusive(
|
||||
settings.min_monsters_per_area,
|
||||
settings.max_monsters_per_area,
|
||||
seed.rotate_left(13),
|
||||
);
|
||||
for i in 0..count {
|
||||
if let Some(cell) = pick_random_corridor_cell(corridor, seed.wrapping_add(i as u64))
|
||||
{
|
||||
monster_markers.push(layout::AreaMarker { cell, size: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
layout.monster_markers = monster_markers;
|
||||
layout
|
||||
}
|
||||
|
||||
fn pick_random_corridor_cell(
|
||||
corridor: &layout::Corridor,
|
||||
seed_value: u64,
|
||||
) -> Option<(usize, usize)> {
|
||||
if corridor.path.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let idx = (seed_value as usize) % corridor.path.len();
|
||||
Some(corridor.path[idx])
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ enum Tab {
|
||||
Generate,
|
||||
Layout,
|
||||
StartAndEnd,
|
||||
MonstersAndTraps,
|
||||
Add,
|
||||
}
|
||||
|
||||
@@ -140,6 +141,12 @@ pub struct UiSettings {
|
||||
pub max_start_marker_count: usize,
|
||||
pub min_end_marker_count: usize,
|
||||
pub max_end_marker_count: usize,
|
||||
pub trap_frequency_percent: usize,
|
||||
pub min_traps_per_area: usize,
|
||||
pub max_traps_per_area: usize,
|
||||
pub monster_frequency_percent: usize,
|
||||
pub min_monsters_per_area: usize,
|
||||
pub max_monsters_per_area: usize,
|
||||
active_tab: Tab,
|
||||
}
|
||||
|
||||
@@ -187,6 +194,12 @@ impl Default for UiSettings {
|
||||
max_start_marker_count: 1,
|
||||
min_end_marker_count: 1,
|
||||
max_end_marker_count: 1,
|
||||
trap_frequency_percent: 20,
|
||||
min_traps_per_area: 1,
|
||||
max_traps_per_area: 3,
|
||||
monster_frequency_percent: 15,
|
||||
min_monsters_per_area: 1,
|
||||
max_monsters_per_area: 2,
|
||||
active_tab: Tab::Generate,
|
||||
}
|
||||
}
|
||||
@@ -204,6 +217,7 @@ pub enum AddTool {
|
||||
Text,
|
||||
StartMarker,
|
||||
EndMarker,
|
||||
TrapMarker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
@@ -231,8 +245,6 @@ pub fn draw_side_panel(
|
||||
ui.horizontal(|ui| {
|
||||
let is_generate = settings.active_tab == Tab::Generate;
|
||||
let is_layout = settings.active_tab == Tab::Layout;
|
||||
let is_start_and_end = settings.active_tab == Tab::StartAndEnd;
|
||||
let is_add = settings.active_tab == Tab::Add;
|
||||
|
||||
if ui.selectable_label(is_generate, "Generate").clicked() {
|
||||
settings.active_tab = Tab::Generate;
|
||||
@@ -240,10 +252,25 @@ pub fn draw_side_panel(
|
||||
if ui.selectable_label(is_layout, "Layout").clicked() {
|
||||
settings.active_tab = Tab::Layout;
|
||||
}
|
||||
if ui.selectable_label(is_start_and_end, "Start & End").clicked() {
|
||||
if ui
|
||||
.selectable_label(settings.active_tab == Tab::StartAndEnd, "Start & End")
|
||||
.clicked()
|
||||
{
|
||||
settings.active_tab = Tab::StartAndEnd;
|
||||
}
|
||||
if ui.selectable_label(is_add, "Add").clicked() {
|
||||
if ui
|
||||
.selectable_label(
|
||||
settings.active_tab == Tab::MonstersAndTraps,
|
||||
"Monsters/Traps",
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
settings.active_tab = Tab::MonstersAndTraps;
|
||||
}
|
||||
if ui
|
||||
.selectable_label(settings.active_tab == Tab::Add, "Add")
|
||||
.clicked()
|
||||
{
|
||||
settings.active_tab = Tab::Add;
|
||||
}
|
||||
});
|
||||
@@ -253,6 +280,7 @@ pub fn draw_side_panel(
|
||||
Tab::Generate => draw_generate_tab(ui, settings, &mut result, export_in_progress),
|
||||
Tab::Layout => draw_layout_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::Add => draw_add_tab(ui, settings),
|
||||
}
|
||||
});
|
||||
@@ -751,6 +779,7 @@ fn draw_add_tab(ui: &mut egui::Ui, settings: &mut UiSettings) {
|
||||
ui.selectable_value(&mut settings.add_tool, AddTool::Text, "Add Text");
|
||||
ui.selectable_value(&mut settings.add_tool, AddTool::StartMarker, "Place Start");
|
||||
ui.selectable_value(&mut settings.add_tool, AddTool::EndMarker, "Place End");
|
||||
ui.selectable_value(&mut settings.add_tool, AddTool::TrapMarker, "Place Trap");
|
||||
});
|
||||
|
||||
ui.add_space(6.0);
|
||||
@@ -772,6 +801,7 @@ fn draw_add_tab(ui: &mut egui::Ui, settings: &mut UiSettings) {
|
||||
}
|
||||
AddTool::StartMarker => ui.label("Click the grid to place the start marker."),
|
||||
AddTool::EndMarker => ui.label("Click the grid to place the end marker."),
|
||||
AddTool::TrapMarker => ui.label("Click the grid to place a trap marker."),
|
||||
AddTool::None => ui.label("Select a tool to add rooms or corridors."),
|
||||
};
|
||||
}
|
||||
@@ -858,6 +888,69 @@ fn draw_start_and_end_tab(
|
||||
ui.label("Markers are generated inside rooms. Matching start/end indices are paired as far apart as possible.");
|
||||
}
|
||||
|
||||
fn draw_monsters_and_traps_tab(
|
||||
ui: &mut egui::Ui,
|
||||
settings: &mut UiSettings,
|
||||
result: &mut SidePanelResult,
|
||||
) {
|
||||
ui.label(RichText::new("Trap Settings").strong());
|
||||
ui.add_space(8.0);
|
||||
draw_usize_slider_row(
|
||||
ui,
|
||||
result,
|
||||
"Trap Frequency (%)",
|
||||
&mut settings.trap_frequency_percent,
|
||||
0..=100,
|
||||
0..=100,
|
||||
);
|
||||
draw_usize_slider_row(
|
||||
ui,
|
||||
result,
|
||||
"Min Traps Per Room/Corridor",
|
||||
&mut settings.min_traps_per_area,
|
||||
0..=20,
|
||||
0..=100,
|
||||
);
|
||||
draw_usize_slider_row(
|
||||
ui,
|
||||
result,
|
||||
"Max Traps Per Room/Corridor",
|
||||
&mut settings.max_traps_per_area,
|
||||
0..=20,
|
||||
0..=100,
|
||||
);
|
||||
|
||||
ui.add_space(12.0);
|
||||
ui.separator();
|
||||
ui.add_space(8.0);
|
||||
ui.label(RichText::new("Monster Settings").strong());
|
||||
ui.add_space(8.0);
|
||||
draw_usize_slider_row(
|
||||
ui,
|
||||
result,
|
||||
"Monster Frequency (%)",
|
||||
&mut settings.monster_frequency_percent,
|
||||
0..=100,
|
||||
0..=100,
|
||||
);
|
||||
draw_usize_slider_row(
|
||||
ui,
|
||||
result,
|
||||
"Min Monsters Per Room/Corridor",
|
||||
&mut settings.min_monsters_per_area,
|
||||
0..=20,
|
||||
0..=100,
|
||||
);
|
||||
draw_usize_slider_row(
|
||||
ui,
|
||||
result,
|
||||
"Max Monsters Per Room/Corridor",
|
||||
&mut settings.max_monsters_per_area,
|
||||
0..=20,
|
||||
0..=100,
|
||||
);
|
||||
}
|
||||
|
||||
fn draw_usize_slider_row(
|
||||
ui: &mut egui::Ui,
|
||||
result: &mut SidePanelResult,
|
||||
|
||||
Reference in New Issue
Block a user