tiger_lib/
game.rs

1//! Dealing with which game we are validating
2
3use std::fmt::{Display, Formatter};
4use std::sync::OnceLock;
5
6use anyhow::{anyhow, Result};
7use bitflags::bitflags;
8
9use crate::helpers::display_choices;
10
11/// Records at runtime which game we are validating, in case there are multiple feature flags set.
12static GAME: OnceLock<Game> = OnceLock::new();
13
14/// Enum specifying which game we are validating.
15///
16/// This enum is meant to be optimized away entirely when there is only one feature flag set.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum Game {
19    #[cfg(feature = "ck3")]
20    Ck3,
21    #[cfg(feature = "vic3")]
22    Vic3,
23    #[cfg(feature = "imperator")]
24    Imperator,
25    #[cfg(feature = "hoi4")]
26    Hoi4,
27}
28
29impl Game {
30    /// Decide which game we are validating. Should be called as early as possible.
31    /// Returns an error if called more than once.
32    pub fn set(game: Game) -> Result<()> {
33        GAME.set(game).map_err(|_| anyhow!("tried to set game type twice"))?;
34        Ok(())
35    }
36
37    /// Return which game we are validating. Should only be called after [`Game::set`].
38    ///
39    /// ## Panics
40    /// Will panic if called before [`Game::set`].
41    #[allow(clippy::self_named_constructors)] // not a constructor
42    #[allow(unreachable_code)]
43    pub fn game() -> Game {
44        #[cfg(all(
45            feature = "ck3",
46            not(feature = "vic3"),
47            not(feature = "imperator"),
48            not(feature = "hoi4")
49        ))]
50        return Game::Ck3;
51        #[cfg(all(
52            feature = "vic3",
53            not(feature = "ck3"),
54            not(feature = "imperator"),
55            not(feature = "hoi4")
56        ))]
57        return Game::Vic3;
58        #[cfg(all(
59            feature = "imperator",
60            not(feature = "ck3"),
61            not(feature = "vic3"),
62            not(feature = "hoi4")
63        ))]
64        return Game::Imperator;
65        #[cfg(all(
66            feature = "hoi4",
67            not(feature = "ck3"),
68            not(feature = "vic3"),
69            not(feature = "imperator")
70        ))]
71        return Game::Hoi4;
72        *GAME.get().expect("internal error: don't know which game we are validating")
73    }
74
75    /// Convenience function indicating whether we are validating Crusader Kings 3 mods.
76    #[inline]
77    pub(crate) fn is_ck3() -> bool {
78        #[cfg(not(feature = "ck3"))]
79        return false;
80        #[cfg(all(
81            feature = "ck3",
82            not(feature = "vic3"),
83            not(feature = "imperator"),
84            not(feature = "hoi4")
85        ))]
86        return true;
87        #[cfg(all(
88            feature = "ck3",
89            any(feature = "vic3", feature = "imperator", feature = "hoi4")
90        ))]
91        return GAME.get() == Some(&Game::Ck3);
92    }
93
94    /// Convenience function indicating whether we are validating Victoria 3 mods.
95    #[inline]
96    pub(crate) fn is_vic3() -> bool {
97        #[cfg(not(feature = "vic3"))]
98        return false;
99        #[cfg(all(
100            feature = "vic3",
101            not(feature = "ck3"),
102            not(feature = "imperator"),
103            not(feature = "hoi4")
104        ))]
105        return true;
106        #[cfg(all(
107            feature = "vic3",
108            any(feature = "ck3", feature = "imperator", feature = "hoi4")
109        ))]
110        return GAME.get() == Some(&Game::Vic3);
111    }
112
113    /// Convenience function indicating whether we are validating Imperator: Rome mods.
114    #[inline]
115    pub(crate) fn is_imperator() -> bool {
116        #[cfg(not(feature = "imperator"))]
117        return false;
118        #[cfg(all(
119            feature = "imperator",
120            not(feature = "ck3"),
121            not(feature = "vic3"),
122            not(feature = "hoi4")
123        ))]
124        return true;
125        #[cfg(all(
126            feature = "imperator",
127            any(feature = "ck3", feature = "vic3", feature = "hoi4")
128        ))]
129        return GAME.get() == Some(&Game::Imperator);
130    }
131
132    /// Convenience function indicating whether we are validating one of the three newer games
133    /// which use the Jomini scripting engine.
134    #[inline]
135    pub(crate) fn is_jomini() -> bool {
136        Game::is_ck3() || Game::is_vic3() || Game::is_imperator()
137    }
138
139    /// Convenience function indicating whether we are validating Imperator: Rome mods.
140    #[inline]
141    pub(crate) fn is_hoi4() -> bool {
142        #[cfg(not(feature = "hoi4"))]
143        return false;
144        #[cfg(all(
145            feature = "hoi4",
146            not(feature = "ck3"),
147            not(feature = "vic3"),
148            not(feature = "imperator")
149        ))]
150        return true;
151        #[cfg(all(
152            feature = "hoi4",
153            any(feature = "ck3", feature = "vic3", feature = "imperator")
154        ))]
155        return GAME.get() == Some(&Game::Hoi4);
156    }
157}
158
159bitflags! {
160    /// A set of bitflags to indicate for which game something is intended,
161    /// independent of which game we are validating.
162    ///
163    /// This way, error messages about things being used in the wrong game can be given at runtime.
164    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
165    pub struct GameFlags: u8 {
166        const Ck3 = 0x01;
167        const Vic3 = 0x02;
168        const Imperator = 0x04;
169        const Hoi4 = 0x08;
170    }
171}
172
173impl GameFlags {
174    /// Get a [`GameFlags`] value representing the game being validated.
175    /// Useful for checking with `.contains`.
176    pub fn game() -> Self {
177        // Unfortunately we have to translate between the types here.
178        match Game::game() {
179            #[cfg(feature = "ck3")]
180            Game::Ck3 => GameFlags::Ck3,
181            #[cfg(feature = "vic3")]
182            Game::Vic3 => GameFlags::Vic3,
183            #[cfg(feature = "imperator")]
184            Game::Imperator => GameFlags::Imperator,
185            #[cfg(feature = "hoi4")]
186            Game::Hoi4 => GameFlags::Hoi4,
187        }
188    }
189
190    pub const fn jomini() -> Self {
191        GameFlags::Ck3.union(GameFlags::Vic3).union(GameFlags::Imperator)
192    }
193}
194
195impl Display for GameFlags {
196    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
197        let mut vec = Vec::new();
198        if self.contains(Self::Ck3) {
199            vec.push("Crusader Kings 3");
200        }
201        if self.contains(Self::Vic3) {
202            vec.push("Victoria 3");
203        }
204        if self.contains(Self::Imperator) {
205            vec.push("Imperator: Rome");
206        }
207        if self.contains(Self::Hoi4) {
208            vec.push("Hearts of Iron 4");
209        }
210        display_choices(f, &vec, "and")
211    }
212}
213
214impl From<Game> for GameFlags {
215    /// Convert a [`Game`] into a [`GameFlags`] with just that game's flag set.
216    fn from(game: Game) -> Self {
217        match game {
218            #[cfg(feature = "ck3")]
219            Game::Ck3 => GameFlags::Ck3,
220            #[cfg(feature = "vic3")]
221            Game::Vic3 => GameFlags::Vic3,
222            #[cfg(feature = "imperator")]
223            Game::Imperator => GameFlags::Imperator,
224            #[cfg(feature = "hoi4")]
225            Game::Hoi4 => GameFlags::Hoi4,
226        }
227    }
228}