Skip to main content

wowlab_engine_content/hooks/
mod.rs

1pub(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        /// Registers the derived specialization configuration.
158        ///
159        /// # Errors
160        ///
161        /// Returns an engine construction error when required game data or registration is invalid.
162        $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;