Skip to main content

dreamwell_engine/physics/
promotion.rs

1use serde::{Deserialize, Serialize};
2
3/// Promotion target — what a particle becomes when promoted to gameplay entity.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5pub enum PromotionTarget {
6    Pickup,
7    Hazard,
8    Interactable,
9    WorldScar,
10    PersistentDebris,
11    ResourceNode,
12    AIStimulus,
13}
14
15/// Promotion authority — who decides promotion.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub enum PromotionAuthority {
18    ClientSuggestsServerConfirms,
19    ServerOnly,
20    EventDeterministic,
21}
22
23/// Promotion rule — condition for a particle to be promoted.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct PromotionRule {
26    pub when: String,
27    pub min_age: f32,
28    pub requires_tags: Vec<String>,
29    pub set_tags: Vec<String>,
30}
31
32/// Promotion policy for an emitter.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct PromotionPolicy {
35    pub enabled: bool,
36    pub target: PromotionTarget,
37    pub rules: Vec<PromotionRule>,
38    pub authority: PromotionAuthority,
39}
40
41impl Default for PromotionPolicy {
42    fn default() -> Self {
43        Self {
44            enabled: false,
45            target: PromotionTarget::Pickup,
46            rules: Vec::new(),
47            authority: PromotionAuthority::ClientSuggestsServerConfirms,
48        }
49    }
50}
51
52impl PromotionTarget {
53    pub const ALL: &[PromotionTarget] = &[
54        Self::Pickup,
55        Self::Hazard,
56        Self::Interactable,
57        Self::WorldScar,
58        Self::PersistentDebris,
59        Self::ResourceNode,
60        Self::AIStimulus,
61    ];
62
63    pub fn name(&self) -> &'static str {
64        match self {
65            Self::Pickup => "Pickup",
66            Self::Hazard => "Hazard",
67            Self::Interactable => "Interactable",
68            Self::WorldScar => "World Scar",
69            Self::PersistentDebris => "Persistent Debris",
70            Self::ResourceNode => "Resource Node",
71            Self::AIStimulus => "AI Stimulus",
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn promotion_target_all() {
82        assert_eq!(PromotionTarget::ALL.len(), 7);
83    }
84
85    #[test]
86    fn promotion_policy_default() {
87        let p = PromotionPolicy::default();
88        assert!(!p.enabled);
89    }
90
91    #[test]
92    fn promotion_policy_serde() {
93        let p = PromotionPolicy {
94            enabled: true,
95            target: PromotionTarget::Pickup,
96            authority: PromotionAuthority::ClientSuggestsServerConfirms,
97            rules: vec![PromotionRule {
98                when: "resting".into(),
99                min_age: 1.5,
100                requires_tags: vec!["isDebris".into(), "isStone".into()],
101                set_tags: vec!["isPickupable".into(), "signal.promoted".into()],
102            }],
103        };
104        let json = serde_json::to_string(&p).unwrap();
105        let restored: PromotionPolicy = serde_json::from_str(&json).unwrap();
106        assert_eq!(restored.rules.len(), 1);
107    }
108}