Skip to main content

edda_types/combat/
cost.rs

1//! What a combat command costs a character's sheet: a spell slot, or uses
2//! of a feature. The combat service spends it on the sheet before running
3//! the command, and gives it back when the command is refused or undone,
4//! so every frontend gets that for free.
5
6use uuid::Uuid;
7
8use super::{CombatCommand, CombatEvent, SpellSlot};
9use crate::CharacterAction;
10
11/// What a command spends on a character's sheet: a spell slot, or uses of
12/// a feature.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum SheetCost {
15    Slot(SpellSlot),
16    Feature { name: String, amount: u16 },
17}
18
19impl SheetCost {
20    /// The action that spends it.
21    pub fn spend(&self) -> CharacterAction {
22        match self {
23            SheetCost::Slot(SpellSlot::Level(level)) => {
24                CharacterAction::ExpendSpellSlot { level: *level }
25            }
26            SheetCost::Slot(SpellSlot::Pact(_)) => CharacterAction::ExpendPactSlot,
27            SheetCost::Feature { name, amount } => CharacterAction::UseResource {
28                name: name.clone(),
29                amount: *amount,
30            },
31        }
32    }
33
34    /// The action that gives it back.
35    pub fn give_back(&self) -> CharacterAction {
36        match self {
37            SheetCost::Slot(SpellSlot::Level(level)) => {
38                CharacterAction::RestoreSpellSlot { level: *level }
39            }
40            SheetCost::Slot(SpellSlot::Pact(_)) => CharacterAction::RestorePactSlot,
41            SheetCost::Feature { name, amount } => CharacterAction::RestoreResource {
42                name: name.clone(),
43                amount: *amount,
44            },
45        }
46    }
47}
48
49impl CombatCommand {
50    /// What running this costs the sheet of which combatant, if anything.
51    pub fn sheet_cost(&self) -> Option<(Uuid, SheetCost)> {
52        match self {
53            CombatCommand::CastSpell {
54                caster,
55                slot: Some(slot),
56                ..
57            } => Some((*caster, SheetCost::Slot(*slot))),
58            CombatCommand::UseFeature {
59                combatant,
60                feature,
61                amount,
62            } => Some((
63                *combatant,
64                SheetCost::Feature {
65                    name: feature.clone(),
66                    amount: *amount,
67                },
68            )),
69            _ => None,
70        }
71    }
72}
73
74impl CombatEvent {
75    /// What the command that logged this event cost a sheet: what undoing
76    /// it gives back.
77    pub fn sheet_cost(&self) -> Option<(Uuid, SheetCost)> {
78        match self {
79            CombatEvent::SpellCast {
80                caster,
81                slot: Some(slot),
82                ..
83            } => Some((*caster, SheetCost::Slot(*slot))),
84            CombatEvent::FeatureUsed {
85                combatant,
86                feature,
87                amount,
88            } => Some((
89                *combatant,
90                SheetCost::Feature {
91                    name: feature.clone(),
92                    amount: *amount,
93                },
94            )),
95            _ => None,
96        }
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn casts_and_feature_uses_cost_what_they_say_and_give_it_back() {
106        let caster = Uuid::new_v4();
107        let bless = CombatCommand::CastSpell {
108            caster,
109            spell: "Bless".into(),
110            slot: Some(SpellSlot::Level(2)),
111            targets: vec![],
112            concentration: None,
113        };
114        let (who, cost) = bless.sheet_cost().unwrap();
115        assert_eq!(who, caster);
116        assert_eq!(cost.spend(), CharacterAction::ExpendSpellSlot { level: 2 });
117        assert_eq!(
118            cost.give_back(),
119            CharacterAction::RestoreSpellSlot { level: 2 }
120        );
121
122        let pact = SheetCost::Slot(SpellSlot::Pact(3));
123        assert_eq!(pact.spend(), CharacterAction::ExpendPactSlot);
124        assert_eq!(pact.give_back(), CharacterAction::RestorePactSlot);
125
126        let cantrip = CombatCommand::CastSpell {
127            caster,
128            spell: "Fire Bolt".into(),
129            slot: None,
130            targets: vec![],
131            concentration: None,
132        };
133        assert_eq!(cantrip.sheet_cost(), None, "cantrips are free");
134
135        let used = CombatEvent::FeatureUsed {
136            combatant: caster,
137            feature: "Rage".into(),
138            amount: 1,
139        };
140        assert_eq!(
141            used.sheet_cost().unwrap().1.give_back(),
142            CharacterAction::RestoreResource {
143                name: "Rage".into(),
144                amount: 1
145            }
146        );
147    }
148}