use std::sync::{Arc, Mutex};
use bevy::ecs::query::QueryData;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use bevy_emerge_rng::{DetRng, seeded};
use rand_chacha::ChaCha8Rng;
use crate::behavior::Doing;
use crate::beliefs::{Knowledge, Opinions};
use crate::core::{PersonId, PersonIndex, SocietyClock, SocietyRng, SocietySettings};
use crate::defs::{ActivityId, FactionId, IDLE_ACTIVITY, NeedId, SocietyDefs, TopicId};
use crate::groups::{Memberships, Status};
use crate::mind::{Emotions, Memories, Mood, Needs, Personality};
use crate::relationships::{TieLabel, Ties};
use crate::rumor::RumorId;
#[derive(Clone, Default)]
pub(crate) struct Names {
pub(crate) needs: Vec<String>,
pub(crate) signals: Vec<String>,
pub(crate) topics: Vec<String>,
pub(crate) factions: Vec<String>,
pub(crate) activities: Vec<String>,
pub(crate) goods: Vec<String>,
}
impl Names {
pub(crate) fn from_defs(defs: &SocietyDefs) -> Self {
Self {
needs: defs.needs.iter().map(|s| s.name.clone()).collect(),
signals: defs.signals.clone(),
topics: defs.topics.iter().map(|s| s.name.clone()).collect(),
factions: defs.factions.iter().map(|s| s.name.clone()).collect(),
activities: defs.activities.iter().map(|s| s.name.clone()).collect(),
goods: defs.goods.clone(),
}
}
pub(crate) fn need(&self, name: &str) -> Result<NeedId, String> {
position(&self.needs, "need", name).map(|i| NeedId(i as u16))
}
pub(crate) fn signal(&self, name: &str) -> Result<crate::defs::SignalId, String> {
position(&self.signals, "signal", name).map(|i| crate::defs::SignalId(i as u16))
}
pub(crate) fn topic(&self, name: &str) -> Result<TopicId, String> {
position(&self.topics, "topic", name).map(|i| TopicId(i as u16))
}
pub(crate) fn faction(&self, name: &str) -> Result<FactionId, String> {
position(&self.factions, "faction", name).map(|i| FactionId(i as u16))
}
pub(crate) fn good(&self, name: &str) -> Result<crate::GoodId, String> {
position(&self.goods, "good", name).map(|i| crate::GoodId(i as u16))
}
pub(crate) fn activity_name(&self, id: ActivityId) -> &str {
if id == IDLE_ACTIVITY {
return "";
}
self.activities
.get(id.0 as usize)
.map_or("", String::as_str)
}
}
fn position(names: &[String], kind: &str, name: &str) -> Result<usize, String> {
names
.iter()
.position(|n| n == name)
.ok_or_else(|| format!("unknown {kind} '{name}'"))
}
#[derive(Clone, Copy)]
pub(crate) struct TieView {
pub(crate) entity: Entity,
pub(crate) affinity: f32,
pub(crate) trust: f32,
pub(crate) respect: f32,
pub(crate) familiarity: f32,
pub(crate) label: TieLabel,
}
#[derive(Clone)]
pub(crate) struct PersonView {
pub(crate) entity: Entity,
pub(crate) id: PersonId,
pub(crate) name: Option<String>,
pub(crate) personality: Personality,
pub(crate) mood: Mood,
pub(crate) emotions: Emotions,
pub(crate) needs: Needs,
pub(crate) signals: crate::mind::Signals,
pub(crate) opinions: Opinions,
pub(crate) ties: Vec<TieView>,
pub(crate) knowledge: Knowledge,
pub(crate) memberships: Memberships,
pub(crate) standing: f32,
pub(crate) activity: Option<ActivityId>,
pub(crate) memories: Memories,
pub(crate) stock: crate::Stock,
pub(crate) position: Option<Vec2>,
}
impl PersonView {
pub(crate) fn tie_to_id(&self, target: Entity) -> Option<&TieView> {
self.ties
.binary_search_by_key(&target.to_bits(), |t| t.entity.to_bits())
.ok()
.map(|i| &self.ties[i])
}
}
pub(crate) struct SocietySnapshot {
now_days: f64,
hour: f32,
day: i64,
weekday: i64,
names: Names,
prices: Vec<f32>,
forgiveness: f32,
persons: Vec<PersonView>,
rng: Mutex<ChaCha8Rng>,
}
impl SocietySnapshot {
pub(crate) fn hour(&self) -> f32 {
self.hour
}
pub(crate) fn day(&self) -> i64 {
self.day
}
pub(crate) fn weekday(&self) -> i64 {
self.weekday
}
pub(crate) fn person(&self, id: i64) -> Result<&PersonView, String> {
let not_found = || format!("no person with id {id}");
let key = u32::try_from(id).map_err(|_| not_found())?;
self.persons
.binary_search_by_key(&key, |p| p.id.0)
.map(|i| &self.persons[i])
.map_err(|_| not_found())
}
pub(crate) fn unit(&self) -> f32 {
let mut rng = match self.rng.lock() {
Ok(rng) => rng,
Err(poisoned) => poisoned.into_inner(),
};
rng.unit() as f32
}
pub(crate) fn affinity(&self, a: i64, b: i64) -> Result<f32, String> {
self.tie_value(a, b, |tie| tie.affinity, 0.0)
}
pub(crate) fn trust(&self, a: i64, b: i64) -> Result<f32, String> {
self.tie_value(a, b, |tie| tie.trust, 0.5)
}
pub(crate) fn respect(&self, a: i64, b: i64) -> Result<f32, String> {
self.tie_value(a, b, |tie| tie.respect, 0.0)
}
pub(crate) fn familiarity(&self, a: i64, b: i64) -> Result<f32, String> {
self.tie_value(a, b, |tie| tie.familiarity, 0.0)
}
pub(crate) fn grudge(&self, a: i64, b: i64) -> Result<f32, String> {
let person = self.person(a)?;
let other = self.entity(b)?;
let trust = person.tie_to_id(other).map_or(0.5, |tie| tie.trust);
let pace = crate::model::recall::forgiving_pace(trust, self.forgiveness);
Ok(person.memories.grudge_at_pace(other, self.now_days, pace))
}
pub(crate) fn label(&self, a: i64, b: i64) -> Result<String, String> {
let person = self.person(a)?;
let other = self.entity(b)?;
Ok(person
.tie_to_id(other)
.map_or(crate::relationships::TieLabel::Stranger, |tie| tie.label)
.script_name()
.to_owned())
}
pub(crate) fn mood(&self, a: i64) -> Result<[f32; 3], String> {
Ok(self.person(a)?.mood.as_array())
}
pub(crate) fn emotion(&self, a: i64, kind: &str) -> Result<f32, String> {
let kind = crate::mind::EmotionKind::from_name(kind)
.ok_or_else(|| format!("unknown emotion '{kind}'"))?;
Ok(self.person(a)?.emotions.intensity_of(kind))
}
pub(crate) fn need(&self, a: i64, need: &str) -> Result<f32, String> {
let id = self.names.need(need)?;
Ok(self
.person(a)?
.needs
.0
.get(usize::from(id.0))
.copied()
.unwrap_or(0.0))
}
pub(crate) fn stock(&self, a: i64, good: &str) -> Result<f32, String> {
let id = self.names.good(good)?;
Ok(self.person(a)?.stock.get(id))
}
pub(crate) fn price(&self, good: &str) -> Result<f32, String> {
let id = self.names.good(good)?;
Ok(self.prices.get(usize::from(id.0)).copied().unwrap_or(1.0))
}
pub(crate) fn signal(&self, a: i64, signal: &str) -> Result<f32, String> {
let id = self.names.signal(signal)?;
Ok(self.person(a)?.signals.get(id))
}
pub(crate) fn opinion(&self, a: i64, topic: &str) -> Result<f32, String> {
let id = self.names.topic(topic)?;
Ok(self.person(a)?.opinions.value(usize::from(id.0)))
}
pub(crate) fn trait_value(&self, a: i64, name: &str) -> Result<f32, String> {
let which =
crate::mind::Trait::from_name(name).ok_or_else(|| format!("unknown trait '{name}'"))?;
Ok(self.person(a)?.personality.value(which))
}
pub(crate) fn tellable(&self, a: i64) -> Result<bool, String> {
Ok(self
.person(a)?
.knowledge
.0
.iter()
.any(|known| known.spreader))
}
pub(crate) fn knows(&self, a: i64, rumor: i64) -> Result<bool, String> {
let id = u32::try_from(rumor)
.map(RumorId)
.map_err(|_| format!("no rumor with id {rumor}"))?;
Ok(self.person(a)?.knowledge.knows(id))
}
pub(crate) fn member(&self, a: i64, faction: &str) -> Result<bool, String> {
let id = self.names.faction(faction)?;
Ok(self.person(a)?.memberships.contains(id))
}
pub(crate) fn faction_size(&self, faction: &str) -> Result<i64, String> {
let id = self.names.faction(faction)?;
let count = self
.persons
.iter()
.filter(|p| p.memberships.contains(id))
.count();
Ok(count as i64)
}
pub(crate) fn standing(&self, a: i64) -> Result<f32, String> {
Ok(self.person(a)?.standing)
}
pub(crate) fn name(&self, a: i64) -> Result<String, String> {
Ok(self
.person(a)?
.name
.clone()
.unwrap_or_else(|| "someone".to_owned()))
}
pub(crate) fn doing(&self, a: i64) -> Result<String, String> {
Ok(self
.person(a)?
.activity
.map_or_else(String::new, |id| self.names.activity_name(id).to_owned()))
}
pub(crate) fn position(&self, a: i64) -> Result<[f32; 2], String> {
self.person(a)?
.position
.map(|p| p.to_array())
.ok_or_else(|| format!("person {a} has no position"))
}
pub(crate) fn distance(&self, a: i64, b: i64) -> Result<f32, String> {
let [ax, ay] = self.position(a)?;
let [bx, by] = self.position(b)?;
Ok(Vec2::new(ax, ay).distance(Vec2::new(bx, by)))
}
pub(crate) fn person_ids(&self) -> Vec<i64> {
self.persons.iter().map(|p| i64::from(p.id.0)).collect()
}
fn person_entity(&self, id: i64) -> Result<Entity, String> {
self.person(id).map(|p| p.entity)
}
fn entity(&self, id: i64) -> Result<Entity, String> {
self.person_entity(id)
}
fn tie_value(
&self,
a: i64,
b: i64,
read: impl Fn(&TieView) -> f32,
absent: f32,
) -> Result<f32, String> {
let person = self.person(a)?;
let other = self.entity(b)?;
Ok(person.tie_to_id(other).map_or(absent, &read))
}
}
#[derive(SystemParam)]
pub(crate) struct SnapshotSources<'w> {
pub(crate) defs: Option<Res<'w, SocietyDefs>>,
pub(crate) prices: Option<Res<'w, crate::Prices>>,
}
#[derive(QueryData)]
#[query_data(mutable)]
pub(crate) struct SnapshotSource<'w> {
personality: &'w Personality,
mood: &'w Mood,
emotions: &'w Emotions,
needs: &'w Needs,
signals: &'w crate::mind::Signals,
ties: &'w Ties,
opinions: &'w Opinions,
knowledge: &'w Knowledge,
memberships: &'w Memberships,
status: &'w Status,
memories: &'w Memories,
doing: Option<&'w Doing>,
name: Option<&'w Name>,
stock: Option<&'w crate::Stock>,
transform: Option<&'w Transform>,
}
#[derive(Resource, Default)]
pub(crate) struct SnapshotCell(pub(crate) Option<Arc<SocietySnapshot>>);
pub(crate) fn build_snapshot(
settings: Res<SocietySettings>,
clock: Res<SocietyClock>,
sources: SnapshotSources,
index: Res<PersonIndex>,
mut rng: ResMut<SocietyRng>,
mut cell: ResMut<SnapshotCell>,
people: Query<SnapshotSource>,
) {
let names = sources
.defs
.as_deref()
.map_or_else(Names::default, Names::from_defs);
let tuning = &settings.tuning;
let now = clock.now_days;
let mut by_bits: Vec<(u64, PersonId)> = index
.sorted
.iter()
.map(|&(id, entity)| (entity.to_bits(), id))
.collect();
by_bits.sort_unstable();
let mut persons = Vec::with_capacity(index.sorted.len());
for &(id, entity) in &index.sorted {
let Ok(view) = people.get(entity) else {
continue;
};
let (
personality,
mood,
emotions,
needs,
signals,
ties,
opinions,
knowledge,
memberships,
status,
memories,
doing,
name,
stock,
transform,
) = (
view.personality,
view.mood,
view.emotions,
view.needs,
view.signals,
view.ties,
view.opinions,
view.knowledge,
view.memberships,
view.status,
view.memories,
view.doing,
view.name,
view.stock,
view.transform,
);
let mut views: Vec<TieView> = ties
.0
.iter()
.map(|tie| TieView {
entity: tie.target,
affinity: tie.affinity,
trust: tie.trust,
respect: tie.respect,
familiarity: tie.familiarity(now as f32, tuning),
label: tie.label,
})
.collect();
views.sort_by_key(|t| t.entity.to_bits());
persons.push(PersonView {
entity,
id,
name: name.map(|n| n.as_str().to_string()),
personality: *personality,
mood: *mood,
emotions: emotions.clone(),
needs: needs.clone(),
signals: signals.clone(),
opinions: opinions.clone(),
ties: views,
knowledge: knowledge.clone(),
memberships: memberships.clone(),
standing: status.standing,
activity: doing.map(|d| d.activity),
memories: memories.clone(),
stock: stock.cloned().unwrap_or_default(),
position: transform.map(|t| settings.plane.project(t.translation)),
});
}
let seed = rng.0.raw_u64();
cell.0 = Some(Arc::new(SocietySnapshot {
now_days: now,
hour: clock.hour(),
day: i64::from(clock.day()),
weekday: i64::from(clock.weekday(settings.start_weekday)),
names,
prices: sources
.prices
.as_deref()
.map(|p| p.0.clone())
.unwrap_or_default(),
forgiveness: tuning.forgiveness,
persons,
rng: Mutex::new(seeded(seed)),
}));
}