| @@ -79,6 +79,7 @@ pub struct Home { |
| 79 |
79 |
} |
| 80 |
80 |
|
| 81 |
81 |
impl Home { |
|
82 |
+ #[cfg(test)] |
| 82 |
83 |
pub fn at(lo: UVec2) -> Self { |
| 83 |
84 |
Self { |
| 84 |
85 |
lo, |
| @@ -86,6 +87,37 @@ impl Home { |
| 86 |
87 |
} |
| 87 |
88 |
} |
| 88 |
89 |
|
|
90 |
+ pub fn sized(lo: UVec2, interior: UVec2) -> Self { |
|
91 |
+ Self { |
|
92 |
+ lo, |
|
93 |
+ hi: lo + interior + UVec2::ONE, |
|
94 |
+ } |
|
95 |
+ } |
|
96 |
+ |
|
97 |
+ pub fn interior_size(&self) -> UVec2 { |
|
98 |
+ self.hi - self.lo - UVec2::ONE |
|
99 |
+ } |
|
100 |
+ |
|
101 |
+ pub fn area(&self) -> u32 { |
|
102 |
+ self.interior_size().element_product() |
|
103 |
+ } |
|
104 |
+ |
|
105 |
+ pub fn grown(&self, side: IVec2) -> Option<Self> { |
|
106 |
+ let lo = self.lo.as_ivec2() + side.min(IVec2::ZERO); |
|
107 |
+ let hi = self.hi.as_ivec2() + side.max(IVec2::ZERO); |
|
108 |
+ (lo.min_element() >= 0).then(|| Self { |
|
109 |
+ lo: lo.as_uvec2(), |
|
110 |
+ hi: hi.as_uvec2(), |
|
111 |
+ }) |
|
112 |
+ } |
|
113 |
+ |
|
114 |
+ pub fn bed_tiles_for(&self, beds: usize) -> Vec<UVec2> { |
|
115 |
+ (0..beds as u32) |
|
116 |
+ .map(|i| UVec2::new(self.lo.x + 1 + i, self.hi.y - 1)) |
|
117 |
+ .take_while(|t| t.x < self.hi.x - 1 || beds == 1) |
|
118 |
+ .collect() |
|
119 |
+ } |
|
120 |
+ |
| 89 |
121 |
pub fn contains(&self, tile: UVec2) -> bool { |
| 90 |
122 |
tile.cmpge(self.lo).all() && tile.cmple(self.hi).all() |
| 91 |
123 |
} |
| @@ -150,7 +182,12 @@ impl Home { |
| 150 |
182 |
}) |
| 151 |
183 |
} |
| 152 |
184 |
|
| 153 |
|
- pub fn blueprints(&self, map: &WorldMap, toward: UVec2) -> HashMap<UVec2, BuildKind> { |
|
185 |
+ pub fn blueprints( |
|
186 |
+ &self, |
|
187 |
+ map: &WorldMap, |
|
188 |
+ toward: UVec2, |
|
189 |
+ beds: usize, |
|
190 |
+ ) -> HashMap<UVec2, BuildKind> { |
| 154 |
191 |
let door = self.door_site(map, toward).unwrap_or(self.lo); |
| 155 |
192 |
let mut plan: HashMap<UVec2, BuildKind> = self |
| 156 |
193 |
.ring() |
| @@ -158,7 +195,7 @@ impl Home { |
| 158 |
195 |
.map(|t| (t, BuildKind::Structure(StructureKind::Wall))) |
| 159 |
196 |
.collect(); |
| 160 |
197 |
plan.insert(door, BuildKind::Structure(StructureKind::Door)); |
| 161 |
|
- for bed in self.bed_tiles() { |
|
198 |
+ for bed in self.bed_tiles_for(beds) { |
| 162 |
199 |
plan.insert(bed, BuildKind::Structure(StructureKind::Bed)); |
| 163 |
200 |
} |
| 164 |
201 |
plan |
| @@ -361,15 +398,61 @@ impl HomeContext<'_> { |
| 361 |
398 |
(interior_open && door).then_some(shared) |
| 362 |
399 |
} |
| 363 |
400 |
|
|
401 |
+ pub fn expansion(&self, home: Home, desired: UVec2) -> Option<(Home, Vec<UVec2>)> { |
|
402 |
+ let size = home.interior_size(); |
|
403 |
+ let sides: Vec<IVec2> = if size.x < desired.x { |
|
404 |
+ vec![IVec2::X, IVec2::NEG_X] |
|
405 |
+ } else if size.y < desired.y { |
|
406 |
+ vec![IVec2::Y, IVec2::NEG_Y] |
|
407 |
+ } else { |
|
408 |
+ vec![IVec2::X, IVec2::NEG_X, IVec2::Y, IVec2::NEG_Y] |
|
409 |
+ }; |
|
410 |
+ let wall = BuildKind::Structure(StructureKind::Wall); |
|
411 |
+ sides |
|
412 |
+ .into_iter() |
|
413 |
+ .filter_map(|side| { |
|
414 |
+ let grown = home.grown(side)?; |
|
415 |
+ if !self.map.in_bounds(grown.hi.as_ivec2()) { |
|
416 |
+ return None; |
|
417 |
+ } |
|
418 |
+ let new_ring: Vec<UVec2> = grown.ring().filter(|t| !home.contains(*t)).collect(); |
|
419 |
+ let new_inside: Vec<UVec2> = |
|
420 |
+ grown.interior().filter(|t| !home.contains(*t)).collect(); |
|
421 |
+ let mut shared = 0; |
|
422 |
+ for &t in &new_ring { |
|
423 |
+ if self.map.is_wall(t.as_ivec2()) || self.blueprints.get(t) == Some(wall) { |
|
424 |
+ shared += 1; |
|
425 |
+ } else if !self.open(t.as_ivec2()) || self.rooms.contains(t) { |
|
426 |
+ return None; |
|
427 |
+ } |
|
428 |
+ } |
|
429 |
+ let inside_open = new_inside |
|
430 |
+ .iter() |
|
431 |
+ .all(|t| self.open(t.as_ivec2()) && !self.rooms.contains(*t)); |
|
432 |
+ let opened: Vec<UVec2> = home |
|
433 |
+ .ring() |
|
434 |
+ .filter(|t| grown.interior().any(|i| i == *t)) |
|
435 |
+ .collect(); |
|
436 |
+ inside_open.then_some((shared, grown, opened)) |
|
437 |
+ }) |
|
438 |
+ .max_by_key(|(shared, ..)| *shared) |
|
439 |
+ .map(|(_, grown, opened)| (grown, opened)) |
|
440 |
+ } |
|
441 |
+ |
|
442 |
+ #[cfg(test)] |
| 364 |
443 |
pub fn plan(&self, colony_home: UVec2) -> Option<Home> { |
| 365 |
|
- let center = (HOME_INTERIOR / 2 + UVec2::ONE).as_ivec2(); |
|
444 |
+ self.plan_sized(colony_home, HOME_INTERIOR) |
|
445 |
+ } |
|
446 |
+ |
|
447 |
+ pub fn plan_sized(&self, colony_home: UVec2, interior: UVec2) -> Option<Home> { |
|
448 |
+ let center = (interior / 2 + UVec2::ONE).as_ivec2(); |
| 366 |
449 |
let mut candidates: Vec<(usize, Home)> = Vec::new(); |
| 367 |
450 |
nearest_tile(self.map, colony_home, HOME_SEARCH_TILES, |p| { |
| 368 |
451 |
let lo = p.as_ivec2() - center; |
| 369 |
452 |
if lo.min_element() < 0 { |
| 370 |
453 |
return false; |
| 371 |
454 |
} |
| 372 |
|
- let home = Home::at(lo.as_uvec2()); |
|
455 |
+ let home = Home::sized(lo.as_uvec2(), interior); |
| 373 |
456 |
let middle = (home.lo + home.hi).as_ivec2() / 2; |
| 374 |
457 |
if (middle - colony_home.as_ivec2()).abs().max_element() < HOME_MIN_DISTANCE { |
| 375 |
458 |
return false; |
| @@ -432,6 +515,31 @@ pub fn torn_down(map: &WorldMap, doors: &HashSet<UVec2>, home: &Home) -> bool { |
| 432 |
515 |
intact * 2 < ring.len() |
| 433 |
516 |
} |
| 434 |
517 |
|
|
518 |
+pub fn desired_interior(adults: usize, children: usize) -> UVec2 { |
|
519 |
+ match (adults.max(1), children) { |
|
520 |
+ (1, 0) => UVec2::new(3, 2), |
|
521 |
+ (_, 0) => HOME_INTERIOR, |
|
522 |
+ (_, 1) => UVec2::new(5, 3), |
|
523 |
+ _ => UVec2::new(5, 4), |
|
524 |
+ } |
|
525 |
+} |
|
526 |
+ |
|
527 |
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] |
|
528 |
+pub struct Household { |
|
529 |
+ pub partners: usize, |
|
530 |
+ pub children: usize, |
|
531 |
+} |
|
532 |
+ |
|
533 |
+impl Household { |
|
534 |
+ pub fn adults(&self) -> usize { |
|
535 |
+ self.partners.max(1) |
|
536 |
+ } |
|
537 |
+ |
|
538 |
+ pub fn desired(&self) -> UVec2 { |
|
539 |
+ desired_interior(self.adults(), self.children) |
|
540 |
+ } |
|
541 |
+} |
|
542 |
+ |
| 435 |
543 |
pub fn home_of_room(room: &Room) -> Option<Home> { |
| 436 |
544 |
let (lo, hi) = room.bounds(); |
| 437 |
545 |
let size = hi - lo + UVec2::ONE; |
| @@ -454,6 +562,7 @@ pub fn surplus_beds(communal: &[UVec2], homeless_adults: usize, toward: UVec2) - |
| 454 |
562 |
|
| 455 |
563 |
#[allow(clippy::too_many_arguments)] |
| 456 |
564 |
fn plan_homes( |
|
565 |
+ mut commands: Commands, |
| 457 |
566 |
tick: Res<SimTick>, |
| 458 |
567 |
map: Res<WorldMap>, |
| 459 |
568 |
zones: Res<Zones>, |
| @@ -461,7 +570,7 @@ fn plan_homes( |
| 461 |
570 |
mut blueprints: ResMut<Blueprints>, |
| 462 |
571 |
structures: Query<(&TilePos, &Structure), Without<Colonist>>, |
| 463 |
572 |
items: Query<&TilePos, With<Item>>, |
| 464 |
|
- families: Query<(Option<&Family>, Has<Partner>, Has<Child>), With<Colonist>>, |
|
573 |
+ families: Query<(Entity, Option<&Family>, Has<Partner>, Has<Child>), With<Colonist>>, |
| 465 |
574 |
mut colony_reach: ResMut<ColonyReach>, |
| 466 |
575 |
mut flood: Local<Option<((u64, UVec2, UVec2), Reach)>>, |
| 467 |
576 |
rooms: Res<Rooms>, |
| @@ -472,9 +581,18 @@ fn plan_homes( |
| 472 |
581 |
let colony_home = zones.home(map.size / 2); |
| 473 |
582 |
let living: HashSet<u32> = families |
| 474 |
583 |
.iter() |
| 475 |
|
- .filter_map(|(f, ..)| f) |
|
584 |
+ .filter_map(|(_, f, ..)| f) |
| 476 |
585 |
.map(|f| f.0) |
| 477 |
586 |
.collect(); |
|
587 |
+ let mut households: HashMap<u32, Household> = HashMap::new(); |
|
588 |
+ for (_, family, partnered, child) in &families { |
|
589 |
+ if let Some(family) = family { |
|
590 |
+ let household = households.entry(family.0).or_default(); |
|
591 |
+ household.partners += partnered as usize; |
|
592 |
+ household.children += child as usize; |
|
593 |
+ } |
|
594 |
+ } |
|
595 |
+ let household = |family: u32| households.get(&family).copied().unwrap_or_default(); |
| 478 |
596 |
let extinct: Vec<u32> = homes |
| 479 |
597 |
.by_family |
| 480 |
598 |
.keys() |
| @@ -517,21 +635,36 @@ fn plan_homes( |
| 517 |
635 |
let homeless_family = |homes: &Homes| { |
| 518 |
636 |
families |
| 519 |
637 |
.iter() |
| 520 |
|
- .filter(|(_, partnered, _)| *partnered) |
| 521 |
|
- .filter_map(|(f, ..)| f) |
|
638 |
+ .filter(|(_, _, partnered, _)| *partnered) |
|
639 |
+ .filter_map(|(_, f, ..)| f) |
| 522 |
640 |
.map(|f| f.0) |
| 523 |
641 |
.filter(|f| !homes.by_family.contains_key(f)) |
| 524 |
642 |
.min() |
| 525 |
643 |
}; |
| 526 |
|
- while let Some(family) = homeless_family(&homes) |
| 527 |
|
- && let Some(index) = (0..homes.vacant.len()).min_by_key(|&i| { |
| 528 |
|
- let d = homes.vacant[i].lo.as_ivec2() - colony_home.as_ivec2(); |
| 529 |
|
- d.length_squared() |
|
644 |
+ let pick_vacant = |homes: &Homes, desired: UVec2| { |
|
645 |
+ (0..homes.vacant.len()).min_by_key(|&i| { |
|
646 |
+ let home = homes.vacant[i]; |
|
647 |
+ let d = home.lo.as_ivec2() - colony_home.as_ivec2(); |
|
648 |
+ (home.area() < desired.element_product(), d.length_squared()) |
| 530 |
649 |
}) |
|
650 |
+ }; |
|
651 |
+ while let Some(family) = homeless_family(&homes) |
|
652 |
+ && let Some(index) = pick_vacant(&homes, household(family).desired()) |
| 531 |
653 |
{ |
| 532 |
654 |
let home = homes.vacant.swap_remove(index); |
| 533 |
655 |
homes.by_family.insert(family, home); |
| 534 |
656 |
} |
|
657 |
+ let solo = families |
|
658 |
+ .iter() |
|
659 |
+ .filter(|(_, family, partnered, child)| { |
|
660 |
+ !partnered |
|
661 |
+ && !child |
|
662 |
+ && family.is_none_or(|f| { |
|
663 |
+ household(f.0).partners > 0 || !homes.by_family.contains_key(&f.0) |
|
664 |
+ }) |
|
665 |
+ }) |
|
666 |
+ .map(|(entity, ..)| entity) |
|
667 |
+ .min(); |
| 535 |
668 |
let anchored = map.move_cost(colony_home.as_ivec2()).is_some(); |
| 536 |
669 |
let key = (map.shape_version(), colony_home, map.size); |
| 537 |
670 |
let stale = flood.as_ref().is_none_or(|(cached, _)| *cached != key) |
| @@ -545,7 +678,8 @@ fn plan_homes( |
| 545 |
678 |
blueprints |
| 546 |
679 |
.0 |
| 547 |
680 |
.retain(|tile, _| !anchored || reachable.near(*tile)); |
| 548 |
|
- let owned: HashSet<Home> = homes.by_family.values().copied().collect(); |
|
681 |
+ let owned: HashMap<Home, u32> = homes.by_family.iter().map(|(f, h)| (*h, *f)).collect(); |
|
682 |
+ let mut expansions: Vec<(u32, Home, Vec<UVec2>)> = Vec::new(); |
| 549 |
683 |
let door = BuildKind::Structure(StructureKind::Door); |
| 550 |
684 |
let entries: HashSet<UVec2> = blueprints |
| 551 |
685 |
.0 |
| @@ -563,7 +697,20 @@ fn plan_homes( |
| 563 |
697 |
continue; |
| 564 |
698 |
} |
| 565 |
699 |
let built = home.interior().all(|t| map.is_roofed(t.as_ivec2())); |
| 566 |
|
- if built && owned.contains(home) { |
|
700 |
+ if let Some(&family) = owned.get(home).filter(|_| built) { |
|
701 |
+ let needs = household(family); |
|
702 |
+ let desired = needs.desired(); |
|
703 |
+ if home.area() < desired.element_product() { |
|
704 |
+ expansions.push((family, *home, Vec::new())); |
|
705 |
+ continue; |
|
706 |
+ } |
|
707 |
+ let bed = BuildKind::Structure(StructureKind::Bed); |
|
708 |
+ let have = beds.iter().filter(|b| home.contains(**b)).count(); |
|
709 |
+ for tile in home.bed_tiles_for(needs.adults()).into_iter().skip(have) { |
|
710 |
+ if !beds.contains(&tile) { |
|
711 |
+ blueprints.0.entry(tile).or_insert(bed); |
|
712 |
+ } |
|
713 |
+ } |
| 567 |
714 |
if !hearths.iter().any(|&t| home.contains(t)) { |
| 568 |
715 |
let hearth = BuildKind::Structure(StructureKind::Hearth); |
| 569 |
716 |
blueprints.0.insert(home.hearth_tile(), hearth); |
| @@ -579,7 +726,7 @@ fn plan_homes( |
| 579 |
726 |
} |
| 580 |
727 |
let homeless_adults = families |
| 581 |
728 |
.iter() |
| 582 |
|
- .filter(|(family, _, child)| !child && homes.of(family.copied()).is_none()) |
|
729 |
+ .filter(|(_, family, _, child)| !child && homes.of(family.copied()).is_none()) |
| 583 |
730 |
.count(); |
| 584 |
731 |
let communal: Vec<UVec2> = beds |
| 585 |
732 |
.iter() |
| @@ -589,9 +736,6 @@ fn plan_homes( |
| 589 |
736 |
for bed in surplus_beds(&communal, homeless_adults, colony_home) { |
| 590 |
737 |
blueprints.0.insert(bed, BuildKind::Deconstruct); |
| 591 |
738 |
} |
| 592 |
|
- let Some(family) = homeless_family(&homes) else { |
| 593 |
|
- return; |
| 594 |
|
- }; |
| 595 |
739 |
let occupied: HashSet<UVec2> = structures |
| 596 |
740 |
.iter() |
| 597 |
741 |
.map(|(pos, _)| pos.0) |
| @@ -605,11 +749,69 @@ fn plan_homes( |
| 605 |
749 |
homes: &homes, |
| 606 |
750 |
occupied: &occupied, |
| 607 |
751 |
}; |
| 608 |
|
- if let Some(home) = ctx.plan(colony_home) { |
| 609 |
|
- let plan = home.blueprints(&map, colony_home); |
| 610 |
|
- blueprints.0.extend(plan); |
| 611 |
|
- homes.by_family.insert(family, home); |
|
752 |
+ let mut grown_homes = Vec::new(); |
|
753 |
+ for (family, home, _) in expansions { |
|
754 |
+ if let Some((grown, opened)) = ctx.expansion(home, household(family).desired()) { |
|
755 |
+ grown_homes.push((family, grown, opened)); |
|
756 |
+ } |
|
757 |
+ } |
|
758 |
+ enum Plan { |
|
759 |
+ Family(u32, Home), |
|
760 |
+ SoloVacant(Entity, usize), |
|
761 |
+ Solo(Entity, Home), |
|
762 |
+ } |
|
763 |
+ let plan = if let Some(family) = homeless_family(&homes) { |
|
764 |
+ ctx.plan_sized(colony_home, household(family).desired()) |
|
765 |
+ .map(|home| Plan::Family(family, home)) |
|
766 |
+ } else if let Some(entity) = solo { |
|
767 |
+ let desired = desired_interior(1, 0); |
|
768 |
+ match pick_vacant(&homes, desired) |
|
769 |
+ .filter(|&i| homes.vacant[i].area() <= HOME_INTERIOR.element_product()) |
|
770 |
+ { |
|
771 |
+ Some(index) => Some(Plan::SoloVacant(entity, index)), |
|
772 |
+ None => ctx |
|
773 |
+ .plan_sized(colony_home, desired) |
|
774 |
+ .map(|home| Plan::Solo(entity, home)), |
|
775 |
+ } |
|
776 |
+ } else { |
|
777 |
+ None |
|
778 |
+ }; |
|
779 |
+ let wall = BuildKind::Structure(StructureKind::Wall); |
|
780 |
+ for (family, grown, opened) in grown_homes { |
|
781 |
+ for tile in grown.ring().filter(|t| !map.is_wall(t.as_ivec2())) { |
|
782 |
+ if !doors.contains(&tile) { |
|
783 |
+ blueprints.0.entry(tile).or_insert(wall); |
|
784 |
+ } |
|
785 |
+ } |
|
786 |
+ for tile in opened { |
|
787 |
+ if map.is_wall(tile.as_ivec2()) || doors.contains(&tile) { |
|
788 |
+ blueprints.0.insert(tile, BuildKind::Deconstruct); |
|
789 |
+ } else { |
|
790 |
+ blueprints.0.remove(&tile); |
|
791 |
+ } |
|
792 |
+ } |
|
793 |
+ homes.by_family.insert(family, grown); |
| 612 |
794 |
} |
|
795 |
+ let (family, home, beds) = match plan { |
|
796 |
+ None => return, |
|
797 |
+ Some(Plan::Family(family, home)) => (family, home, household(family).adults()), |
|
798 |
+ Some(Plan::SoloVacant(entity, index)) => { |
|
799 |
+ let family = homes.new_family(); |
|
800 |
+ commands.entity(entity).insert(family); |
|
801 |
+ let home = homes.vacant.swap_remove(index); |
|
802 |
+ homes.by_family.insert(family.0, home); |
|
803 |
+ return; |
|
804 |
+ } |
|
805 |
+ Some(Plan::Solo(entity, home)) => { |
|
806 |
+ let family = homes.new_family(); |
|
807 |
+ commands.entity(entity).insert(family); |
|
808 |
+ (family.0, home, 1) |
|
809 |
+ } |
|
810 |
+ }; |
|
811 |
+ blueprints |
|
812 |
+ .0 |
|
813 |
+ .extend(home.blueprints(&map, colony_home, beds)); |
|
814 |
+ homes.by_family.insert(family, home); |
| 613 |
815 |
} |
| 614 |
816 |
|
| 615 |
817 |
#[allow(clippy::too_many_arguments, clippy::type_complexity)] |
| @@ -812,7 +1014,7 @@ mod tests { |
| 812 |
1014 |
fn a_home_is_a_walled_room_with_a_door_and_two_beds() { |
| 813 |
1015 |
let home = Home::at(UVec2::new(10, 10)); |
| 814 |
1016 |
let map = WorldMap::filled(UVec2::splat(20), Terrain::Grass); |
| 815 |
|
- let plan = home.blueprints(&map, UVec2::new(12, 0)); |
|
1017 |
+ let plan = home.blueprints(&map, UVec2::new(12, 0), 2); |
| 816 |
1018 |
let count = |kind: StructureKind| { |
| 817 |
1019 |
plan.values() |
| 818 |
1020 |
.filter(|k| **k == BuildKind::Structure(kind)) |
| @@ -881,7 +1083,11 @@ mod tests { |
| 881 |
1083 |
.interior() |
| 882 |
1084 |
.filter(|t| blueprints.get(*t) == Some(BuildKind::Floor)) |
| 883 |
1085 |
.count(); |
| 884 |
|
- assert_eq!(floors, home.interior().count() - 2); |
|
1086 |
+ assert_eq!(floors, home.interior().count() - 3); |
|
1087 |
+ assert_eq!( |
|
1088 |
+ blueprints.get(home.bed_tiles_for(1)[0]), |
|
1089 |
+ Some(BuildKind::Structure(StructureKind::Bed)) |
|
1090 |
+ ); |
| 885 |
1091 |
assert_eq!( |
| 886 |
1092 |
blueprints.get(home.hearth_tile()), |
| 887 |
1093 |
Some(BuildKind::Structure(StructureKind::Hearth)) |
| @@ -911,7 +1117,7 @@ mod tests { |
| 911 |
1117 |
zones.add_tiles(&map, [UVec2::new(20, 20)]); |
| 912 |
1118 |
let home = plan_on(&map, &zones, &HashSet::new()); |
| 913 |
1119 |
assert!(home.lo.x == 27 || home.hi.x == 27, "{home:?}"); |
| 914 |
|
- let plan = home.blueprints(&map, UVec2::new(20, 20)); |
|
1120 |
+ let plan = home.blueprints(&map, UVec2::new(20, 20), 2); |
| 915 |
1121 |
assert!(plan.keys().all(|t| t.x != 27)); |
| 916 |
1122 |
let door = plan |
| 917 |
1123 |
.iter() |
| @@ -982,8 +1188,9 @@ mod tests { |
| 982 |
1188 |
assert!(blueprints.get(UVec2::new(12, 12)).is_some()); |
| 983 |
1189 |
} |
| 984 |
1190 |
|
| 985 |
|
- |
| 986 |
|
- |
|
1191 |
+ |
|
1192 |
+ |
|
1193 |
+ |
| 987 |
1194 |
#[test] |
| 988 |
1195 |
fn vacant_homes_are_reused_and_spare_beds_removed() { |
| 989 |
1196 |
let mut map = WorldMap::filled(UVec2::splat(30), Terrain::Grass); |
| @@ -1038,12 +1245,10 @@ mod tests { |
| 1038 |
1245 |
.filter(|k| **k == BuildKind::Deconstruct) |
| 1039 |
1246 |
.count(); |
| 1040 |
1247 |
assert_eq!(removed, 3); |
| 1041 |
|
- assert!( |
| 1042 |
|
- !blueprints |
| 1043 |
|
- .0 |
| 1044 |
|
- .values() |
| 1045 |
|
- .any(|k| *k == BuildKind::Structure(StructureKind::Wall)) |
| 1046 |
|
- ); |
|
1248 |
+ let homes = app.world().resource::<Homes>(); |
|
1249 |
+ assert_eq!(homes.by_family.len(), 2); |
|
1250 |
+ let solo = homes.by_family.values().find(|h| **h != old).unwrap(); |
|
1251 |
+ assert_eq!(solo.interior_size(), desired_interior(1, 0)); |
| 1047 |
1252 |
} |
| 1048 |
1253 |
|
| 1049 |
1254 |
|
| @@ -1080,4 +1285,75 @@ mod tests { |
| 1080 |
1285 |
app.update(); |
| 1081 |
1286 |
assert!(app.world().resource::<Homes>().of(Some(Family(1))) != Some(&home)); |
| 1082 |
1287 |
} |
|
1288 |
+ |
|
1289 |
+ |
|
1290 |
+ |
|
1291 |
+ |
|
1292 |
+ #[test] |
|
1293 |
+ fn growing_families_move_a_wall_out() { |
|
1294 |
+ assert_eq!(desired_interior(1, 0), UVec2::new(3, 2)); |
|
1295 |
+ assert_eq!(desired_interior(2, 1), UVec2::new(5, 3)); |
|
1296 |
+ let mut map = WorldMap::filled(UVec2::splat(30), Terrain::Grass); |
|
1297 |
+ let home = Home::at(UVec2::new(10, 10)); |
|
1298 |
+ let door = UVec2::new(12, 10); |
|
1299 |
+ for tile in home.ring().filter(|t| *t != door) { |
|
1300 |
+ map.set_wall(tile, true); |
|
1301 |
+ } |
|
1302 |
+ for tile in home.interior() { |
|
1303 |
+ map.set_roof(tile, true); |
|
1304 |
+ } |
|
1305 |
+ let mut app = App::new(); |
|
1306 |
+ app.insert_resource(map.clone()) |
|
1307 |
+ .init_resource::<SimTick>() |
|
1308 |
+ .init_resource::<Zones>() |
|
1309 |
+ .init_resource::<Blueprints>() |
|
1310 |
+ .init_resource::<ColonyReach>() |
|
1311 |
+ .insert_resource(Rooms::detect(&map, &HashSet::from([door]))) |
|
1312 |
+ .insert_resource(Homes { |
|
1313 |
+ by_family: HashMap::from([(1, home)]), |
|
1314 |
+ ..default() |
|
1315 |
+ }) |
|
1316 |
+ .add_systems(Update, plan_homes); |
|
1317 |
+ let world = app.world_mut(); |
|
1318 |
+ world.spawn(( |
|
1319 |
+ Structure { |
|
1320 |
+ kind: StructureKind::Door, |
|
1321 |
+ }, |
|
1322 |
+ TilePos(door), |
|
1323 |
+ )); |
|
1324 |
+ let a = world |
|
1325 |
+ .spawn((Colonist { name: "Ada".into() }, Family(1))) |
|
1326 |
+ .id(); |
|
1327 |
+ let b = world |
|
1328 |
+ .spawn(( |
|
1329 |
+ Colonist { |
|
1330 |
+ name: "Bram".into(), |
|
1331 |
+ }, |
|
1332 |
+ Family(1), |
|
1333 |
+ Partner(a), |
|
1334 |
+ )) |
|
1335 |
+ .id(); |
|
1336 |
+ world.entity_mut(a).insert(Partner(b)); |
|
1337 |
+ world.spawn(( |
|
1338 |
+ Colonist { |
|
1339 |
+ name: "Cleo".into(), |
|
1340 |
+ }, |
|
1341 |
+ Family(1), |
|
1342 |
+ Child { born: 0 }, |
|
1343 |
+ )); |
|
1344 |
+ app.update(); |
|
1345 |
+ let grown = app.world().resource::<Homes>().by_family[&1]; |
|
1346 |
+ assert_eq!(grown.interior_size(), UVec2::new(5, 3)); |
|
1347 |
+ let blueprints = app.world().resource::<Blueprints>(); |
|
1348 |
+ let opened = home.ring().filter(|t| grown.interior().any(|i| i == *t)); |
|
1349 |
+ for tile in opened { |
|
1350 |
+ assert_eq!(blueprints.get(tile), Some(BuildKind::Deconstruct)); |
|
1351 |
+ } |
|
1352 |
+ let walls = grown |
|
1353 |
+ .ring() |
|
1354 |
+ .filter(|t| !home.contains(*t)) |
|
1355 |
+ .filter(|t| blueprints.get(*t) == Some(BuildKind::Structure(StructureKind::Wall))) |
|
1356 |
+ .count(); |
|
1357 |
+ assert_eq!(walls, 5); |
|
1358 |
+ } |
| 1083 |
1359 |
} |