1use serde::Deserialize;
4
5#[derive(Debug)]
7pub enum ScalarRef {
9 EffectRef {
10 spell_id: u32,
11 effect: u8,
12 field: ScalarField,
13 multiplier: Option<f64>,
14 offset: Option<f64>,
15 round_decimals: Option<u8>,
16 },
17 SpellRef {
18 spell_id: u32,
19 field: SpellScalarField,
20 multiplier: Option<f64>,
21 offset: Option<f64>,
22 round_decimals: Option<u8>,
23 },
24 Literal(f64),
25}
26
27#[derive(Deserialize)]
28#[serde(untagged)]
29enum ScalarRefRepr {
30 EffectRef {
31 spell_id: u32,
32 effect: u8,
33 field: ScalarField,
34 #[serde(default)]
35 multiplier: Option<f64>,
36 #[serde(default)]
37 offset: Option<f64>,
38 #[serde(default, deserialize_with = "deserialize_round_decimals")]
39 round_decimals: Option<u8>,
40 },
41 SpellRef {
42 spell_id: u32,
43 field: SpellScalarField,
44 #[serde(default)]
45 multiplier: Option<f64>,
46 #[serde(default)]
47 offset: Option<f64>,
48 #[serde(default, deserialize_with = "deserialize_round_decimals")]
49 round_decimals: Option<u8>,
50 },
51 Float(f64),
52 Integer(i64),
53}
54
55impl ScalarRef {
56 #[must_use]
57 pub const fn effect_spell_id(&self) -> Option<u32> {
58 match self {
59 Self::EffectRef { spell_id, .. } | Self::SpellRef { spell_id, .. } => Some(*spell_id),
60 Self::Literal(_) => None,
61 }
62 }
63}
64
65impl<'de> Deserialize<'de> for ScalarRef {
66 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67 where
68 D: serde::Deserializer<'de>,
69 {
70 Ok(match ScalarRefRepr::deserialize(deserializer)? {
71 ScalarRefRepr::EffectRef {
72 spell_id,
73 effect,
74 field,
75 multiplier,
76 offset,
77 round_decimals,
78 } => Self::EffectRef {
79 spell_id,
80 effect,
81 field,
82 multiplier,
83 offset,
84 round_decimals,
85 },
86 ScalarRefRepr::SpellRef {
87 spell_id,
88 field,
89 multiplier,
90 offset,
91 round_decimals,
92 } => Self::SpellRef {
93 spell_id,
94 field,
95 multiplier,
96 offset,
97 round_decimals,
98 },
99 ScalarRefRepr::Float(value) => Self::Literal(value),
100 ScalarRefRepr::Integer(value) => {
101 Self::Literal(wowlab_types::numeric::i64_to_f64(value))
102 }
103 })
104 }
105}
106
107#[derive(Clone, Copy, Debug, Deserialize)]
109#[serde(rename_all = "snake_case")]
110#[non_exhaustive]
111pub enum SpellScalarField {
112 DurationMs,
113 MaxStacks,
114 Cooldown,
115 Cost,
116 CastTimeMs,
117 Charges,
118 ChargeCd,
119 GcdMs,
120 ProcCategoryRecoveryMs,
122}
123
124#[derive(Clone, Copy, Debug, Deserialize)]
126#[serde(rename_all = "snake_case")]
127#[non_exhaustive]
128pub enum ScalarField {
129 BasePoints,
130 ApCoef,
131 SpCoef,
132 Amplitude,
134 Period,
136 Coefficient,
137}
138
139const MAX_ROUND_DECIMALS: u8 = 15;
140
141fn deserialize_round_decimals<'de, D>(deserializer: D) -> Result<Option<u8>, D::Error>
142where
143 D: serde::Deserializer<'de>,
144{
145 use serde::de::Error as _;
146
147 let value = Option::<u8>::deserialize(deserializer)?;
148
149 if value.is_some_and(|decimals| decimals > MAX_ROUND_DECIMALS) {
150 return Err(D::Error::custom(format!(
151 "round_decimals must be between 0 and {MAX_ROUND_DECIMALS}"
152 )));
153 }
154
155 Ok(value)
156}