Skip to main content

wowlab_engine_combat/handler/can_cast/
reject.rs

1//! Typed cast-rejection reasons.
2
3use wowlab_types::sim::SimTime;
4
5/// Why a spell cannot be cast this instant.
6#[derive(Clone, Copy, Debug)]
7pub(crate) enum CastReject {
8    NoTarget,
9    UnknownSpell,
10    Disabled,
11    PrimaryResource {
12        have: f64,
13        cost: f64,
14    },
15    SecondaryResource {
16        have: f64,
17        cost: f64,
18    },
19    Health {
20        have: f64,
21        cost: f64,
22    },
23    NoCharges,
24    Cooldown {
25        ready_at: f64,
26        now: f64,
27    },
28    CooldownCategory {
29        category: wowlab_types::data::CooldownCategoryId,
30        ready_at: SimTime,
31        now: SimTime,
32    },
33    ChargeCategory {
34        category: wowlab_types::data::CooldownCategoryId,
35        ready_at: SimTime,
36        now: SimTime,
37    },
38    StartRecoveryCategory {
39        category: wowlab_types::data::CooldownCategoryId,
40        ready_at: SimTime,
41        now: SimTime,
42    },
43    ExecuteRange {
44        target_health_pct: f64,
45        threshold_pct: f64,
46    },
47    GatingAuraUnknown {
48        aura_id: u32,
49    },
50    GatingAuraInactive {
51        aura_id: u32,
52        min_stacks: i32,
53    },
54    RequiresStealth,
55    RequiresBehindTarget,
56    RequiresUnshifted {
57        form: i64,
58    },
59    ShapeshiftRequired {
60        form: i64,
61        required_mask: u64,
62    },
63    ShapeshiftExcluded {
64        form: i64,
65    },
66    RequiresMainHand,
67    RequiresOffHand,
68    EquippedItemRequirement,
69    Moving,
70    SchoolLocked,
71    CrowdControlled,
72    AuraState,
73    OutOfRange,
74}
75
76impl std::fmt::Display for CastReject {
77    // #t(fn: rust_cyclomatic_complexity) flat 1:1 reject-reason formatting arms
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match *self {
80            Self::NoTarget => write!(f, "no target"),
81            Self::UnknownSpell => write!(f, "spell not in resolved set"),
82            Self::Disabled => write!(f, "disabled (masked or talent-gated off)"),
83            Self::PrimaryResource { have, cost } => {
84                write!(f, "primary resource {have:.0}/{cost:.0}")
85            }
86            Self::SecondaryResource { have, cost } => {
87                write!(f, "secondary resource {have:.0}/{cost:.0}")
88            }
89            Self::Health { have, cost } => write!(f, "health {have:.0}/{cost:.0}"),
90            Self::NoCharges => write!(f, "no charges available"),
91            Self::Cooldown { ready_at, now } => {
92                write!(f, "on cooldown ({:.2}s remaining)", ready_at - now)
93            }
94            Self::CooldownCategory {
95                category,
96                ready_at,
97                now,
98            } => write!(
99                f,
100                "cooldown category {} locked ({:.2}s remaining)",
101                category.raw(),
102                ready_at.saturating_sub(now).as_secs_f64()
103            ),
104            Self::ChargeCategory {
105                category,
106                ready_at,
107                now,
108            } => write!(
109                f,
110                "charge category {} locked ({:.2}s remaining)",
111                category.raw(),
112                ready_at.saturating_sub(now).as_secs_f64()
113            ),
114            Self::StartRecoveryCategory {
115                category,
116                ready_at,
117                now,
118            } => write!(
119                f,
120                "start-recovery category {} locked ({:.2}s remaining)",
121                category.raw(),
122                ready_at.saturating_sub(now).as_secs_f64()
123            ),
124            Self::ExecuteRange {
125                target_health_pct,
126                threshold_pct,
127            } => write!(
128                f,
129                "target at {target_health_pct:.0}%, above execute threshold {threshold_pct:.0}%"
130            ),
131            Self::GatingAuraUnknown { aura_id } => {
132                write!(f, "gating aura {aura_id} not resolved")
133            }
134            Self::GatingAuraInactive {
135                aura_id,
136                min_stacks,
137            } => write!(
138                f,
139                "gating aura {aura_id} inactive (need {min_stacks} stacks)"
140            ),
141            Self::RequiresStealth => write!(f, "requires stealth (stealthed flag = 0)"),
142            Self::RequiresBehindTarget => write!(f, "requires position behind target"),
143            Self::RequiresUnshifted { form } => {
144                write!(f, "requires no shapeshift (current form = {form})")
145            }
146            Self::ShapeshiftRequired {
147                form,
148                required_mask,
149            } => write!(
150                f,
151                "requires shapeshift form mask {required_mask:#x} (current form = {form})"
152            ),
153            Self::ShapeshiftExcluded { form } => {
154                write!(f, "excluded in shapeshift form {form}")
155            }
156            Self::RequiresMainHand => write!(f, "requires a main-hand weapon"),
157            Self::RequiresOffHand => write!(f, "requires an off-hand weapon"),
158            Self::EquippedItemRequirement => {
159                write!(f, "equipped weapon does not satisfy spell requirements")
160            }
161            Self::Moving => write!(f, "cannot cast while moving"),
162            Self::SchoolLocked => write!(f, "spell school is locked"),
163            Self::CrowdControlled => write!(f, "actor is crowd controlled"),
164            Self::AuraState => write!(f, "aura-state requirement is not satisfied"),
165            Self::OutOfRange => write!(f, "target out of range"),
166        }
167    }
168}