1use crate::{Algorithm, Grid, Rng, Tile};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::Path;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct PrefabConfig {
9 pub max_prefabs: usize,
11 pub min_spacing: usize,
13 pub allow_rotation: bool,
15 pub allow_mirroring: bool,
17 pub weighted_selection: bool,
19 pub placement_mode: PrefabPlacementMode,
21 pub tags: Option<Vec<String>>,
23}
24
25impl Default for PrefabConfig {
26 fn default() -> Self {
27 Self {
28 max_prefabs: 3,
29 min_spacing: 5,
30 allow_rotation: true,
31 allow_mirroring: false,
32 weighted_selection: true,
33 placement_mode: PrefabPlacementMode::Overwrite,
34 tags: None,
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
40pub enum PrefabPlacementMode {
42 Overwrite,
43 Merge,
44 PaintFloor,
45 PaintWall,
46}
47
48#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct PrefabLegendEntry {
51 pub tile: Option<String>,
52 pub marker: Option<String>,
53 pub mask: Option<String>,
54}
55
56#[derive(Clone, Debug, Serialize, Deserialize)]
57pub struct PrefabData {
59 pub name: String,
60 pub width: usize,
61 pub height: usize,
62 pub pattern: Vec<String>,
63 pub weight: f32,
64 pub tags: Vec<String>,
65 #[serde(default)]
66 pub legend: Option<HashMap<String, PrefabLegendEntry>>,
67}
68
69#[derive(Clone, Debug, Default)]
70pub struct PrefabCell {
72 pub tile: Option<Tile>,
73 pub marker: Option<String>,
74 pub mask: Option<String>,
75}
76
77#[derive(Debug, Clone)]
78pub struct Prefab {
80 pub name: String,
81 pub width: usize,
82 pub height: usize,
83 pub cells: Vec<PrefabCell>,
84 pub symbols: Vec<char>,
85 pub legend: Option<HashMap<char, PrefabLegendEntry>>,
86 pub weight: f32,
87 pub tags: Vec<String>,
88}
89
90impl Prefab {
91 pub fn new(pattern: &[&str]) -> Self {
93 let height = pattern.len();
94 let width = pattern.first().map(|s| s.len()).unwrap_or(0);
95 let (cells, symbols) = parse_pattern_with_legend(pattern, None);
96 Self {
97 name: "unnamed".to_string(),
98 width,
99 height,
100 cells,
101 symbols,
102 legend: None,
103 weight: 1.0,
104 tags: Vec::new(),
105 }
106 }
107
108 pub fn from_data(data: PrefabData) -> Self {
110 let width = data.width;
111 let height = data.height;
112 let legend = data.legend.as_ref().map(convert_legend);
113 let (cells, symbols) = parse_pattern_with_legend(&data.pattern, legend.as_ref());
114
115 Self {
116 name: data.name,
117 width,
118 height,
119 cells,
120 symbols,
121 legend,
122 weight: data.weight,
123 tags: data.tags,
124 }
125 }
126
127 pub fn rect(w: usize, h: usize) -> Self {
129 Self {
130 name: format!("rect_{}x{}", w, h),
131 width: w,
132 height: h,
133 cells: vec![
134 PrefabCell {
135 tile: Some(Tile::Floor),
136 marker: None,
137 mask: None,
138 };
139 w * h
140 ],
141 symbols: vec!['.'; w * h],
142 legend: None,
143 weight: 1.0,
144 tags: vec!["rectangle".to_string()],
145 }
146 }
147
148 pub fn rotated(&self) -> Self {
151 let mut rotated_cells = vec![PrefabCell::default(); self.width * self.height];
152 let mut rotated_symbols = vec!['#'; self.width * self.height];
153 for y in 0..self.height {
154 for x in 0..self.width {
155 let old_idx = y * self.width + x;
156 let new_x = self.height - 1 - y;
157 let new_y = x;
158 let new_idx = new_y * self.height + new_x;
159 rotated_cells[new_idx] = self.cells[old_idx].clone();
160 rotated_symbols[new_idx] = self.symbols[old_idx];
161 }
162 }
163
164 Self {
165 name: format!("{}_rot90", self.name),
166 width: self.height,
167 height: self.width,
168 cells: rotated_cells,
169 symbols: rotated_symbols,
170 legend: self.legend.clone(),
171 weight: self.weight,
172 tags: self.tags.clone(),
173 }
174 }
175
176 pub fn mirrored_horizontal(&self) -> Self {
179 let mut mirrored_cells = vec![PrefabCell::default(); self.width * self.height];
180 let mut mirrored_symbols = vec!['#'; self.width * self.height];
181 for y in 0..self.height {
182 for x in 0..self.width {
183 let old_idx = y * self.width + x;
184 let new_x = self.width - 1 - x;
185 let new_idx = y * self.width + new_x;
186 mirrored_cells[new_idx] = self.cells[old_idx].clone();
187 mirrored_symbols[new_idx] = self.symbols[old_idx];
188 }
189 }
190
191 Self {
192 name: format!("{}_mirror_h", self.name),
193 width: self.width,
194 height: self.height,
195 cells: mirrored_cells,
196 symbols: mirrored_symbols,
197 legend: self.legend.clone(),
198 weight: self.weight,
199 tags: self.tags.clone(),
200 }
201 }
202
203 pub fn mirrored_vertical(&self) -> Self {
206 let mut mirrored_cells = vec![PrefabCell::default(); self.width * self.height];
207 let mut mirrored_symbols = vec!['#'; self.width * self.height];
208 for y in 0..self.height {
209 for x in 0..self.width {
210 let old_idx = y * self.width + x;
211 let new_y = self.height - 1 - y;
212 let new_idx = new_y * self.width + x;
213 mirrored_cells[new_idx] = self.cells[old_idx].clone();
214 mirrored_symbols[new_idx] = self.symbols[old_idx];
215 }
216 }
217
218 Self {
219 name: format!("{}_mirror_v", self.name),
220 width: self.width,
221 height: self.height,
222 cells: mirrored_cells,
223 symbols: mirrored_symbols,
224 legend: self.legend.clone(),
225 weight: self.weight,
226 tags: self.tags.clone(),
227 }
228 }
229
230 pub fn get(&self, x: usize, y: usize) -> bool {
232 self.cell_tile(x, y) == Some(Tile::Floor)
233 }
234
235 pub fn cell_tile(&self, x: usize, y: usize) -> Option<Tile> {
237 if x < self.width && y < self.height {
238 self.cells[y * self.width + x].tile
239 } else {
240 None
241 }
242 }
243
244 pub fn cell_marker(&self, x: usize, y: usize) -> Option<&str> {
246 if x < self.width && y < self.height {
247 self.cells[y * self.width + x].marker.as_deref()
248 } else {
249 None
250 }
251 }
252
253 pub fn cell_mask(&self, x: usize, y: usize) -> Option<&str> {
255 if x < self.width && y < self.height {
256 self.cells[y * self.width + x].mask.as_deref()
257 } else {
258 None
259 }
260 }
261
262 pub fn has_tag(&self, tag: &str) -> bool {
264 self.tags.contains(&tag.to_string())
265 }
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct PrefabLibraryData {
271 pub prefabs: Vec<PrefabData>,
272}
273
274#[derive(Debug, Clone)]
275pub struct PrefabLibrary {
277 prefabs: Vec<Prefab>,
278 by_tag: HashMap<String, Vec<usize>>,
279}
280
281impl PrefabLibrary {
282 pub fn new() -> Self {
284 Self {
285 prefabs: Vec::new(),
286 by_tag: HashMap::new(),
287 }
288 }
289
290 pub fn add_prefab(&mut self, prefab: Prefab) {
292 let index = self.prefabs.len();
293
294 for tag in &prefab.tags {
295 self.by_tag.entry(tag.clone()).or_default().push(index);
296 }
297
298 self.prefabs.push(prefab);
299 }
300
301 pub fn load_from_json<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
303 let content = std::fs::read_to_string(path)?;
304 let data: PrefabLibraryData = serde_json::from_str(&content)?;
305
306 let mut library = Self::new();
307 for prefab_data in data.prefabs {
308 library.add_prefab(Prefab::from_data(prefab_data));
309 }
310
311 Ok(library)
312 }
313
314 pub fn load_from_paths<I, P>(paths: I) -> Result<Self, Box<dyn std::error::Error>>
316 where
317 I: IntoIterator<Item = P>,
318 P: AsRef<Path>,
319 {
320 let mut library = Self::new();
321 for path in paths {
322 let loaded = Self::load_from_json(path)?;
323 library.extend_from(loaded);
324 }
325 Ok(library)
326 }
327
328 pub fn load_from_dir<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
330 let mut entries: Vec<std::path::PathBuf> = std::fs::read_dir(path)?
331 .filter_map(|entry| entry.ok().map(|e| e.path()))
332 .filter(|p| p.extension().and_then(|e| e.to_str()) == Some("json"))
333 .collect();
334 entries.sort();
335 if entries.is_empty() {
336 return Ok(Self::new());
337 }
338 Self::load_from_paths(entries)
339 }
340
341 pub fn save_to_json<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<dyn std::error::Error>> {
343 let data = PrefabLibraryData {
344 prefabs: self
345 .prefabs
346 .iter()
347 .map(|p| PrefabData {
348 name: p.name.clone(),
349 width: p.width,
350 height: p.height,
351 pattern: self.prefab_to_pattern(p),
352 weight: p.weight,
353 tags: p.tags.clone(),
354 legend: p.legend.as_ref().map(convert_legend_to_strings),
355 })
356 .collect(),
357 };
358
359 let content = serde_json::to_string_pretty(&data)?;
360 std::fs::write(path, content)?;
361 Ok(())
362 }
363
364 fn prefab_to_pattern(&self, prefab: &Prefab) -> Vec<String> {
365 let mut pattern = Vec::new();
366 for y in 0..prefab.height {
367 let mut row = String::new();
368 for x in 0..prefab.width {
369 let idx = y * prefab.width + x;
370 let symbol = prefab.symbols.get(idx).copied().unwrap_or_else(|| {
371 if prefab.get(x, y) {
372 '.'
373 } else {
374 '#'
375 }
376 });
377 row.push(symbol);
378 }
379 pattern.push(row);
380 }
381 pattern
382 }
383
384 pub fn get_prefabs(&self) -> &[Prefab] {
386 &self.prefabs
387 }
388
389 pub fn extend_from(&mut self, other: PrefabLibrary) {
391 for prefab in other.prefabs {
392 self.add_prefab(prefab);
393 }
394 }
395
396 pub fn get_by_tag(&self, tag: &str) -> Vec<&Prefab> {
398 self.by_tag
399 .get(tag)
400 .map(|indices| indices.iter().map(|&i| &self.prefabs[i]).collect())
401 .unwrap_or_default()
402 }
403
404 pub fn select_weighted(&self, rng: &mut Rng, tag: Option<&str>) -> Option<&Prefab> {
406 let tags = tag.map(|t| vec![t.to_string()]);
407 self.select_with_tags(rng, tags.as_deref(), true)
408 }
409
410 pub fn select_with_tags(
412 &self,
413 rng: &mut Rng,
414 tags: Option<&[String]>,
415 weighted: bool,
416 ) -> Option<&Prefab> {
417 let candidates = if let Some(tags) = tags {
418 self.get_by_any_tag(tags)
419 } else {
420 self.prefabs.iter().collect()
421 };
422
423 if candidates.is_empty() {
424 return None;
425 }
426
427 if !weighted {
428 return rng.pick(&candidates).copied();
429 }
430
431 let total_weight: f32 = candidates.iter().map(|p| p.weight).sum();
432 if total_weight <= 0.0 {
433 return rng.pick(&candidates).copied();
434 }
435
436 let mut target = rng.random() as f32 * total_weight;
437 for prefab in &candidates {
438 target -= prefab.weight;
439 if target <= 0.0 {
440 return Some(prefab);
441 }
442 }
443
444 candidates.last().copied()
445 }
446
447 pub fn get_by_any_tag(&self, tags: &[String]) -> Vec<&Prefab> {
449 if tags.is_empty() {
450 return Vec::new();
451 }
452 let mut indices = Vec::new();
453 for tag in tags {
454 if let Some(found) = self.by_tag.get(tag) {
455 indices.extend_from_slice(found);
456 }
457 }
458 if indices.is_empty() {
459 return Vec::new();
460 }
461 indices.sort_unstable();
462 indices.dedup();
463 indices.iter().map(|&i| &self.prefabs[i]).collect()
464 }
465
466 pub fn create_default() -> Self {
468 let mut library = Self::new();
469
470 let mut room = Prefab::rect(5, 5);
472 room.name = "small_room".to_string();
473 room.tags = vec!["room".to_string(), "small".to_string()];
474 library.add_prefab(room);
475
476 let mut corridor = Prefab::new(&[".....", "#####"]);
477 corridor.name = "corridor".to_string();
478 corridor.tags = vec!["corridor".to_string()];
479 library.add_prefab(corridor);
480
481 let mut cross = Prefab::new(&["#.#", "...", "#.#"]);
482 cross.name = "cross".to_string();
483 cross.tags = vec!["junction".to_string()];
484 library.add_prefab(cross);
485
486 library
487 }
488}
489
490impl Default for PrefabLibrary {
491 fn default() -> Self {
492 Self::create_default()
493 }
494}
495
496#[derive(Debug, Clone, Default)]
498pub struct PrefabTransform {
499 pub rotation: u8, pub mirror_h: bool,
501 pub mirror_v: bool,
502}
503
504impl PrefabTransform {
505 pub fn apply(&self, prefab: &Prefab) -> Prefab {
507 let mut result = prefab.clone();
508
509 if self.mirror_h {
511 result = result.mirrored_horizontal();
512 }
513 if self.mirror_v {
514 result = result.mirrored_vertical();
515 }
516
517 for _ in 0..self.rotation {
519 result = result.rotated();
520 }
521
522 result
523 }
524
525 pub fn random(rng: &mut Rng, allow_rotation: bool, allow_mirroring: bool) -> Self {
527 Self {
528 rotation: if allow_rotation {
529 rng.range(0, 4) as u8
530 } else {
531 0
532 },
533 mirror_h: allow_mirroring && rng.chance(0.5),
534 mirror_v: allow_mirroring && rng.chance(0.5),
535 }
536 }
537}
538#[derive(Debug, Clone)]
540pub struct PrefabPlacer {
541 config: PrefabConfig,
542 library: PrefabLibrary,
543}
544
545impl PrefabPlacer {
546 pub fn new(config: PrefabConfig, library: PrefabLibrary) -> Self {
548 Self { config, library }
549 }
550
551 pub fn with_library(library: PrefabLibrary) -> Self {
553 Self::new(PrefabConfig::default(), library)
554 }
555}
556
557impl Default for PrefabPlacer {
558 fn default() -> Self {
559 Self::with_library(PrefabLibrary::default())
560 }
561}
562
563impl Algorithm<Tile> for PrefabPlacer {
564 fn generate(&self, grid: &mut Grid<Tile>, seed: u64) {
565 self.generate_internal(grid, seed, None);
566 }
567
568 fn name(&self) -> &'static str {
569 "PrefabPlacer"
570 }
571}
572
573impl PrefabPlacer {
574 pub fn generate_with_semantic(
576 &self,
577 grid: &mut Grid<Tile>,
578 seed: u64,
579 semantic: &mut crate::semantic::SemanticLayers,
580 ) {
581 self.generate_internal(grid, seed, Some(semantic));
582 }
583
584 fn generate_internal(
585 &self,
586 grid: &mut Grid<Tile>,
587 seed: u64,
588 mut semantic: Option<&mut crate::semantic::SemanticLayers>,
589 ) {
590 let mut rng = Rng::new(seed);
591 let mut placed: Vec<(usize, usize, usize, usize)> = Vec::new();
592
593 for _ in 0..self.config.max_prefabs * 10 {
594 if placed.len() >= self.config.max_prefabs {
595 break;
596 }
597
598 let base_prefab = if let Some(prefab) = self.library.select_with_tags(
599 &mut rng,
600 self.config.tags.as_deref(),
601 self.config.weighted_selection,
602 ) {
603 prefab
604 } else {
605 continue;
606 };
607
608 let transform = PrefabTransform::random(
610 &mut rng,
611 self.config.allow_rotation,
612 self.config.allow_mirroring,
613 );
614 let prefab = transform.apply(base_prefab);
615
616 if prefab.width + 2 >= grid.width() || prefab.height + 2 >= grid.height() {
617 continue;
618 }
619
620 let x = rng.range_usize(1, grid.width() - prefab.width - 1);
621 let y = rng.range_usize(1, grid.height() - prefab.height - 1);
622
623 let overlaps = placed.iter().any(|&(px, py, pw, ph)| {
624 let s = self.config.min_spacing;
625 !(x + prefab.width + s < px
626 || px + pw + s < x
627 || y + prefab.height + s < py
628 || py + ph + s < y)
629 });
630
631 if overlaps {
632 continue;
633 }
634
635 for py in 0..prefab.height {
636 for px in 0..prefab.width {
637 let cell_tile = prefab.cell_tile(px, py);
638 let cell_marker = prefab.cell_marker(px, py);
639 let cell_mask = prefab.cell_mask(px, py);
640 let gx = (x + px) as i32;
641 let gy = (y + py) as i32;
642
643 let mut applied = false;
644 if let Some(tile) = cell_tile {
645 let current = *grid.get(gx, gy).unwrap_or(&Tile::Wall);
646 let should_place = match self.config.placement_mode {
647 PrefabPlacementMode::Overwrite => true,
648 PrefabPlacementMode::Merge => matches!(current, Tile::Wall),
649 PrefabPlacementMode::PaintFloor => matches!(current, Tile::Floor),
650 PrefabPlacementMode::PaintWall => matches!(current, Tile::Wall),
651 };
652 if should_place {
653 grid.set(gx, gy, tile);
654 applied = true;
655 }
656 }
657
658 if let Some(layers) = semantic.as_deref_mut() {
659 let marker_allowed = cell_tile.is_none() || applied;
660 if marker_allowed {
661 if let Some(tag) = cell_marker {
662 layers.markers.push(crate::semantic::Marker::with_tag(
663 gx as u32,
664 gy as u32,
665 tag.to_string(),
666 ));
667 }
668 if let Some(mask) = cell_mask {
669 apply_prefab_mask(&mut layers.masks, gx, gy, mask);
670 }
671 }
672 }
673 }
674 }
675 placed.push((x, y, prefab.width, prefab.height));
676 }
677 }
678}
679
680fn parse_pattern_with_legend(
681 pattern: &[impl AsRef<str>],
682 legend: Option<&HashMap<char, PrefabLegendEntry>>,
683) -> (Vec<PrefabCell>, Vec<char>) {
684 let mut cells = Vec::new();
685 let mut symbols = Vec::new();
686 for row in pattern {
687 for ch in row.as_ref().chars() {
688 let cell = if let Some(legend) = legend {
689 legend
690 .get(&ch)
691 .map(parse_legend_entry)
692 .unwrap_or_else(|| default_cell_from_symbol(ch))
693 } else {
694 default_cell_from_symbol(ch)
695 };
696 cells.push(cell);
697 symbols.push(ch);
698 }
699 }
700 (cells, symbols)
701}
702
703fn default_cell_from_symbol(ch: char) -> PrefabCell {
704 match ch {
705 '.' => PrefabCell {
706 tile: Some(Tile::Floor),
707 marker: None,
708 mask: None,
709 },
710 '#' => PrefabCell::default(),
711 _ => PrefabCell::default(),
712 }
713}
714
715fn parse_legend_entry(entry: &PrefabLegendEntry) -> PrefabCell {
716 PrefabCell {
717 tile: parse_tile_name(entry.tile.as_deref()),
718 marker: entry.marker.clone(),
719 mask: entry.mask.clone(),
720 }
721}
722
723fn parse_tile_name(value: Option<&str>) -> Option<Tile> {
724 let value = value?;
725 match value.trim().to_ascii_lowercase().as_str() {
726 "floor" | "f" => Some(Tile::Floor),
727 "wall" | "w" => Some(Tile::Wall),
728 "empty" | "none" | "skip" => None,
729 _ => None,
730 }
731}
732
733fn convert_legend(legend: &HashMap<String, PrefabLegendEntry>) -> HashMap<char, PrefabLegendEntry> {
734 let mut out = HashMap::new();
735 for (key, value) in legend {
736 let mut chars = key.chars();
737 if let Some(ch) = chars.next() {
738 out.insert(ch, value.clone());
739 }
740 }
741 out
742}
743
744fn convert_legend_to_strings(
745 legend: &HashMap<char, PrefabLegendEntry>,
746) -> HashMap<String, PrefabLegendEntry> {
747 legend
748 .iter()
749 .map(|(ch, entry)| (ch.to_string(), entry.clone()))
750 .collect()
751}
752
753fn apply_prefab_mask(masks: &mut crate::semantic::Masks, x: i32, y: i32, mask: &str) {
754 if x < 0 || y < 0 {
755 return;
756 }
757 let (x, y) = (x as usize, y as usize);
758 if y >= masks.height || x >= masks.width {
759 return;
760 }
761 match mask.trim().to_ascii_lowercase().as_str() {
762 "no_spawn" | "nospawn" | "reserved" => {
763 if let Some(row) = masks.no_spawn.get_mut(y) {
764 if let Some(cell) = row.get_mut(x) {
765 *cell = true;
766 }
767 }
768 }
769 _ => {}
770 }
771}