wowlab_engine_content/hooks/
mod.rs1pub(crate) fn gated<T>(enabled: bool, derive: impl FnOnce() -> T) -> T
2where
3 T: Default,
4{
5 if enabled { derive() } else { T::default() }
6}
7
8pub(crate) fn try_gated<T, E>(enabled: bool, derive: impl FnOnce() -> Result<T, E>) -> Result<T, E>
9where
10 T: Default,
11{
12 if enabled { derive() } else { Ok(T::default()) }
13}
14
15pub(crate) fn gated_or<T>(enabled: bool, fallback: T, derive: impl FnOnce() -> T) -> T {
16 if enabled { derive() } else { fallback }
17}
18
19macro_rules! define_spec_handler {
20 (
21 impl $handler:ident($this:ident, $inner:ident) {
22 $(
23 $(#[$custom_meta:meta])*
24 fn $custom_name:ident $custom_args:tt $(-> $custom_return:ty)? $custom_body:block
25 )*
26 }
27 ) => {
28 impl wowlab_engine_ports::SpecHandler for $handler {
29 $(
30 $(#[$custom_meta])*
31 fn $custom_name $custom_args $(-> $custom_return)? $custom_body
32 )*
33 wowlab_engine_ports::delegate_spec_handler!(
34 $inner;
35 skip [$($custom_name),*]
36 );
37 }
38 };
39 (
40 impl $handler:ident($this:ident, $inner:ident) {
41 $(
42 $(#[$custom_meta:meta])*
43 fn $custom_name:ident $custom_args:tt $(-> $custom_return:ty)? $custom_body:block
44 )*
45 }
46 delegate {
47 $(
48 fn $delegated_name:ident $delegated_args:tt $(-> $delegated_return:ty)?;
49 )*
50 }
51 ) => {
52 impl wowlab_engine_ports::SpecHandler for $handler {
53 $(
54 $(#[$custom_meta])*
55 fn $custom_name $custom_args $(-> $custom_return)? $custom_body
56 )*
57 delegate::delegate! {
58 to $this.$inner {
59 $(
60 fn $delegated_name $delegated_args $(-> $delegated_return)?;
61 )*
62 }
63 }
64 wowlab_engine_ports::delegate_spec_handler!(
65 $inner;
66 skip [$($custom_name,)* $($delegated_name),*]
67 );
68 }
69 };
70}
71
72pub(crate) use define_spec_handler;
73
74macro_rules! define_spec_module {
75 (modules [$($module:ident),+ $(,)?]; exports { $($export:item)* }) => {
76 $(mod $module;)+
77 $($export)*
78 };
79}
80
81pub(crate) use define_spec_module;
82
83macro_rules! define_spec_config {
84 (
85 $(#[$config_meta:meta])*
86 $vis:vis struct $config:ident {
87 $(
88 $(#[$field_meta:meta])*
89 $field:ident : $field_ty:ty {
90 provenance: $provenance:literal,
91 gate: $gate:expr,
92 source: $source:expr,
93 transform: $transform:expr
94 $(, fallback: $fallback:expr)?
95 $(,)?
96 }
97 ),* $(,)?
98 }
99 $(accessor $accessor_vis:vis fn $accessor:ident($ctx:ident);)?
100 register $register_vis:vis fn $register:ident(
101 $built:ident,
102 $params:ident
103 ) -> $return_ty:ty;
104 prepare { $($prepare:tt)* }
105 auras {
106 $(
107 {
108 provenance: $aura_provenance:literal,
109 gate: $aura_gate:expr,
110 aura: $aura:expr,
111 effect: $effect:expr $(,)?
112 }
113 ),* $(,)?
114 }
115 $(wiring {
116 $(
117 {
118 provenance: $wiring_provenance:literal,
119 gate: $wiring_gate:expr,
120 apply: $wiring_apply:block $(,)?
121 }
122 ),* $(,)?
123 })?
124 finish |$cfg:ident| $finish:block
125 ) => {
126 $(#[$config_meta])*
127 #[derive(Copy, Clone, Debug)]
128 $vis struct $config {
129 $(
130 #[doc = $provenance]
131 $(#[$field_meta])*
132 pub $field: $field_ty,
133 )*
134 }
135
136 impl Default for $config {
137 fn default() -> Self {
138 Self {
139 $(
140 $field: $crate::hooks::define_spec_config!(
141 @fallback $field_ty $(, $fallback)?
142 ),
143 )*
144 }
145 }
146 }
147
148 $(
149 $accessor_vis fn $accessor($ctx: &wowlab_engine_combat::HookCtx<'_>) -> $config {
150 $ctx.state()
151 .spec_config::<$config>()
152 .copied()
153 .unwrap_or_default()
154 }
155 )?
156
157 $register_vis fn $register(
163 $built: &mut wowlab_engine_combat::BuiltCombatSystem,
164 $params: &wowlab_engine_ports::HandlerParams<'_>,
165 ) -> $return_ty {
166 $($prepare)*
167 let $cfg = $config {
168 $(
169 $field: if $gate {
170 let value = $source;
171 ($transform)(value)
172 } else {
173 $crate::hooks::define_spec_config!(
174 @fallback $field_ty $(, $fallback)?
175 )
176 },
177 )*
178 };
179 $(
180 if $aura_gate {
181 $built.set_aura_effect($aura, $effect);
182 }
183 )*
184 $(
185 $(
186 if $wiring_gate {
187 $wiring_apply
188 }
189 )*
190 )?
191 $finish
192 }
193 };
194 (@fallback $field_ty:ty, $fallback:expr) => {
195 $fallback
196 };
197 (@fallback $field_ty:ty) => {
198 <$field_ty>::default()
199 };
200}
201
202pub(crate) use define_spec_config;
203
204#[cfg(test)]
205mod config_macro_tests {
206 #![expect(
207 dead_code,
208 reason = "the macro fixture deliberately exercises fields that individual tests do not read"
209 )]
210
211 use googletest::prelude::*;
212
213 define_spec_config! {
214 struct MacroConfig {
215 fraction: f64 {
216 provenance: "Synthetic effect e1 used to exercise source transforms.",
217 gate: enabled,
218 source: numerator,
219 transform: |value| value / denominator,
220 },
221 }
222 accessor fn macro_config(ctx);
223 register fn register_macro_config(built, params) -> ();
224 prepare {
225 let enabled = true;
226 let numerator = 25.0;
227 let denominator = 100.0;
228 }
229 auras {}
230 finish |cfg| {
231 let _ = params;
232 built.state.set_spec_config(cfg);
233 }
234 }
235
236 #[gtest]
237 fn config_table_emits_transformed_fields() -> Result<()> {
238 let value = if true {
239 let value = 25.0;
240 let transform = |value| value / 100.0;
241
242 transform(value)
243 } else {
244 f64::default()
245 };
246 let config = MacroConfig { fraction: value };
247
248 verify_that!(config.fraction, eq(0.25))
249 }
250}
251
252pub(crate) mod affliction_warlock;
253pub(crate) mod arcane_mage;
254
255pub(crate) mod arms_warrior;
256pub(crate) mod assassination_rogue;
257pub(crate) mod balance_druid;
258pub(crate) mod beast_mastery_hunter;
259pub(crate) mod demonology_warlock;
260pub(crate) mod destruction_warlock;
261pub(crate) mod devastation_evoker;
262pub(crate) mod devourer_demon_hunter;
263pub(crate) mod elemental_shaman;
264pub(crate) mod enhancement_shaman;
265pub(crate) mod feral_druid;
266pub(crate) mod fire_mage;
267pub(crate) mod frost_death_knight;
268pub(crate) mod frost_mage;
269pub(crate) mod fury_warrior;
270pub(crate) mod havoc_demon_hunter;
271pub(crate) mod items;
272pub(crate) mod marksmanship_hunter;
273pub(crate) mod outlaw_rogue;
274pub(crate) mod retribution_paladin;
275pub(crate) mod shadow_priest;
276pub(crate) mod shared;
277pub(crate) mod subtlety_rogue;
278pub(crate) mod survival_hunter;
279pub(crate) mod unholy_death_knight;
280pub(crate) mod vengeance_demon_hunter;
281pub(crate) mod windwalker_monk;