Skip to main content

forge/
encounter_fixture.rs

1//! Canonical Phase 8 encounter fixtures selectable from Forge commands.
2
3use anyhow::{Context, Result};
4#[cfg(test)]
5use googletest::{Result as GtestResult, prelude::*};
6use wowlab_types::sim::EncounterDefinition;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, clap::ValueEnum)]
9#[value(rename_all = "snake_case")]
10#[non_exhaustive]
11pub(crate) enum EncounterFixture {
12    TwoTargetPack,
13    BossAddWave,
14    TwoPullDungeon,
15    /// Forge-only fixture with separated actors, obstacles, and scripted movement.
16    SpatialShowcase,
17}
18
19impl EncounterFixture {
20    #[must_use]
21    pub(crate) const fn slug(self) -> &'static str {
22        match self {
23            Self::TwoTargetPack => "two_target_pack",
24            Self::BossAddWave => "boss_add_wave",
25            Self::TwoPullDungeon => "two_pull_dungeon",
26            Self::SpatialShowcase => "spatial_showcase",
27        }
28    }
29
30    pub(crate) fn definition(self) -> Result<EncounterDefinition> {
31        let definition: EncounterDefinition = toml::from_str(self.source())
32            .with_context(|| format!("failed to parse encounter fixture '{}'", self.slug()))?;
33
34        definition
35            .validate()
36            .with_context(|| format!("invalid encounter fixture '{}'", self.slug()))?;
37
38        Ok(definition)
39    }
40
41    const fn source(self) -> &'static str {
42        match self {
43            Self::TwoTargetPack => {
44                include_str!("../../engine/tests/fixtures/encounters/two_target_pack.toml")
45            }
46            Self::BossAddWave => {
47                include_str!("../../engine/tests/fixtures/encounters/boss_add_wave.toml")
48            }
49            Self::TwoPullDungeon => {
50                include_str!("../../engine/tests/fixtures/encounters/two_pull_dungeon.toml")
51            }
52            Self::SpatialShowcase => {
53                include_str!("../fixtures/encounters/spatial_showcase.toml")
54            }
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[gtest]
64    fn phase_eight_fixtures_parse_with_stable_dense_identities() -> GtestResult<()> {
65        let fixtures = [
66            (EncounterFixture::TwoTargetPack, 2, 1, 1),
67            (EncounterFixture::BossAddWave, 3, 3, 1),
68            (EncounterFixture::TwoPullDungeon, 2, 2, 2),
69        ];
70
71        for (fixture, enemies, groups, pulls) in fixtures {
72            let definition = fixture.definition().or_fail()?;
73
74            verify_that!(definition.enemies.len(), eq(enemies))?;
75            verify_that!(definition.groups.len(), eq(groups))?;
76            verify_that!(definition.pulls.len(), eq(pulls))?;
77
78            for (index, enemy) in definition.enemies.iter().enumerate() {
79                verify_that!(enemy.id.as_usize(), eq(index))?;
80            }
81        }
82
83        Ok(())
84    }
85
86    #[gtest]
87    fn fixture_slugs_match_the_phase_eight_filenames() -> GtestResult<()> {
88        verify_that!(
89            EncounterFixture::TwoTargetPack.slug(),
90            eq("two_target_pack")
91        )?;
92        verify_that!(EncounterFixture::BossAddWave.slug(), eq("boss_add_wave"))?;
93        verify_that!(
94            EncounterFixture::TwoPullDungeon.slug(),
95            eq("two_pull_dungeon")
96        )?;
97
98        Ok(())
99    }
100}