Skip to main content

wowlab_engine_ports/
test_support.rs

1use wowlab_engine_gamedata::ResolvedGameData;
2use wowlab_types::{
3    constants::{CURRENT_EXPANSION_ID, MAX_PLAYER_LEVEL},
4    sim::EncounterDefinition,
5};
6
7use crate::{
8    EncounterConstructionError, ResolvedEncounter, ResolvedEnemyCombatStats,
9    ResolvedEnemyEncounter, ResolvedEnemyIdentityMetadata,
10};
11
12const FIXTURE_MAX_HEALTH: f64 = 1_000_000.0;
13const FIXTURE_CREATURE_ARMOR: f64 = 377.0;
14const FIXTURE_ARMOR_CONSTANT: f64 = f32::from_bits(0x4475_22c1) as f64;
15const FIXTURE_AUTO_ATTACK_DPS: f64 = f32::from_bits(0x44b6_eb33) as f64;
16const FIXTURE_SPELL_DAMAGE: f64 = 65_851.0;
17
18/// Builds a deterministic combat-ready encounter for test and smoke-test consumers.
19///
20/// # Errors
21///
22/// Returns an error if the built-in encounter cannot satisfy resolved encounter invariants.
23pub fn introspection_fixture(
24    duration_s: f64,
25) -> Result<ResolvedEncounter, EncounterConstructionError> {
26    let definition =
27        EncounterDefinition::patchwerk(duration_s, MAX_PLAYER_LEVEL, CURRENT_EXPANSION_ID)?;
28    let enemy = definition
29        .enemies
30        .first()
31        .ok_or_else(|| EncounterConstructionError::resolved_enemy_count_mismatch(1, 0))?;
32    let group = definition
33        .groups
34        .get(enemy.group_id.0 as usize)
35        .ok_or_else(|| EncounterConstructionError::resolved_enemy_metadata_mismatch(enemy.id))?;
36    let identity = ResolvedEnemyIdentityMetadata::new(
37        enemy.id,
38        "Training Dummy".to_string(),
39        None,
40        None,
41        None,
42    )?;
43    let stats = ResolvedEnemyCombatStats::new(
44        enemy.id,
45        FIXTURE_MAX_HEALTH,
46        FIXTURE_CREATURE_ARMOR,
47        FIXTURE_ARMOR_CONSTANT,
48        Some(
49            enemy
50                .auto_attack_dps_override
51                .unwrap_or(FIXTURE_AUTO_ATTACK_DPS),
52        ),
53        Some(enemy.spell_damage_override.unwrap_or(FIXTURE_SPELL_DAMAGE)),
54    )?;
55    let resolved = ResolvedEnemyEncounter::new(enemy, &group.tags, enemy.level, identity, stats)?;
56
57    ResolvedEncounter::new(definition, vec![resolved])
58}
59
60/// Builds empty spell data paired with an encounter's exact primary-enemy armor tuple.
61#[must_use]
62pub fn introspection_game_data(encounter: &ResolvedEncounter) -> ResolvedGameData {
63    let primary = encounter.primary_enemy();
64
65    ResolvedGameData::from_environment(
66        u32::from(primary.level()),
67        primary.creature_armor(),
68        primary.armor_constant(),
69    )
70}
71
72/// Supplies standard no-op [`SpecHandler`](crate::SpecHandler) methods for test doubles.
73///
74/// List required methods that should use their neutral test implementation.
75/// Write required overrides and optional trait methods as ordinary items.
76///
77/// ```
78/// use wowlab_engine_ports::{SimContext, SpecAction, test_spec_handler};
79///
80/// struct IdleHandler;
81///
82/// test_spec_handler! {
83///     impl IdleHandler {
84///         defaults [
85///             on_player_ready,
86///             on_cast_complete,
87///             on_sim_start,
88///             total_damage,
89///             cast_time_ms,
90///         ]
91///     }
92/// }
93///
94/// let _ = (IdleHandler, None::<SpecAction>, None::<SimContext<'static>>);
95/// ```
96#[macro_export]
97macro_rules! test_spec_handler {
98    (
99        impl $handler:ident {
100            defaults [$($default:ident),* $(,)?]
101            $($custom:item)*
102        }
103    ) => {
104        impl $crate::SpecHandler for $handler {
105            $(
106                $crate::__test_spec_handler_default!($default);
107            )*
108            $($custom)*
109        }
110    };
111}
112
113#[doc(hidden)]
114#[macro_export]
115macro_rules! __test_spec_handler_default {
116    (on_player_ready) => {
117        fn on_player_ready(&mut self, _ctx: &mut $crate::SimContext) -> Option<$crate::SpecAction> {
118            None
119        }
120    };
121    (on_cast_complete) => {
122        fn on_cast_complete(&mut self, _event: $crate::Event, _ctx: &mut $crate::SimContext) {}
123    };
124    (on_sim_start) => {
125        fn on_sim_start(&mut self, _ctx: &mut $crate::SimContext) {}
126    };
127    (total_damage) => {
128        fn total_damage(&self) -> f64 {
129            0.0
130        }
131    };
132    (cast_time_ms) => {
133        fn cast_time_ms(&self, _spell_id: $crate::__TestSpellIdx, _empower_rank: u8) -> u32 {
134            0
135        }
136    };
137    ($unknown:ident) => {
138        compile_error!(concat!(
139            "test_spec_handler! has no neutral implementation for `",
140            stringify!($unknown),
141            "`"
142        ));
143    };
144}
145
146#[cfg(test)]
147mod tests;