diff --git a/src/world.rs b/src/world.rs index 1f3f8d5..adc504e 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1121,9 +1121,8 @@ impl World { continue; } - let location = (self.people[first_index].x, self.people[first_index].y); self.food -= 6; - self.spawn_person_at(location); + self.spawn_person_at(self.home); if let Some(partnership) = self .partnerships .iter_mut() @@ -1181,6 +1180,9 @@ impl World { p.hunger = (p.hunger - 25.0).max(0.0); } if !p.stage.can_partner() { + if !at_home { + (p.x, p.y) = self.move_toward(pos, home); + } p.action = "growing up".to_string(); self.people[i] = p; return; @@ -1437,7 +1439,21 @@ impl World { } pub fn from_snapshot(snapshot: &str, fallback_seed: u64) -> Result { - let saved: WorldSnapshot = serde_json::from_str(snapshot).map_err(|e| e.to_string())?; + let snapshot_value: serde_json::Value = + serde_json::from_str(snapshot).map_err(|e| e.to_string())?; + let legacy_people = snapshot_value + .get("people") + .and_then(serde_json::Value::as_array) + .is_some_and(|people| { + !people.is_empty() + && people.iter().all(|person| { + person + .as_object() + .is_some_and(|person| !person.contains_key("stage")) + }) + }); + let saved: WorldSnapshot = + serde_json::from_value(snapshot_value).map_err(|e| e.to_string())?; if saved.version != 1 { return Err(format!( "unsupported world snapshot version {}", @@ -1479,11 +1495,47 @@ impl World { calendar: saved.calendar, }; world.calendar.normalize(); + if legacy_people { + world.initialize_legacy_people(); + } world.prune_partnerships(); world.sync_entities(); Ok(world) } + fn initialize_legacy_people(&mut self) { + for index in 0..self.people.len() { + let stage = if index < 3 { + LifeStage::YoungAdult + } else { + LifeStage::Adult + }; + let duration_days = self.sample_stage_duration(stage); + let prior_stages = match stage { + LifeStage::YoungAdult => { + [LifeStage::Toddler, LifeStage::Child, LifeStage::Teen].as_slice() + } + LifeStage::Adult => [ + LifeStage::Toddler, + LifeStage::Child, + LifeStage::Teen, + LifeStage::YoungAdult, + ] + .as_slice(), + _ => &[], + }; + let age_days: u64 = prior_stages + .iter() + .map(|&past_stage| self.sample_stage_duration(past_stage) as u64) + .sum(); + let person = &mut self.people[index]; + person.stage = stage; + person.stage_duration_days = duration_days; + person.stage_elapsed_ticks = 0; + person.age_ticks = age_days * self.calendar.ticks_per_day; + } + } + fn rect(t: &[&str], i: usize) -> Option<(usize, usize, usize, usize)> { let n: Vec = (i..i + 4).filter_map(|k| t.get(k)?.parse().ok()).collect(); if n.len() != 4 { @@ -1851,7 +1903,7 @@ impl Drop for World { #[cfg(test)] mod tests { - use super::{DEFAULT_SCRIPT, DEFAULT_TICKS_PER_DAY, LifeStage, World}; + use super::{DEFAULT_SCRIPT, DEFAULT_TICKS_PER_DAY, LifeStage, World, cheb}; #[test] fn new_people_progress_through_every_stage_and_die() { @@ -1907,6 +1959,8 @@ mod tests { .count(), 8 ); + world.people[0].x = 0; + world.people[0].y = 0; world.sync_entities(); assert_eq!( @@ -1930,6 +1984,39 @@ mod tests { .find(|person| person.stage == LifeStage::Toddler) .expect("fertile partner actors should eventually have a child"); assert_eq!(toddler.age_ticks, 0); + assert_eq!((toddler.x, toddler.y), world.home); + } + + #[test] + fn legacy_snapshots_restore_the_village_as_adults() { + let world = World::new(42); + let mut snapshot: serde_json::Value = + serde_json::from_str(&world.snapshot_json().unwrap()).unwrap(); + for person in snapshot["people"].as_array_mut().unwrap() { + let person = person.as_object_mut().unwrap(); + for field in [ + "age_ticks", + "stage", + "stage_elapsed_ticks", + "stage_duration_days", + "partner_id", + ] { + person.remove(field); + } + } + snapshot.as_object_mut().unwrap().remove("partnerships"); + + let restored = World::from_snapshot(&snapshot.to_string(), 42).unwrap(); + + assert_eq!(restored.people.len(), 9); + assert_eq!(restored.people[0].stage, LifeStage::YoungAdult); + assert!( + restored + .people + .iter() + .skip(3) + .all(|person| person.stage == LifeStage::Adult) + ); } #[test] @@ -1948,6 +2035,21 @@ mod tests { ); } + #[test] + fn children_return_home_instead_of_staying_away_from_food() { + let mut world = World::new(42); + world.food = 100; + let home = world.home; + let position = (home.0.saturating_add(5), home.1.saturating_add(5)); + world.spawn_person_at(position); + + world.tick_person(world.people.len() - 1); + + let child = world.people.last().unwrap(); + assert!(cheb((child.x, child.y), home) < cheb(position, home)); + assert_eq!(child.action, "growing up"); + } + #[test] fn snapshot_restores_state_and_rng() { let mut original = World::new(42);