use bevy::ecs::entity::Entity;
use bevy::ecs::message::Message;
use crate::beliefs::{Knowledge, Opinions};
use crate::core::{AssessmentNorm, SocietySettings, Tuning};
use crate::defs::{DeedId, TopicId};
use crate::mind::{EmotionKind, Emotions, Memories, Memory, MemoryKind, Personality};
use crate::relationships::Ties;
use crate::rumor::{RumorBook, RumorId, plant_rumor};
pub const DISTRUSTED: f32 = 0.3;
#[derive(Message, Debug, Clone, PartialEq, Eq)]
pub struct NormViolated {
pub witness: Entity,
pub actor: Entity,
pub deed: String,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct WitnessReaction {
pub(crate) approval: f32,
pub(crate) violated: bool,
}
pub(crate) fn trust_gain(tuning: &Tuning, conscientiousness: f32) -> f32 {
tuning.witness_gain * (0.5 + conscientiousness)
}
#[expect(
clippy::too_many_arguments,
reason = "one call per witnessed deed; the read/write split stays visible at the call site"
)]
pub(crate) fn witness_reaction(
ties: &mut Ties,
emotions: &mut Emotions,
memories: &mut Memories,
opinions: &Opinions,
personality: &Personality,
deed: DeedId,
deed_topic: TopicId,
harm: f32,
actor: Entity,
patient: Option<Entity>,
trust_in_patient: Option<f32>,
settings: &SocietySettings,
witnesses: usize,
now_days: f64,
) -> WitnessReaction {
let norm = opinions.value(usize::from(deed_topic.0)).clamp(-1.0, 1.0);
let approval = norm * harm.signum();
let seen = (witnesses.max(1) as f32).powf(settings.tuning.witness_dilution);
let gain = trust_gain(&settings.tuning, personality.conscientiousness) / seen;
let justified = patient.is_some()
&& settings.assessment == AssessmentNorm::Standing
&& harm < 0.0
&& trust_in_patient.is_some_and(|trust| trust < DISTRUSTED);
if !justified {
let tie = ties.entry(actor);
tie.trust = (tie.trust + gain * harm).clamp(0.0, 1.0);
}
let tie = ties.entry(actor);
tie.add_affinity(0.15 * approval, &settings.tuning);
tie.respect = (tie.respect + 0.1 * approval).clamp(-1.0, 1.0);
if approval > 0.0 {
emotions.appraise(EmotionKind::Admiration, approval, Some(actor));
} else if approval < 0.0 {
emotions.appraise(EmotionKind::Reproach, -approval, Some(actor));
}
memories.record(
Memory {
day: now_days as f32,
kind: MemoryKind::Witnessed { deed, actor },
other: Some(actor),
valence: (approval * harm.abs()).clamp(-1.0, 1.0),
importance: harm.abs().clamp(0.0, 1.0),
},
now_days,
settings.memory_capacity,
);
WitnessReaction {
approval,
violated: approval < -0.5,
}
}
pub(crate) fn patient_reaction(
ties: &mut Ties,
emotions: &mut Emotions,
harm: f32,
gain: f32,
actor: Entity,
) {
if harm > 0.0 {
emotions.appraise(EmotionKind::Gratitude, harm, Some(actor));
} else if harm < 0.0 {
emotions.appraise(EmotionKind::Anger, -harm, Some(actor));
}
let tie = ties.entry(actor);
tie.trust = (tie.trust + gain * harm).clamp(0.0, 1.0);
}
pub(crate) fn actor_reaction(
emotions: &mut Emotions,
opinions: &Opinions,
deed_topic: TopicId,
patient: Entity,
) {
let own = opinions.value(usize::from(deed_topic.0));
if own > 0.2 {
emotions.appraise(EmotionKind::Pride, own, Some(patient));
} else if own < -0.2 {
emotions.appraise(EmotionKind::Shame, -own, Some(patient));
}
}
#[expect(
clippy::too_many_arguments,
reason = "the caller owns this state anyway; bundling it into a struct would hide the read/write split"
)]
pub(crate) fn spread_witnessed_rumor(
book: &mut RumorBook,
witnesses: &mut [(Entity, Knowledge)],
subject: Entity,
patient: Option<Entity>,
deed: DeedId,
harm: f32,
now_days: f32,
truth: bool,
knowledge_capacity: usize,
) -> Option<RumorId> {
let (origin, rest) = witnesses.split_first_mut()?;
let id = plant_rumor(
book,
&mut origin.1,
origin.0,
subject,
patient,
deed,
harm,
now_days,
truth,
knowledge_capacity,
);
for (_, knowledge) in rest.iter_mut() {
knowledge.insert(
crate::beliefs::Known {
rumor: id,
belief: 1.0,
spreader: true,
heard_day: now_days,
},
knowledge_capacity,
now_days,
);
}
if let Some(rumor) = book.get_mut(id) {
rumor.known_by += rest.len() as u32;
}
Some(id)
}