use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use bevy::ecs::entity::Entity;
use bevy::ecs::query::{Has, QueryData, With};
use bevy::ecs::world::World;
use bevy::log::warn;
use bevy::math::Vec2;
use rand_chacha::rand_core::SeedableRng;
use serde::{Deserialize, Serialize};
use crate::behavior::{Destination, Doing, DoingTarget};
use crate::beliefs::{Knowledge, Opinions};
use crate::core::{
Dormant, NextPersonId, Person, PersonId, PersonIndex, PersonKey, PlaceKey, SocietyClock,
SocietyRng, SocietyTick,
};
use crate::defs::{ActivityId, DeedId, InteractionId, SocietyDefs};
use crate::economy::Prices;
use crate::groups::{FactionStats, Memberships, Status};
use crate::mind::Signals;
use crate::mind::{
Chronotype, EmotionKind, Emotions, Memories, MemoryKind, Mood, Needs, Personality,
};
use crate::relationships::{Tie, Ties};
use crate::rules::{LastDays, NeedDisarmed, RumorReported, TriggerRemap, TriggerState};
use crate::rumor::{Rumor, RumorBook, RumorId};
pub const VERSION: u32 = 5;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct NamesSave {
pub needs: Vec<String>,
pub topics: Vec<String>,
pub deeds: Vec<String>,
pub factions: Vec<String>,
pub activities: Vec<String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
pub struct ClockSave {
pub tick: u64,
pub now_days: f64,
pub dt_days: f32,
pub accumulator: f32,
pub due: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RngSave {
pub seed: [u8; 32],
pub stream: u64,
pub word_pos: u128,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct EmotionSave {
pub kind: EmotionKind,
pub intensity: f32,
pub toward: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct TieSave {
pub target: u64,
pub affinity: f32,
pub trust: f32,
pub respect: f32,
pub familiarity_peak: f32,
pub last_contact_day: f32,
pub label: crate::relationships::TieLabel,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MemoryKindSave {
Interaction {
id: InteractionId,
accepted: bool,
initiated: bool,
},
Witnessed {
deed: DeedId,
actor: Option<u64>,
},
Suffered {
deed: DeedId,
actor: Option<u64>,
},
HeardRumor(RumorId),
Talked,
Custom(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MemorySave {
pub day: f32,
pub kind: MemoryKindSave,
pub other: Option<u64>,
pub valence: f32,
pub importance: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum DoingTargetSave {
Itself,
Point([f32; 2]),
Place(u64),
Person(u64),
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct DoingSave {
pub activity: ActivityId,
pub target: DoingTargetSave,
pub started_day_bits: u64,
pub arrived_day_bits: Option<u64>,
}
impl DoingSave {
pub fn started_day(&self) -> f64 {
f64::from_bits(self.started_day_bits)
}
pub fn arrived_day(&self) -> Option<f64> {
self.arrived_day_bits.map(f64::from_bits)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PersonSave {
pub key: u64,
pub person_id: u32,
pub personality: Personality,
pub mood: Mood,
pub emotions: Vec<EmotionSave>,
pub needs: Needs,
pub ties: Vec<TieSave>,
pub opinions: Opinions,
pub knowledge: Knowledge,
pub memories: Vec<MemorySave>,
pub memberships: Memberships,
pub signals: Signals,
pub status: Status,
pub dormant: bool,
pub deceased: bool,
pub doing: Option<DoingSave>,
pub destination: Option<[f32; 2]>,
#[cfg(feature = "steering")]
pub velocity: Option<[f32; 2]>,
#[serde(default)]
pub stock: Vec<f32>,
#[serde(default)]
pub away_since_bits: Option<u64>,
#[serde(default)]
pub recent_harm: Vec<(u64, u64)>,
#[serde(default)]
pub rebuffed: Vec<(u64, u64)>,
#[serde(default)]
pub failed_places: Vec<(u64, u64)>,
#[serde(default)]
pub travel_progress: Option<(u32, u64)>,
#[serde(default)]
pub chronotype: Chronotype,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RumorSave {
pub deed: DeedId,
pub subject: u64,
pub patient: Option<u64>,
pub origin: u64,
pub day: f32,
pub truth: bool,
pub valence: f32,
pub known_by: u32,
pub alive: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct TriggersSave {
#[serde(default)]
pub ids: Vec<String>,
pub fired: Vec<(u32, i64)>,
pub cooling: Vec<(u32, i64, f64)>,
pub last_days: Option<f64>,
#[serde(default)]
pub need_disarmed: Vec<(u16, u32, u32)>,
pub rumor_reported: Vec<(u16, u32)>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FieldsSave {
pub crowd: Vec<Vec<f32>>,
pub factions: Vec<Vec<Vec<f32>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SocietySave {
pub version: u32,
pub names: NamesSave,
pub clock: ClockSave,
pub rng: RngSave,
pub persons: Vec<PersonSave>,
pub rumors: Vec<RumorSave>,
pub triggers: TriggersSave,
pub faction_stats: FactionStats,
#[serde(default)]
pub prices: Vec<u32>,
#[cfg(feature = "fields")]
pub fields: Option<FieldsSave>,
}
impl SocietySave {
pub fn upgrade(mut self) -> Result<Self, RestoreError> {
if self.version == 1 {
self.version = 2;
}
if self.version == 2 {
self.version = 3;
}
if self.version == 3 {
self.version = 4;
}
if self.version == 4 {
self.version = 5;
}
if self.version != VERSION {
return Err(RestoreError::Version {
found: self.version,
expected: VERSION,
});
}
Ok(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RestoreError {
Version {
found: u32,
expected: u32,
},
DefsMismatch {
kind: &'static str,
},
NotReady,
}
impl fmt::Display for RestoreError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Version { found, expected } => write!(
f,
"the save is version {found} but this build reads version {expected}"
),
Self::DefsMismatch { kind } => write!(
f,
"the save's {kind} names are not the names the definitions declare"
),
Self::NotReady => f.write_str(
"the world is not ready for a restore: it needs SocietyDefs and SocietyClock",
),
}
}
}
impl std::error::Error for RestoreError {}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RestoreReport {
pub unknown_keys: Vec<u64>,
pub fresh: Vec<Entity>,
pub dropped_activities: usize,
pub upgraded_from: Option<u32>,
}
#[derive(QueryData)]
struct PersonCapture {
entity: Entity,
id: &'static PersonId,
key: &'static PersonKey,
personality: &'static Personality,
mood: &'static Mood,
emotions: &'static Emotions,
needs: &'static Needs,
ties: &'static Ties,
opinions: &'static Opinions,
knowledge: &'static Knowledge,
memories: &'static Memories,
memberships: &'static Memberships,
status: &'static Status,
dormant: Has<Dormant>,
deceased: Has<crate::Deceased>,
away: Option<&'static crate::AwaySince>,
harmed: Option<&'static crate::behavior::RecentHarm>,
rebuffed: Option<&'static crate::behavior::Rebuffed>,
failed: Option<&'static crate::behavior::FailedPlaces>,
signals: &'static Signals,
doing: Option<&'static Doing>,
destination: Option<&'static Destination>,
#[cfg(feature = "steering")]
velocity: Option<&'static crate::steering::SocialVelocity>,
stock: Option<&'static crate::Stock>,
progress: Option<&'static crate::behavior::TravelProgress>,
chronotype: Option<&'static Chronotype>,
}
fn place_keys(world: &mut World) -> BTreeMap<Entity, u64> {
let mut state = world.query_filtered::<(Entity, &PlaceKey), ()>();
state
.iter(world)
.map(|(entity, key)| (entity, key.0))
.collect()
}
fn keyed_persons(world: &mut World) -> Vec<(u64, Entity)> {
let mut state = world.query_filtered::<(Entity, &PersonKey), With<Person>>();
let mut pairs: Vec<(u64, Entity)> = state
.iter(world)
.map(|(entity, key)| (key.0, entity))
.collect();
pairs.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.to_bits().cmp(&b.1.to_bits())));
pairs
}
pub fn capture(world: &mut World) -> SocietySave {
let defs = world
.get_resource::<SocietyDefs>()
.cloned()
.unwrap_or_default();
let clock = world
.get_resource::<SocietyClock>()
.copied()
.unwrap_or_default();
let tick = world
.get_resource::<SocietyTick>()
.copied()
.unwrap_or_default();
let rng = world.get_resource::<SocietyRng>().map_or(
RngSave {
seed: [0; 32],
stream: 0,
word_pos: 0,
},
|rng| RngSave {
seed: rng.0.get_seed(),
stream: rng.0.get_stream(),
word_pos: rng.0.get_word_pos(),
},
);
let mut person_keys: BTreeMap<Entity, u64> = BTreeMap::new();
let mut key_owner: BTreeMap<u64, Entity> = BTreeMap::new();
for (key, entity) in keyed_persons(world) {
if let Some(&owner) = key_owner.get(&key) {
warn!(
"bevy_society: two persons share the key {key}; the save addresses {owner:?} with \
it and cannot address {entity:?}"
);
continue;
}
key_owner.insert(key, entity);
person_keys.insert(entity, key);
}
let place_keys = place_keys(world);
let mut counting = world.query_filtered::<Entity, With<Person>>();
let total = counting.iter(world).count();
let mut rows: Vec<(u32, u64, Entity, PersonSave)> = Vec::new();
let mut state = world.query_filtered::<PersonCapture, With<Person>>();
for person in state.iter(world) {
let Some(&key) = person_keys.get(&person.entity) else {
continue;
};
let mut ties: Vec<TieSave> = person
.ties
.0
.iter()
.filter_map(|tie| {
let target = *person_keys.get(&tie.target)?;
Some(TieSave {
target,
affinity: tie.affinity,
trust: tie.trust,
respect: tie.respect,
familiarity_peak: tie.familiarity_peak,
last_contact_day: tie.last_contact_day,
label: tie.label,
})
})
.collect();
ties.sort_by_key(|tie| tie.target);
let emotions = person
.emotions
.0
.iter()
.map(|emotion| EmotionSave {
kind: emotion.kind,
intensity: emotion.intensity,
toward: emotion.toward.and_then(|e| person_keys.get(&e).copied()),
})
.collect();
let memories = person
.memories
.entries
.iter()
.map(|memory| MemorySave {
day: memory.day,
kind: match &memory.kind {
MemoryKind::Interaction {
id,
accepted,
initiated,
} => MemoryKindSave::Interaction {
id: *id,
accepted: *accepted,
initiated: *initiated,
},
MemoryKind::Witnessed { deed, actor } => MemoryKindSave::Witnessed {
deed: *deed,
actor: person_keys.get(actor).copied(),
},
MemoryKind::Suffered { deed, actor } => MemoryKindSave::Suffered {
deed: *deed,
actor: person_keys.get(actor).copied(),
},
MemoryKind::HeardRumor(rumor) => MemoryKindSave::HeardRumor(*rumor),
MemoryKind::Talked => MemoryKindSave::Talked,
MemoryKind::Custom(text) => MemoryKindSave::Custom(text.clone()),
},
other: memory.other.and_then(|e| person_keys.get(&e).copied()),
valence: memory.valence,
importance: memory.importance,
})
.collect();
let doing = person.doing.and_then(|doing| {
let target = match doing.target {
DoingTarget::Itself => DoingTargetSave::Itself,
DoingTarget::Point(point) => DoingTargetSave::Point([point.x, point.y]),
DoingTarget::Place(place) => DoingTargetSave::Place(*place_keys.get(&place)?),
DoingTarget::Person(other) => DoingTargetSave::Person(*person_keys.get(&other)?),
};
Some(DoingSave {
activity: doing.activity,
target,
started_day_bits: doing.started_day.to_bits(),
arrived_day_bits: doing.arrived_day.map(f64::to_bits),
})
});
let save = PersonSave {
key,
person_id: person.id.0,
personality: *person.personality,
mood: *person.mood,
emotions,
needs: person.needs.clone(),
ties,
opinions: person.opinions.clone(),
knowledge: person.knowledge.clone(),
memories,
memberships: person.memberships.clone(),
status: *person.status,
dormant: person.dormant,
deceased: person.deceased,
away_since_bits: person.away.map(|since| since.0.to_bits()),
recent_harm: person.harmed.map_or_else(Vec::new, |harmed| {
harmed
.0
.iter()
.filter_map(|(other, until)| Some((*person_keys.get(other)?, until.to_bits())))
.collect()
}),
rebuffed: person.rebuffed.map_or_else(Vec::new, |rebuffed| {
rebuffed
.0
.iter()
.filter_map(|(other, until)| Some((*person_keys.get(other)?, until.to_bits())))
.collect()
}),
failed_places: person.failed.map_or_else(Vec::new, |failed| {
failed
.0
.iter()
.filter_map(|(place, until)| Some((*place_keys.get(place)?, until.to_bits())))
.collect()
}),
travel_progress: person
.progress
.map(|p| (p.best.to_bits(), p.improved_day.to_bits())),
chronotype: person.chronotype.copied().unwrap_or_default(),
signals: person.signals.clone(),
doing,
destination: person
.destination
.map(|destination| [destination.0.x, destination.0.y]),
#[cfg(feature = "steering")]
velocity: person.velocity.map(|velocity| [velocity.0.x, velocity.0.y]),
stock: person.stock.map_or_else(Vec::new, |stock| stock.0.clone()),
};
rows.push((person.id.0, key, person.entity, save));
}
drop(state);
rows.sort_by(|a, b| {
a.0.cmp(&b.0)
.then(a.1.cmp(&b.1))
.then(a.2.to_bits().cmp(&b.2.to_bits()))
});
let skipped = total.saturating_sub(rows.len());
if skipped > 0 {
warn!(
"bevy_society: {skipped} person(s) were not saved: they have no PersonKey, or they are \
missing a component Person requires. A saved society cannot address them after a reload."
);
}
let rumors = world
.get_resource::<RumorBook>()
.map(|book| {
book.rumors
.iter()
.map(|rumor| RumorSave {
deed: rumor.deed,
subject: person_keys.get(&rumor.subject).copied().unwrap_or(u64::MAX),
patient: rumor.patient.and_then(|e| person_keys.get(&e).copied()),
origin: person_keys.get(&rumor.origin).copied().unwrap_or(u64::MAX),
day: rumor.day,
truth: rumor.truth,
valence: rumor.valence,
known_by: rumor.known_by,
alive: rumor.alive,
})
.collect()
})
.unwrap_or_default();
SocietySave {
version: VERSION,
names: names_of(&defs),
clock: ClockSave {
tick: clock.tick,
now_days: clock.now_days,
dt_days: clock.dt_days,
accumulator: tick.accumulator,
due: tick.due,
},
rng,
persons: rows.into_iter().map(|(_, _, _, save)| save).collect(),
rumors,
triggers: capture_triggers(world),
faction_stats: world
.get_resource::<FactionStats>()
.cloned()
.unwrap_or_default(),
prices: world
.get_resource::<Prices>()
.map(|prices| prices.0.iter().map(|price| price.to_bits()).collect())
.unwrap_or_default(),
#[cfg(feature = "fields")]
fields: capture_fields(world),
}
}
fn names_of(defs: &SocietyDefs) -> NamesSave {
NamesSave {
needs: defs.needs.iter().map(|spec| spec.name.clone()).collect(),
topics: defs.topics.iter().map(|spec| spec.name.clone()).collect(),
deeds: defs.deeds.iter().map(|spec| spec.name.clone()).collect(),
factions: defs.factions.iter().map(|spec| spec.name.clone()).collect(),
activities: defs
.activities
.iter()
.map(|spec| spec.name.clone())
.collect(),
}
}
fn capture_triggers(world: &World) -> TriggersSave {
let mut save = TriggersSave::default();
if let Some(defs) = world.get_resource::<SocietyDefs>() {
save.ids = defs.triggers.iter().map(|t| t.id.clone()).collect();
}
if let Some(state) = world.get_resource::<TriggerState>() {
save.fired = state
.fired_set()
.iter()
.map(|&(index, actor)| (index as u32, actor))
.collect();
save.cooling = state
.cooling_map()
.iter()
.map(|(&(index, actor), day)| (index as u32, actor, *day))
.collect();
}
if let Some(last) = world.get_resource::<LastDays>() {
save.last_days = last.0;
}
if let Some(disarmed) = world.get_resource::<NeedDisarmed>() {
save.need_disarmed = disarmed.0.iter().copied().collect();
}
if let Some(reported) = world.get_resource::<RumorReported>() {
save.rumor_reported = reported
.0
.iter()
.map(|&(deed, rumor)| (deed, rumor))
.collect();
}
save
}
#[cfg(feature = "fields")]
fn capture_fields(world: &World) -> Option<FieldsSave> {
let fields = world.get_resource::<crate::fields::SocialFields>()?;
Some(FieldsSave {
crowd: fields.crowd.channels().to_vec(),
factions: fields
.factions
.iter()
.map(|grid| grid.channels().to_vec())
.collect(),
})
}
pub fn restore(world: &mut World, save: SocietySave) -> Result<RestoreReport, RestoreError> {
let written_at = save.version;
let save = save.upgrade()?;
let Some(defs) = world.get_resource::<SocietyDefs>().cloned() else {
return Err(RestoreError::NotReady);
};
let names = names_of(&defs);
for (kind, saved, current) in [
("needs", &save.names.needs, &names.needs),
("topics", &save.names.topics, &names.topics),
("deeds", &save.names.deeds, &names.deeds),
("factions", &save.names.factions, &names.factions),
("activities", &save.names.activities, &names.activities),
] {
if saved != current {
return Err(RestoreError::DefsMismatch { kind });
}
}
if world.get_resource::<SocietyClock>().is_none() {
return Err(RestoreError::NotReady);
}
let mut person_by_key: BTreeMap<u64, Entity> = BTreeMap::new();
for (key, entity) in keyed_persons(world) {
if person_by_key.insert(key, entity).is_some() {
warn!(
"bevy_society: two persons share the key {key}; the restore uses the first and \
leaves the second fresh"
);
}
}
let place_by_key: BTreeMap<u64, Entity> = {
let mut state = world.query_filtered::<(Entity, &PlaceKey), ()>();
let mut pairs: Vec<(u64, Entity)> = state.iter(world).map(|(e, k)| (k.0, e)).collect();
pairs.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.to_bits().cmp(&b.1.to_bits())));
let mut map = BTreeMap::new();
for (key, entity) in pairs {
if map.insert(key, entity).is_some() {
warn!("bevy_society: two places share the key {key}; the restore uses the first");
}
}
map
};
let mut report = RestoreReport {
upgraded_from: (written_at != VERSION).then_some(written_at),
..RestoreReport::default()
};
let mut unknown: BTreeSet<u64> = BTreeSet::new();
for person in &save.persons {
if !person_by_key.contains_key(&person.key) {
unknown.insert(person.key);
}
}
for key in &unknown {
warn!(
"bevy_society: the save has a person keyed {key} with no person in the world; its state \
was skipped"
);
}
report.unknown_keys = unknown.into_iter().collect();
let mut used_ids: BTreeSet<u32> = BTreeSet::new();
let mut restored: BTreeSet<Entity> = BTreeSet::new();
let mut fresh_entities: Vec<Entity> = Vec::new();
for person in &save.persons {
let Some(&entity) = person_by_key.get(&person.key) else {
continue;
};
if !used_ids.insert(person.person_id) {
warn!(
"bevy_society: the save gives id {} to more than one person; the second is left fresh",
person.person_id
);
fresh_entities.push(entity);
continue;
}
restored.insert(entity);
write_person(
world,
entity,
person,
&person_by_key,
&place_by_key,
&defs,
&mut report,
);
}
let mut all_persons: Vec<Entity> = {
let mut state = world.query_filtered::<Entity, With<Person>>();
state.iter(world).collect()
};
all_persons.sort_by_key(|entity| {
let id = world.get::<PersonId>(*entity).map_or(u32::MAX, |id| id.0);
(id, entity.to_bits())
});
for entity in all_persons {
if !restored.contains(&entity) && !fresh_entities.contains(&entity) {
warn!(
"bevy_society: {entity:?} is a person the save does not know, so it keeps the state \
the host spawned it with and is given a new id"
);
fresh_entities.push(entity);
}
}
let next_id = used_ids
.last()
.map_or(0, |highest| highest.saturating_add(1));
let mut next = next_id;
for &entity in &fresh_entities {
while used_ids.contains(&next) {
next = next.saturating_add(1);
}
used_ids.insert(next);
if let Ok(mut em) = world.get_entity_mut(entity) {
em.insert(PersonId(next));
}
next = next.saturating_add(1);
}
report.fresh = fresh_entities;
let mut ordered: Vec<(PersonId, Entity)> = {
let mut state = world.query_filtered::<(Entity, &PersonId), With<Person>>();
state
.iter(world)
.map(|(entity, id)| (*id, entity))
.collect()
};
ordered.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.to_bits().cmp(&b.1.to_bits())));
if let Some(index) = world.get_resource_mut::<PersonIndex>() {
let mut index = index;
index.sorted = ordered.clone();
index.by_id.clear();
for &(id, entity) in &ordered {
index.by_id.insert(id, entity);
}
}
if let Some(mut next_person) = world.get_resource_mut::<NextPersonId>() {
next_person.0 = used_ids
.last()
.map_or(0, |highest| highest.saturating_add(1));
}
if let Some(mut book) = world.get_resource_mut::<RumorBook>() {
let mut rumors = Vec::with_capacity(save.rumors.len());
for rumor in &save.rumors {
let subject = person_by_key.get(&rumor.subject).copied();
let origin = person_by_key.get(&rumor.origin).copied();
let patient = rumor
.patient
.and_then(|key| person_by_key.get(&key).copied());
let resolved = subject.is_some()
&& origin.is_some()
&& patient.is_some() == rumor.patient.is_some();
rumors.push(Rumor {
deed: rumor.deed,
subject: subject.unwrap_or(Entity::PLACEHOLDER),
patient,
origin: origin.unwrap_or(Entity::PLACEHOLDER),
day: rumor.day,
truth: rumor.truth,
valence: rumor.valence,
known_by: rumor.known_by,
alive: rumor.alive && resolved,
});
}
book.rumors = rumors;
}
if let Some(mut state) = world.get_resource_mut::<TriggerState>() {
let current: Vec<String> = defs.triggers.iter().map(|t| t.id.clone()).collect();
let remap = (!save.triggers.ids.is_empty())
.then(|| TriggerRemap::new(&save.triggers.ids, ¤t));
let mut gone: BTreeSet<String> = BTreeSet::new();
let mut resolve = |saved: u32| -> Option<usize> {
let Some(remap) = &remap else {
return Some(saved as usize);
};
let found = remap.get(saved as usize);
if found.is_none() {
gone.insert(
save.triggers
.ids
.get(saved as usize)
.cloned()
.unwrap_or_else(|| format!("#{saved}")),
);
}
found
};
let fired: BTreeSet<(usize, i64)> = save
.triggers
.fired
.iter()
.filter_map(|&(index, actor)| resolve(index).map(|n| (n, actor)))
.collect();
let cooling: BTreeMap<(usize, i64), f64> = save
.triggers
.cooling
.iter()
.filter_map(|&(index, actor, day)| resolve(index).map(|n| ((n, actor), day)))
.collect();
if !gone.is_empty() {
warn!(
"bevy_society: the save has once/cooldown state for triggers the defs no longer \
have ({}); it is dropped",
gone.into_iter().collect::<Vec<_>>().join(", ")
);
}
state.replace(fired, cooling);
}
if let Some(mut last) = world.get_resource_mut::<LastDays>() {
last.0 = save.triggers.last_days;
}
if let Some(mut disarmed) = world.get_resource_mut::<NeedDisarmed>() {
disarmed.0 = save.triggers.need_disarmed.iter().copied().collect();
}
if let Some(mut reported) = world.get_resource_mut::<RumorReported>() {
reported.0 = save.triggers.rumor_reported.iter().copied().collect();
}
if let Some(mut clock) = world.get_resource_mut::<SocietyClock>() {
clock.tick = save.clock.tick;
clock.now_days = save.clock.now_days;
clock.dt_days = save.clock.dt_days;
}
if let Some(mut tick) = world.get_resource_mut::<SocietyTick>() {
tick.accumulator = save.clock.accumulator;
tick.due = save.clock.due;
}
if let Some(mut rng) = world.get_resource_mut::<SocietyRng>() {
let mut generator = rand_chacha::ChaCha8Rng::from_seed(save.rng.seed);
generator.set_stream(save.rng.stream);
generator.set_word_pos(save.rng.word_pos);
rng.0 = generator;
} else {
warn!("bevy_society: no SocietyRng to restore; the stream continues where it was");
}
if let Some(mut stats) = world.get_resource_mut::<FactionStats>() {
stats.0 = save.faction_stats.0.clone();
}
if !save.prices.is_empty()
&& let Some(mut prices) = world.get_resource_mut::<Prices>()
{
prices.0 = save
.prices
.iter()
.map(|&bits| f32::from_bits(bits))
.collect();
}
#[cfg(feature = "fields")]
restore_fields(world, save.fields.as_ref());
Ok(report)
}
fn write_person(
world: &mut World,
entity: Entity,
person: &PersonSave,
persons: &BTreeMap<u64, Entity>,
places: &BTreeMap<u64, Entity>,
defs: &SocietyDefs,
report: &mut RestoreReport,
) {
let mut ties: Vec<Tie> = person
.ties
.iter()
.filter_map(|tie| {
let target = *persons.get(&tie.target)?;
Some(Tie {
target,
affinity: tie.affinity,
trust: tie.trust,
respect: tie.respect,
familiarity_peak: tie.familiarity_peak,
last_contact_day: tie.last_contact_day,
label: tie.label,
})
})
.collect();
ties.sort_by_key(|tie| tie.target.to_bits());
let emotions = Emotions(
person
.emotions
.iter()
.map(|emotion| crate::mind::ActiveEmotion {
kind: emotion.kind,
intensity: emotion.intensity,
toward: emotion.toward.and_then(|key| persons.get(&key).copied()),
})
.collect(),
);
let memories = Memories {
entries: person
.memories
.iter()
.map(|memory| crate::mind::Memory {
day: memory.day,
kind: match &memory.kind {
MemoryKindSave::Interaction {
id,
accepted,
initiated,
} => MemoryKind::Interaction {
id: *id,
accepted: *accepted,
initiated: *initiated,
},
MemoryKindSave::Witnessed { deed, actor } => MemoryKind::Witnessed {
deed: *deed,
actor: actor
.and_then(|key| persons.get(&key).copied())
.unwrap_or(Entity::PLACEHOLDER),
},
MemoryKindSave::Suffered { deed, actor } => MemoryKind::Suffered {
deed: *deed,
actor: actor
.and_then(|key| persons.get(&key).copied())
.unwrap_or(Entity::PLACEHOLDER),
},
MemoryKindSave::HeardRumor(rumor) => MemoryKind::HeardRumor(*rumor),
MemoryKindSave::Talked => MemoryKind::Talked,
MemoryKindSave::Custom(text) => MemoryKind::Custom(text.clone()),
},
other: memory.other.and_then(|key| persons.get(&key).copied()),
valence: memory.valence,
importance: memory.importance,
})
.collect(),
};
let doing = person.doing.and_then(|doing| {
if usize::from(doing.activity.0) >= defs.activities.len() {
return None;
}
let target = match doing.target {
DoingTargetSave::Itself => DoingTarget::Itself,
DoingTargetSave::Point([x, y]) => DoingTarget::Point(Vec2::new(x, y)),
DoingTargetSave::Place(key) => DoingTarget::Place(*places.get(&key)?),
DoingTargetSave::Person(key) => DoingTarget::Person(*persons.get(&key)?),
};
Some(Doing {
activity: doing.activity,
target,
started_day: doing.started_day(),
arrived_day: doing.arrived_day(),
})
});
if person.doing.is_some() && doing.is_none() {
report.dropped_activities += 1;
}
let Ok(mut em) = world.get_entity_mut(entity) else {
return;
};
em.insert((
PersonId(person.person_id),
person.personality,
person.chronotype,
person.mood,
emotions,
person.needs.clone(),
Ties(ties),
person.opinions.clone(),
person.knowledge.clone(),
memories,
person.memberships.clone(),
person.status,
));
if person.stock.is_empty() {
if defs.goods.is_empty() {
em.remove::<crate::Stock>();
} else {
em.insert(crate::Stock(vec![0.0; defs.goods.len()]));
}
} else {
em.insert(crate::Stock(person.stock.clone()));
}
if person.dormant {
em.insert(Dormant);
} else {
em.remove::<Dormant>();
}
if person.deceased {
em.insert(crate::Deceased);
} else {
em.remove::<crate::Deceased>();
}
match person.away_since_bits {
Some(bits) => {
em.insert((crate::AwaySince(f64::from_bits(bits)), crate::Away));
}
None => {
em.remove::<crate::Away>();
}
}
em.insert(person.signals.clone());
if person.recent_harm.is_empty() {
em.remove::<crate::behavior::RecentHarm>();
} else {
em.insert(crate::behavior::RecentHarm(
person
.recent_harm
.iter()
.filter_map(|&(key, until)| Some((*persons.get(&key)?, f64::from_bits(until))))
.collect(),
));
}
if person.rebuffed.is_empty() {
em.remove::<crate::behavior::Rebuffed>();
} else {
em.insert(crate::behavior::Rebuffed(
person
.rebuffed
.iter()
.filter_map(|&(key, until)| Some((*persons.get(&key)?, f64::from_bits(until))))
.collect(),
));
}
if person.failed_places.is_empty() {
em.remove::<crate::behavior::FailedPlaces>();
} else {
em.insert(crate::behavior::FailedPlaces(
person
.failed_places
.iter()
.filter_map(|&(key, until)| Some((*places.get(&key)?, f64::from_bits(until))))
.collect(),
));
}
match person.travel_progress {
Some((best, improved)) => {
em.insert(crate::behavior::TravelProgress {
best: f32::from_bits(best),
improved_day: f64::from_bits(improved),
});
}
None => {
em.remove::<crate::behavior::TravelProgress>();
}
}
if let Some(doing) = doing {
em.insert(doing);
} else {
em.remove::<Doing>();
}
match person.destination {
Some([x, y]) => {
em.insert(Destination(Vec2::new(x, y)));
}
None => {
em.remove::<Destination>();
}
}
#[cfg(feature = "steering")]
match person.velocity {
Some([x, y]) => {
em.insert(crate::steering::SocialVelocity(Vec2::new(x, y)));
}
None => {
em.remove::<crate::steering::SocialVelocity>();
}
}
}
#[cfg(feature = "fields")]
fn restore_fields(world: &mut World, save: Option<&FieldsSave>) {
let Some(save) = save else {
return;
};
let Some(mut fields) = world.get_resource_mut::<crate::fields::SocialFields>() else {
warn!(
"bevy_society: the save has social fields but the world has no SocialFields; skipped"
);
return;
};
for (channel, values) in save.crowd.iter().enumerate() {
if !fields.crowd.set_channel(channel, values) {
warn!("bevy_society: saved crowd channel {channel} does not match the grid; skipped");
}
}
for (index, grid) in save.factions.iter().enumerate() {
let Some(target) = fields.factions.get_mut(index) else {
warn!(
"bevy_society: the save has faction grid {index} but the world has fewer; skipped"
);
continue;
};
for (channel, values) in grid.iter().enumerate() {
if !target.set_channel(channel, values) {
warn!(
"bevy_society: saved faction grid {index} channel {channel} does not match; \
skipped"
);
}
}
}
fields.refresh_peaks();
}