| @@ -69,7 +69,7 @@ impl Child { |
| 69 |
69 |
} |
| 70 |
70 |
} |
| 71 |
71 |
|
| 72 |
|
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] |
|
72 |
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] |
| 73 |
73 |
pub struct Home { |
| 74 |
74 |
pub lo: UVec2, |
| 75 |
75 |
pub hi: UVec2, |
| @@ -104,17 +104,36 @@ impl Home { |
| 104 |
104 |
[corner, corner + UVec2::X] |
| 105 |
105 |
} |
| 106 |
106 |
|
|
107 |
+ pub fn outward(&self, tile: UVec2) -> IVec2 { |
|
108 |
+ let t = tile.as_ivec2(); |
|
109 |
+ let step = if tile.y == self.lo.y { |
|
110 |
+ IVec2::NEG_Y |
|
111 |
+ } else if tile.y == self.hi.y { |
|
112 |
+ IVec2::Y |
|
113 |
+ } else if tile.x == self.lo.x { |
|
114 |
+ IVec2::NEG_X |
|
115 |
+ } else { |
|
116 |
+ IVec2::X |
|
117 |
+ }; |
|
118 |
+ t + step |
|
119 |
+ } |
|
120 |
+ |
|
121 |
+ fn is_corner(&self, t: UVec2) -> bool { |
|
122 |
+ (t.x == self.lo.x || t.x == self.hi.x) && (t.y == self.lo.y || t.y == self.hi.y) |
|
123 |
+ } |
|
124 |
+ |
|
125 |
+ pub fn door_sites(&self) -> impl Iterator<Item = UVec2> + use<> { |
|
126 |
+ let home = *self; |
|
127 |
+ self.ring().filter(move |t| !home.is_corner(*t)) |
|
128 |
+ } |
|
129 |
+ |
| 107 |
130 |
pub fn statue_tile(&self) -> UVec2 { |
| 108 |
131 |
UVec2::new(self.hi.x - 1, self.lo.y + 1) |
| 109 |
132 |
} |
| 110 |
133 |
|
| 111 |
134 |
pub fn blueprints(&self, toward: UVec2) -> HashMap<UVec2, BuildKind> { |
| 112 |
|
- let corner = |t: &UVec2| { |
| 113 |
|
- (t.x == self.lo.x || t.x == self.hi.x) && (t.y == self.lo.y || t.y == self.hi.y) |
| 114 |
|
- }; |
| 115 |
135 |
let door = self |
| 116 |
|
- .ring() |
| 117 |
|
- .filter(|t| !corner(t)) |
|
136 |
+ .door_sites() |
| 118 |
137 |
.min_by(|a, b| { |
| 119 |
138 |
a.as_vec2() |
| 120 |
139 |
.distance_squared(toward.as_vec2()) |
| @@ -138,6 +157,8 @@ impl Home { |
| 138 |
157 |
pub struct Homes { |
| 139 |
158 |
pub by_family: HashMap<u32, Home>, |
| 140 |
159 |
next_family: u32, |
|
160 |
+ #[serde(default)] |
|
161 |
+ pub vacant: Vec<Home>, |
| 141 |
162 |
} |
| 142 |
163 |
|
| 143 |
164 |
impl Homes { |
| @@ -153,6 +174,14 @@ impl Homes { |
| 153 |
174 |
.map(|(family, _)| *family) |
| 154 |
175 |
} |
| 155 |
176 |
|
|
177 |
+ pub fn all(&self) -> impl Iterator<Item = &Home> { |
|
178 |
+ self.by_family.values().chain(&self.vacant) |
|
179 |
+ } |
|
180 |
+ |
|
181 |
+ pub fn is_home(&self, tile: UVec2) -> bool { |
|
182 |
+ self.all().any(|home| home.contains(tile)) |
|
183 |
+ } |
|
184 |
+ |
| 156 |
185 |
pub fn of(&self, family: Option<Family>) -> Option<&Home> { |
| 157 |
186 |
self.by_family.get(&family?.0) |
| 158 |
187 |
} |
| @@ -163,6 +192,7 @@ pub struct Affinity(pub HashMap<(Entity, Entity), u32>); |
| 163 |
192 |
|
| 164 |
193 |
fn clear_families(mut homes: ResMut<Homes>, mut affinity: ResMut<Affinity>) { |
| 165 |
194 |
homes.by_family.clear(); |
|
195 |
+ homes.vacant.clear(); |
| 166 |
196 |
affinity.0.clear(); |
| 167 |
197 |
} |
| 168 |
198 |
|
| @@ -274,7 +304,7 @@ impl HomeContext<'_> { |
| 274 |
304 |
self.zones.is_stockpile(tile) |
| 275 |
305 |
|| self.zones.is_farm(tile) |
| 276 |
306 |
|| self.blueprints.get(tile).is_some() |
| 277 |
|
- || self.homes.owner(tile).is_some() |
|
307 |
+ || self.homes.is_home(tile) |
| 278 |
308 |
} |
| 279 |
309 |
|
| 280 |
310 |
fn open(&self, tile: IVec2) -> bool { |
| @@ -296,7 +326,8 @@ impl HomeContext<'_> { |
| 296 |
326 |
if core { |
| 297 |
327 |
self.open(tile) |
| 298 |
328 |
} else { |
| 299 |
|
- !self.map.in_bounds(tile) || !self.claimed(tile.as_uvec2()) |
|
329 |
+ !self.map.in_bounds(tile) |
|
330 |
+ || (!self.claimed(tile.as_uvec2()) && self.map.move_cost(tile).is_some()) |
| 300 |
331 |
} |
| 301 |
332 |
}) |
| 302 |
333 |
}) |
| @@ -322,6 +353,56 @@ impl HomeContext<'_> { |
| 322 |
353 |
} |
| 323 |
354 |
} |
| 324 |
355 |
|
|
356 |
+pub fn door_repair( |
|
357 |
+ map: &WorldMap, |
|
358 |
+ doors: &HashSet<UVec2>, |
|
359 |
+ reachable: &HashSet<UVec2>, |
|
360 |
+ home: &Home, |
|
361 |
+ toward: UVec2, |
|
362 |
+) -> Option<(UVec2, BuildKind)> { |
|
363 |
+ let open_outside = |t: &UVec2| { |
|
364 |
+ let out = home.outward(*t); |
|
365 |
+ out.min_element() >= 0 && reachable.contains(&out.as_uvec2()) |
|
366 |
+ }; |
|
367 |
+ if home |
|
368 |
+ .door_sites() |
|
369 |
+ .any(|t| doors.contains(&t) && open_outside(&t)) |
|
370 |
+ { |
|
371 |
+ return None; |
|
372 |
+ } |
|
373 |
+ let site = home |
|
374 |
+ .door_sites() |
|
375 |
+ .filter(|t| !doors.contains(t) && open_outside(t)) |
|
376 |
+ .min_by_key(|t| { |
|
377 |
+ let d = t.as_ivec2() - toward.as_ivec2(); |
|
378 |
+ (map.is_wall(t.as_ivec2()), d.length_squared(), t.y, t.x) |
|
379 |
+ })?; |
|
380 |
+ let kind = if map.is_wall(site.as_ivec2()) { |
|
381 |
+ BuildKind::Deconstruct |
|
382 |
+ } else { |
|
383 |
+ BuildKind::Structure(StructureKind::Door) |
|
384 |
+ }; |
|
385 |
+ Some((site, kind)) |
|
386 |
+} |
|
387 |
+ |
|
388 |
+pub fn abandoned_home(map: &WorldMap, doors: &HashSet<UVec2>, bed: UVec2) -> Option<Home> { |
|
389 |
+ let room = enclosed_room(map, doors, bed)?; |
|
390 |
+ let lo = room.iter().fold(UVec2::MAX, |a, t| a.min(*t)); |
|
391 |
+ let hi = room.iter().fold(UVec2::ZERO, |a, t| a.max(*t)); |
|
392 |
+ let size = hi - lo + UVec2::ONE; |
|
393 |
+ (size == HOME_INTERIOR && room.len() == size.element_product() as usize && lo.min_element() > 0) |
|
394 |
+ .then(|| Home::at(lo - UVec2::ONE)) |
|
395 |
+} |
|
396 |
+ |
|
397 |
+pub fn surplus_beds(communal: &[UVec2], homeless_adults: usize, toward: UVec2) -> Vec<UVec2> { |
|
398 |
+ let mut beds = communal.to_vec(); |
|
399 |
+ beds.sort_by_key(|b| { |
|
400 |
+ let d = b.as_ivec2() - toward.as_ivec2(); |
|
401 |
+ (d.length_squared(), b.y, b.x) |
|
402 |
+ }); |
|
403 |
+ beds.split_off(homeless_adults.min(beds.len())) |
|
404 |
+} |
|
405 |
+ |
| 325 |
406 |
pub fn roofs_needed(map: &WorldMap, doors: &HashSet<UVec2>, home: &Home) -> Vec<UVec2> { |
| 326 |
407 |
let interior: Vec<UVec2> = home.interior().collect(); |
| 327 |
408 |
let Some(room) = enclosed_room(map, doors, interior[0]) else { |
| @@ -345,24 +426,93 @@ fn plan_homes( |
| 345 |
426 |
mut blueprints: ResMut<Blueprints>, |
| 346 |
427 |
structures: Query<(&TilePos, &Structure), Without<Colonist>>, |
| 347 |
428 |
items: Query<&TilePos, With<Item>>, |
| 348 |
|
- families: Query<(&Family, Has<Partner>), With<Colonist>>, |
|
429 |
+ families: Query<(Option<&Family>, Has<Partner>, Has<Child>), With<Colonist>>, |
| 349 |
430 |
) { |
| 350 |
431 |
if !tick.0.is_multiple_of(HOME_PLAN_INTERVAL_TICKS) { |
| 351 |
432 |
return; |
| 352 |
433 |
} |
| 353 |
|
- let living: HashSet<u32> = families.iter().map(|(f, _)| f.0).collect(); |
| 354 |
|
- homes.by_family.retain(|family, _| living.contains(family)); |
| 355 |
|
- let doors: HashSet<UVec2> = structures |
|
434 |
+ let colony_home = zones.home(map.size / 2); |
|
435 |
+ let living: HashSet<u32> = families |
| 356 |
436 |
.iter() |
| 357 |
|
- .filter(|(_, s)| s.kind == StructureKind::Door) |
| 358 |
|
- .map(|(pos, _)| pos.0) |
|
437 |
+ .filter_map(|(f, ..)| f) |
|
438 |
+ .map(|f| f.0) |
| 359 |
439 |
.collect(); |
| 360 |
|
- let statues: HashSet<UVec2> = structures |
|
440 |
+ let extinct: Vec<u32> = homes |
|
441 |
+ .by_family |
|
442 |
+ .keys() |
|
443 |
+ .filter(|f| !living.contains(f)) |
|
444 |
+ .copied() |
|
445 |
+ .collect(); |
|
446 |
+ for family in extinct { |
|
447 |
+ if let Some(home) = homes.by_family.remove(&family) { |
|
448 |
+ homes.vacant.push(home); |
|
449 |
+ } |
|
450 |
+ } |
|
451 |
+ let of_kind = |kind: StructureKind| -> HashSet<UVec2> { |
|
452 |
+ structures |
|
453 |
+ .iter() |
|
454 |
+ .filter(|(_, s)| s.kind == kind) |
|
455 |
+ .map(|(pos, _)| pos.0) |
|
456 |
+ .collect() |
|
457 |
+ }; |
|
458 |
+ let doors = of_kind(StructureKind::Door); |
|
459 |
+ let statues = of_kind(StructureKind::Statue); |
|
460 |
+ let beds = of_kind(StructureKind::Bed); |
|
461 |
+ for &bed in &beds { |
|
462 |
+ if !homes.is_home(bed) |
|
463 |
+ && let Some(home) = abandoned_home(&map, &doors, bed) |
|
464 |
+ && !homes.all().any(|h| h.ring().any(|t| home.contains(t))) |
|
465 |
+ { |
|
466 |
+ homes.vacant.push(home); |
|
467 |
+ } |
|
468 |
+ } |
|
469 |
+ let homeless_family = |homes: &Homes| { |
|
470 |
+ families |
|
471 |
+ .iter() |
|
472 |
+ .filter(|(_, partnered, _)| *partnered) |
|
473 |
+ .filter_map(|(f, ..)| f) |
|
474 |
+ .map(|f| f.0) |
|
475 |
+ .filter(|f| !homes.by_family.contains_key(f)) |
|
476 |
+ .min() |
|
477 |
+ }; |
|
478 |
+ while let Some(family) = homeless_family(&homes) |
|
479 |
+ && let Some(index) = (0..homes.vacant.len()).min_by_key(|&i| { |
|
480 |
+ let d = homes.vacant[i].lo.as_ivec2() - colony_home.as_ivec2(); |
|
481 |
+ d.length_squared() |
|
482 |
+ }) |
|
483 |
+ { |
|
484 |
+ let home = homes.vacant.swap_remove(index); |
|
485 |
+ homes.by_family.insert(family, home); |
|
486 |
+ } |
|
487 |
+ let mut reachable = HashSet::new(); |
|
488 |
+ nearest_tile(&map, colony_home, usize::MAX, |p| { |
|
489 |
+ reachable.insert(p); |
|
490 |
+ false |
|
491 |
+ }); |
|
492 |
+ let anchored = map.move_cost(colony_home.as_ivec2()).is_some(); |
|
493 |
+ blueprints.0.retain(|tile, _| { |
|
494 |
+ !anchored |
|
495 |
+ || (-1..=1).any(|dy| { |
|
496 |
+ (-1..=1).any(|dx| { |
|
497 |
+ let near = tile.as_ivec2() + IVec2::new(dx, dy); |
|
498 |
+ near.min_element() >= 0 && reachable.contains(&near.as_uvec2()) |
|
499 |
+ }) |
|
500 |
+ }) |
|
501 |
+ }); |
|
502 |
+ let owned: HashSet<Home> = homes.by_family.values().copied().collect(); |
|
503 |
+ let door = BuildKind::Structure(StructureKind::Door); |
|
504 |
+ let entries: HashSet<UVec2> = blueprints |
|
505 |
+ .0 |
| 361 |
506 |
.iter() |
| 362 |
|
- .filter(|(_, s)| s.kind == StructureKind::Statue) |
| 363 |
|
- .map(|(pos, _)| pos.0) |
|
507 |
+ .filter(|(_, kind)| **kind == door) |
|
508 |
+ .map(|(tile, _)| *tile) |
|
509 |
+ .chain(doors.iter().copied()) |
| 364 |
510 |
.collect(); |
| 365 |
|
- for home in homes.by_family.values() { |
|
511 |
+ for home in homes.all() { |
|
512 |
+ if let Some((tile, kind)) = door_repair(&map, &entries, &reachable, home, colony_home) { |
|
513 |
+ blueprints.0.insert(tile, kind); |
|
514 |
+ continue; |
|
515 |
+ } |
| 366 |
516 |
if blueprints.0.keys().any(|t| home.contains(*t)) { |
| 367 |
517 |
continue; |
| 368 |
518 |
} |
| @@ -371,17 +521,24 @@ fn plan_homes( |
| 371 |
521 |
for tile in roofs { |
| 372 |
522 |
blueprints.0.insert(tile, BuildKind::Roof); |
| 373 |
523 |
} |
| 374 |
|
- if built && !statues.iter().any(|&t| home.contains(t)) { |
|
524 |
+ if built && owned.contains(home) && !statues.iter().any(|&t| home.contains(t)) { |
| 375 |
525 |
let statue = BuildKind::Structure(StructureKind::Statue); |
| 376 |
526 |
blueprints.0.insert(home.statue_tile(), statue); |
| 377 |
527 |
} |
| 378 |
528 |
} |
| 379 |
|
- let Some(family) = families |
|
529 |
+ let homeless_adults = families |
| 380 |
530 |
.iter() |
| 381 |
|
- .filter(|(f, partnered)| *partnered && !homes.by_family.contains_key(&f.0)) |
| 382 |
|
- .map(|(f, _)| f.0) |
| 383 |
|
- .min() |
| 384 |
|
- else { |
|
531 |
+ .filter(|(family, _, child)| !child && homes.of(family.copied()).is_none()) |
|
532 |
+ .count(); |
|
533 |
+ let communal: Vec<UVec2> = beds |
|
534 |
+ .iter() |
|
535 |
+ .filter(|b| !homes.is_home(**b) && blueprints.get(**b).is_none() && reachable.contains(b)) |
|
536 |
+ .copied() |
|
537 |
+ .collect(); |
|
538 |
+ for bed in surplus_beds(&communal, homeless_adults, colony_home) { |
|
539 |
+ blueprints.0.insert(bed, BuildKind::Deconstruct); |
|
540 |
+ } |
|
541 |
+ let Some(family) = homeless_family(&homes) else { |
| 385 |
542 |
return; |
| 386 |
543 |
}; |
| 387 |
544 |
let occupied: HashSet<UVec2> = structures |
| @@ -389,7 +546,6 @@ fn plan_homes( |
| 389 |
546 |
.map(|(pos, _)| pos.0) |
| 390 |
547 |
.chain(items.iter().map(|pos| pos.0)) |
| 391 |
548 |
.collect(); |
| 392 |
|
- let colony_home = zones.home(map.size / 2); |
| 393 |
549 |
let ctx = HomeContext { |
| 394 |
550 |
map: &map, |
| 395 |
551 |
zones: &zones, |
| @@ -680,4 +836,158 @@ mod tests { |
| 680 |
836 |
Some(BuildKind::Structure(StructureKind::Statue)) |
| 681 |
837 |
); |
| 682 |
838 |
} |
|
839 |
+ |
|
840 |
+ fn walled(map: &mut WorldMap, home: Home, door: UVec2) -> HashSet<UVec2> { |
|
841 |
+ for tile in home.ring().filter(|t| *t != door) { |
|
842 |
+ map.set_wall(tile, true); |
|
843 |
+ } |
|
844 |
+ HashSet::from([door]) |
|
845 |
+ } |
|
846 |
+ |
|
847 |
+ fn reachable(map: &WorldMap, from: UVec2) -> HashSet<UVec2> { |
|
848 |
+ let mut seen = HashSet::new(); |
|
849 |
+ nearest_tile(map, from, usize::MAX, |p| { |
|
850 |
+ seen.insert(p); |
|
851 |
+ false |
|
852 |
+ }); |
|
853 |
+ seen |
|
854 |
+ } |
|
855 |
+ |
|
856 |
+ |
|
857 |
+ |
|
858 |
+ #[test] |
|
859 |
+ fn new_homes_never_touch_existing_walls() { |
|
860 |
+ let mut map = WorldMap::filled(UVec2::splat(40), Terrain::Grass); |
|
861 |
+ for y in 0..40 { |
|
862 |
+ map.set_wall(UVec2::new(27, y), true); |
|
863 |
+ } |
|
864 |
+ let mut zones = Zones::default(); |
|
865 |
+ zones.add_tiles(&map, [UVec2::new(20, 20)]); |
|
866 |
+ let home = plan_on(&map, &zones, &HashSet::new()); |
|
867 |
+ for y in home.lo.y - 1..=home.hi.y + 1 { |
|
868 |
+ for x in home.lo.x - 1..=home.hi.x + 1 { |
|
869 |
+ assert!(!map.is_wall(IVec2::new(x as i32, y as i32)), "{home:?}"); |
|
870 |
+ } |
|
871 |
+ } |
|
872 |
+ } |
|
873 |
+ |
|
874 |
+ |
|
875 |
+ |
|
876 |
+ #[test] |
|
877 |
+ fn homes_with_a_blocked_door_get_a_new_one() { |
|
878 |
+ let mut map = WorldMap::filled(UVec2::splat(20), Terrain::Grass); |
|
879 |
+ let home = Home::at(UVec2::new(5, 5)); |
|
880 |
+ let blocked = UVec2::new(7, 5); |
|
881 |
+ let doors = walled(&mut map, home, blocked); |
|
882 |
+ map.set_wall(UVec2::new(7, 4), true); |
|
883 |
+ let colony = UVec2::new(7, 0); |
|
884 |
+ let open = reachable(&map, colony); |
|
885 |
+ let (site, kind) = door_repair(&map, &doors, &open, &home, colony).unwrap(); |
|
886 |
+ assert_eq!(kind, BuildKind::Deconstruct); |
|
887 |
+ assert!(home.door_sites().any(|t| t == site) && site != blocked); |
|
888 |
+ map.set_wall(site, false); |
|
889 |
+ let open = reachable(&map, colony); |
|
890 |
+ assert_eq!( |
|
891 |
+ door_repair(&map, &doors, &open, &home, colony), |
|
892 |
+ Some((site, BuildKind::Structure(StructureKind::Door))) |
|
893 |
+ ); |
|
894 |
+ let fine = walled( |
|
895 |
+ &mut WorldMap::filled(UVec2::splat(20), Terrain::Grass), |
|
896 |
+ home, |
|
897 |
+ blocked, |
|
898 |
+ ); |
|
899 |
+ let map = WorldMap::filled(UVec2::splat(20), Terrain::Grass); |
|
900 |
+ assert_eq!( |
|
901 |
+ door_repair(&map, &fine, &reachable(&map, colony), &home, colony), |
|
902 |
+ None |
|
903 |
+ ); |
|
904 |
+ } |
|
905 |
+ |
|
906 |
+ |
|
907 |
+ |
|
908 |
+ #[test] |
|
909 |
+ fn unreachable_blueprints_are_dropped() { |
|
910 |
+ let mut map = WorldMap::filled(UVec2::splat(20), Terrain::Grass); |
|
911 |
+ let pocket = Home::at(UVec2::new(12, 12)); |
|
912 |
+ for tile in pocket.ring() { |
|
913 |
+ map.set_wall(tile, true); |
|
914 |
+ } |
|
915 |
+ let mut app = App::new(); |
|
916 |
+ app.insert_resource(map) |
|
917 |
+ .init_resource::<SimTick>() |
|
918 |
+ .init_resource::<Zones>() |
|
919 |
+ .init_resource::<Homes>() |
|
920 |
+ .insert_resource(Blueprints(HashMap::from([ |
|
921 |
+ (UVec2::new(14, 14), BuildKind::Roof), |
|
922 |
+ (UVec2::new(2, 2), BuildKind::Roof), |
|
923 |
+ (UVec2::new(12, 12), BuildKind::Deconstruct), |
|
924 |
+ ]))) |
|
925 |
+ .add_systems(Update, plan_homes); |
|
926 |
+ app.update(); |
|
927 |
+ let blueprints = app.world().resource::<Blueprints>(); |
|
928 |
+ assert_eq!(blueprints.get(UVec2::new(14, 14)), None); |
|
929 |
+ assert!(blueprints.get(UVec2::new(2, 2)).is_some()); |
|
930 |
+ assert!(blueprints.get(UVec2::new(12, 12)).is_some()); |
|
931 |
+ } |
|
932 |
+ |
|
933 |
+ |
|
934 |
+ |
|
935 |
+ #[test] |
|
936 |
+ fn vacant_homes_are_reused_and_spare_beds_removed() { |
|
937 |
+ let mut map = WorldMap::filled(UVec2::splat(30), Terrain::Grass); |
|
938 |
+ let old = Home::at(UVec2::new(5, 5)); |
|
939 |
+ let door = UVec2::new(7, 5); |
|
940 |
+ walled(&mut map, old, door); |
|
941 |
+ for tile in old.interior() { |
|
942 |
+ map.set_roof(tile, true); |
|
943 |
+ } |
|
944 |
+ let mut app = App::new(); |
|
945 |
+ app.insert_resource(map) |
|
946 |
+ .init_resource::<SimTick>() |
|
947 |
+ .init_resource::<Zones>() |
|
948 |
+ .init_resource::<Blueprints>() |
|
949 |
+ .init_resource::<Homes>() |
|
950 |
+ .add_systems(Update, plan_homes); |
|
951 |
+ let world = app.world_mut(); |
|
952 |
+ let structure = |kind| Structure { kind }; |
|
953 |
+ world.spawn((structure(StructureKind::Door), TilePos(door))); |
|
954 |
+ for bed in old.bed_tiles() { |
|
955 |
+ world.spawn((structure(StructureKind::Bed), TilePos(bed))); |
|
956 |
+ } |
|
957 |
+ for x in 20..24 { |
|
958 |
+ world.spawn((structure(StructureKind::Bed), TilePos(UVec2::new(x, 20)))); |
|
959 |
+ } |
|
960 |
+ let a = world |
|
961 |
+ .spawn((Colonist { name: "Ada".into() }, Family(2))) |
|
962 |
+ .id(); |
|
963 |
+ let b = world |
|
964 |
+ .spawn(( |
|
965 |
+ Colonist { |
|
966 |
+ name: "Bram".into(), |
|
967 |
+ }, |
|
968 |
+ Family(2), |
|
969 |
+ Partner(a), |
|
970 |
+ )) |
|
971 |
+ .id(); |
|
972 |
+ world.entity_mut(a).insert(Partner(b)); |
|
973 |
+ world.spawn(Colonist { |
|
974 |
+ name: "Cleo".into(), |
|
975 |
+ }); |
|
976 |
+ app.update(); |
|
977 |
+ let homes = app.world().resource::<Homes>(); |
|
978 |
+ assert_eq!(homes.by_family.get(&2), Some(&old)); |
|
979 |
+ let blueprints = app.world().resource::<Blueprints>(); |
|
980 |
+ let removed = blueprints |
|
981 |
+ .0 |
|
982 |
+ .values() |
|
983 |
+ .filter(|k| **k == BuildKind::Deconstruct) |
|
984 |
+ .count(); |
|
985 |
+ assert_eq!(removed, 3); |
|
986 |
+ assert!( |
|
987 |
+ !blueprints |
|
988 |
+ .0 |
|
989 |
+ .values() |
|
990 |
+ .any(|k| *k == BuildKind::Structure(StructureKind::Wall)) |
|
991 |
+ ); |
|
992 |
+ } |
| 683 |
993 |
} |