use bevy::ecs::entity::Entity;
use bevy::ecs::message::Message;
use bevy::ecs::resource::Resource;
use bevy::reflect::Reflect;
use crate::beliefs::{Knowledge, Known, belief_after_hearing};
use crate::defs::DeedId;
use crate::relationships::Ties;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RumorId(pub u32);
#[derive(Debug, Clone, PartialEq, Reflect)]
pub struct Rumor {
pub deed: DeedId,
pub subject: Entity,
pub patient: Option<Entity>,
pub origin: Entity,
pub day: f32,
pub truth: bool,
pub valence: f32,
pub known_by: u32,
pub alive: bool,
}
#[derive(Message, Debug, Clone, PartialEq, Eq)]
pub struct PlantRumor {
pub origin: Entity,
pub subject: Entity,
pub patient: Option<Entity>,
pub deed: String,
pub truth: bool,
}
#[derive(Message, Debug, Clone, Copy, PartialEq, Eq)]
pub struct RumorLearned {
pub person: Entity,
pub rumor: RumorId,
pub from: Option<Entity>,
}
#[derive(Resource, Debug, Clone, Default, PartialEq, Reflect)]
pub struct RumorBook {
pub rumors: Vec<Rumor>,
}
impl RumorBook {
pub fn add(&mut self, rumor: Rumor) -> RumorId {
let id = RumorId(self.rumors.len() as u32);
self.rumors.push(rumor);
id
}
pub fn get(&self, id: RumorId) -> Option<&Rumor> {
self.rumors.get(id.0 as usize)
}
pub fn get_mut(&mut self, id: RumorId) -> Option<&mut Rumor> {
self.rumors.get_mut(id.0 as usize)
}
pub fn reach(&self, id: RumorId, living_persons: usize) -> f32 {
match (self.get(id), living_persons) {
(Some(r), n) if n > 0 => r.known_by as f32 / n as f32,
_ => 0.0,
}
}
pub fn retire_about(&mut self, subject: Entity) -> Vec<RumorId> {
let mut retired = Vec::new();
for (i, rumor) in self.rumors.iter_mut().enumerate() {
if rumor.alive && rumor.subject == subject {
rumor.alive = false;
retired.push(RumorId(i as u32));
}
}
retired
}
}
#[expect(
clippy::too_many_arguments,
reason = "the rumor's fields are passed one by one so that callers cannot mix up the entities"
)]
pub(crate) fn plant_rumor(
book: &mut RumorBook,
origin_knowledge: &mut Knowledge,
origin: Entity,
subject: Entity,
patient: Option<Entity>,
deed: DeedId,
harm: f32,
now_days: f32,
truth: bool,
knowledge_capacity: usize,
) -> RumorId {
let id = book.add(Rumor {
deed,
subject,
patient,
origin,
day: now_days,
truth,
valence: harm,
known_by: 1,
alive: true,
});
origin_knowledge.insert(
Known {
rumor: id,
belief: 1.0,
spreader: true,
heard_day: now_days,
},
knowledge_capacity,
now_days,
);
id
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Gossip {
Nothing,
Stifled(RumorId),
Learned {
rumor: RumorId,
subject: Entity,
},
}
#[expect(
clippy::too_many_arguments,
reason = "one call per accepted gossip; the teller's and listener's state stay separate arguments"
)]
pub(crate) fn gossip(
book: &mut RumorBook,
teller_book: &Knowledge,
listener_knowledge: &mut Knowledge,
listener_ties: &mut Ties,
listener_emotions: &mut crate::mind::Emotions,
listener_memories: &mut crate::mind::Memories,
listener_entity: Entity,
listener_trust_in_teller: f32,
listener_close_targets: &[Entity],
settings: &crate::core::SocietySettings,
now_days: f32,
) -> Gossip {
let pick = teller_book.best_telling(now_days, |rumor| {
let entry = book.get(rumor)?;
if !entry.alive || entry.subject == listener_entity {
return None;
}
Some(entry.valence)
});
let Some(rumor) = pick else {
return Gossip::Nothing;
};
let Some(entry) = book.get(rumor) else {
return Gossip::Nothing;
};
let (valence, subject, teller_belief) = match teller_book.get(rumor) {
Some(known) => (entry.valence, entry.subject, known.belief),
None => return Gossip::Nothing,
};
if listener_knowledge.knows(rumor) {
return Gossip::Stifled(rumor);
}
let belief = belief_after_hearing(teller_belief, listener_trust_in_teller);
listener_knowledge.insert(
Known {
rumor,
belief,
spreader: true,
heard_day: now_days,
},
settings.knowledge_capacity,
now_days,
);
if let Some(entry) = book.get_mut(rumor) {
entry.known_by += 1;
}
let tie = listener_ties.entry(subject);
tie.trust = (tie.trust + settings.tuning.gossip_trust_gain * valence * belief).clamp(0.0, 1.0);
tie.add_affinity(
settings.tuning.gossip_affinity_gain * valence * belief,
&settings.tuning,
);
let importance = (valence.abs() * belief).clamp(0.0, 1.0);
listener_memories.record(
crate::mind::Memory {
day: now_days,
kind: crate::mind::MemoryKind::HeardRumor(rumor),
other: Some(subject),
valence: (valence * belief).clamp(-1.0, 1.0),
importance,
},
f64::from(now_days),
settings.memory_capacity,
);
if valence < 0.0 && listener_close_targets.contains(&subject) {
listener_emotions.appraise(
crate::mind::EmotionKind::Distress,
importance,
Some(subject),
);
}
Gossip::Learned { rumor, subject }
}