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::{Result, anyhow};
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 = "eu5")]
26    Eu5,
27    #[cfg(feature = "hoi4")]
28    Hoi4,
29}
30
31impl Game {
32    /// Decide which game we are validating. Should be called as early as possible.
33    /// Returns an error if called more than once.
34    pub fn set(game: Game) -> Result<()> {
35        GAME.set(game).map_err(|_| anyhow!("tried to set game type twice"))?;
36        Ok(())
37    }
38
39    /// Return which game we are validating. Should only be called after [`Game::set`].
40    ///
41    /// ## Panics
42    /// Will panic if called before [`Game::set`].
43    #[allow(clippy::self_named_constructors)] // not a constructor
44    #[allow(unreachable_code)]
45    pub fn game() -> Game {
46        #[cfg(all(
47            feature = "ck3",
48            not(feature = "vic3"),
49            not(feature = "imperator"),
50            not(feature = "eu5"),
51            not(feature = "hoi4")
52        ))]
53        return Game::Ck3;
54        #[cfg(all(
55            feature = "vic3",
56            not(feature = "ck3"),
57            not(feature = "imperator"),
58            not(feature = "eu5"),
59            not(feature = "hoi4")
60        ))]
61        return Game::Vic3;
62        #[cfg(all(
63            feature = "imperator",
64            not(feature = "ck3"),
65            not(feature = "vic3"),
66            not(feature = "eu5"),
67            not(feature = "hoi4")
68        ))]
69        return Game::Imperator;
70        #[cfg(all(
71            feature = "hoi4",
72            not(feature = "ck3"),
73            not(feature = "vic3"),
74            not(feature = "imperator"),
75            not(feature = "eu5"),
76        ))]
77        return Game::Hoi4;
78        #[cfg(all(
79            feature = "eu5",
80            not(feature = "ck3"),
81            not(feature = "vic3"),
82            not(feature = "imperator"),
83            not(feature = "hoi4"),
84        ))]
85        return Game::Eu5;
86        *GAME.get().expect("internal error: don't know which game we are validating")
87    }
88
89    /// Convenience function indicating whether we are validating Crusader Kings 3 mods.
90    #[inline]
91    pub(crate) fn is_ck3() -> bool {
92        #[cfg(not(feature = "ck3"))]
93        return false;
94        #[cfg(all(
95            feature = "ck3",
96            not(feature = "vic3"),
97            not(feature = "imperator"),
98            not(feature = "eu5"),
99            not(feature = "hoi4")
100        ))]
101        return true;
102        #[cfg(all(
103            feature = "ck3",
104            any(feature = "vic3", feature = "imperator", feature = "eu5", feature = "hoi4")
105        ))]
106        return GAME.get() == Some(&Game::Ck3);
107    }
108
109    /// Convenience function indicating whether we are validating Victoria 3 mods.
110    #[inline]
111    pub(crate) fn is_vic3() -> bool {
112        #[cfg(not(feature = "vic3"))]
113        return false;
114        #[cfg(all(
115            feature = "vic3",
116            not(feature = "ck3"),
117            not(feature = "imperator"),
118            not(feature = "eu5"),
119            not(feature = "hoi4")
120        ))]
121        return true;
122        #[cfg(all(
123            feature = "vic3",
124            any(feature = "ck3", feature = "imperator", feature = "eu5", feature = "hoi4")
125        ))]
126        return GAME.get() == Some(&Game::Vic3);
127    }
128
129    /// Convenience function indicating whether we are validating Imperator: Rome mods.
130    #[inline]
131    pub(crate) fn is_imperator() -> bool {
132        #[cfg(not(feature = "imperator"))]
133        return false;
134        #[cfg(all(
135            feature = "imperator",
136            not(feature = "ck3"),
137            not(feature = "vic3"),
138            not(feature = "eu5"),
139            not(feature = "hoi4")
140        ))]
141        return true;
142        #[cfg(all(
143            feature = "imperator",
144            any(feature = "ck3", feature = "vic3", feature = "hoi4", feature = "eu5")
145        ))]
146        return GAME.get() == Some(&Game::Imperator);
147    }
148
149    /// Convenience function indicating whether we are validating Europa Universalis 5 mods.
150    #[inline]
151    pub(crate) fn is_eu5() -> bool {
152        #[cfg(not(feature = "eu5"))]
153        return false;
154        #[cfg(all(
155            feature = "eu5",
156            not(feature = "ck3"),
157            not(feature = "vic3"),
158            not(feature = "imperator"),
159            not(feature = "hoi4")
160        ))]
161        return true;
162        #[cfg(all(
163            feature = "eu5",
164            any(feature = "ck3", feature = "vic3", feature = "hoi4", feature = "imperator")
165        ))]
166        return GAME.get() == Some(&Game::Eu5);
167    }
168
169    /// Convenience function indicating whether we are validating one of the four newer games
170    /// which use the Jomini scripting engine.
171    #[inline]
172    pub(crate) fn is_jomini() -> bool {
173        Game::is_ck3() || Game::is_vic3() || Game::is_imperator() || Game::is_eu5()
174    }
175
176    /// Convenience function indicating whether we are validating Hearts of Iron 4 mods.
177    #[inline]
178    pub(crate) fn is_hoi4() -> bool {
179        #[cfg(not(feature = "hoi4"))]
180        return false;
181        #[cfg(all(
182            feature = "hoi4",
183            not(feature = "ck3"),
184            not(feature = "vic3"),
185            not(feature = "imperator"),
186            not(feature = "eu5")
187        ))]
188        return true;
189        #[cfg(all(
190            feature = "hoi4",
191            any(feature = "ck3", feature = "vic3", feature = "imperator", feature = "eu5")
192        ))]
193        return GAME.get() == Some(&Game::Hoi4);
194    }
195}
196
197bitflags! {
198    /// A set of bitflags to indicate for which game something is intended,
199    /// independent of which game we are validating.
200    ///
201    /// This way, error messages about things being used in the wrong game can be given at runtime.
202    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
203    pub struct GameFlags: u8 {
204        const Ck3 = 0x01;
205        const Vic3 = 0x02;
206        const Imperator = 0x04;
207        const Eu5 = 0x08;
208        const Hoi4 = 0x10;
209    }
210}
211
212impl GameFlags {
213    /// Get a [`GameFlags`] value representing the game being validated.
214    /// Useful for checking with `.contains`.
215    pub fn game() -> Self {
216        // Unfortunately we have to translate between the types here.
217        match Game::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 = "eu5")]
225            Game::Eu5 => GameFlags::Eu5,
226            #[cfg(feature = "hoi4")]
227            Game::Hoi4 => GameFlags::Hoi4,
228        }
229    }
230
231    pub const fn jomini() -> Self {
232        GameFlags::Ck3.union(GameFlags::Vic3).union(GameFlags::Imperator).union(GameFlags::Eu5)
233    }
234}
235
236impl Display for GameFlags {
237    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
238        let mut vec = Vec::new();
239        if self.contains(Self::Ck3) {
240            vec.push("Crusader Kings 3");
241        }
242        if self.contains(Self::Vic3) {
243            vec.push("Victoria 3");
244        }
245        if self.contains(Self::Imperator) {
246            vec.push("Imperator: Rome");
247        }
248        if self.contains(Self::Eu5) {
249            vec.push("Europa Universalis 5");
250        }
251        if self.contains(Self::Hoi4) {
252            vec.push("Hearts of Iron 4");
253        }
254        display_choices(f, &vec, "and")
255    }
256}
257
258impl From<Game> for GameFlags {
259    /// Convert a [`Game`] into a [`GameFlags`] with just that game's flag set.
260    fn from(game: Game) -> Self {
261        match game {
262            #[cfg(feature = "ck3")]
263            Game::Ck3 => GameFlags::Ck3,
264            #[cfg(feature = "vic3")]
265            Game::Vic3 => GameFlags::Vic3,
266            #[cfg(feature = "imperator")]
267            Game::Imperator => GameFlags::Imperator,
268            #[cfg(feature = "eu5")]
269            Game::Eu5 => GameFlags::Eu5,
270            #[cfg(feature = "hoi4")]
271            Game::Hoi4 => GameFlags::Hoi4,
272        }
273    }
274}