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}
26
27impl Game {
28    /// Decide which game we are validating. Should be called as early as possible.
29    /// Returns an error if called more than once.
30    pub fn set(game: Game) -> Result<()> {
31        GAME.set(game).map_err(|_| anyhow!("tried to set game type twice"))?;
32        Ok(())
33    }
34
35    /// Return which game we are validating. Should only be called after [`Game::set`].
36    ///
37    /// ## Panics
38    /// Will panic if called before [`Game::set`].
39    #[allow(clippy::self_named_constructors)] // not a constructor
40    #[allow(unreachable_code)]
41    pub fn game() -> Game {
42        #[cfg(all(feature = "ck3", not(feature = "vic3"), not(feature = "imperator")))]
43        return Game::Ck3;
44        #[cfg(all(feature = "vic3", not(feature = "ck3"), not(feature = "imperator")))]
45        return Game::Vic3;
46        #[cfg(all(feature = "imperator", not(feature = "ck3"), not(feature = "vic3")))]
47        return Game::Imperator;
48        *GAME.get().expect("internal error: don't know which game we are validating")
49    }
50
51    /// Convenience function indicating whether we are validating Crusader Kings 3 mods.
52    pub(crate) fn is_ck3() -> bool {
53        #[cfg(not(feature = "ck3"))]
54        return false;
55        #[cfg(all(feature = "ck3", not(feature = "vic3"), not(feature = "imperator")))]
56        return true;
57        #[cfg(all(feature = "ck3", any(feature = "vic3", feature = "imperator")))]
58        return GAME.get() == Some(&Game::Ck3);
59    }
60
61    /// Convenience function indicating whether we are validating Victoria 3 mods.
62    pub(crate) fn is_vic3() -> bool {
63        #[cfg(not(feature = "vic3"))]
64        return false;
65        #[cfg(all(feature = "vic3", not(feature = "ck3"), not(feature = "imperator")))]
66        return true;
67        #[cfg(all(feature = "vic3", any(feature = "ck3", feature = "imperator")))]
68        return GAME.get() == Some(&Game::Vic3);
69    }
70
71    /// Convenience function indicating whether we are validating Imperator: Rome mods.
72    pub(crate) fn is_imperator() -> bool {
73        #[cfg(not(feature = "imperator"))]
74        return false;
75        #[cfg(all(feature = "imperator", not(feature = "ck3"), not(feature = "vic3")))]
76        return true;
77        #[cfg(all(feature = "imperator", any(feature = "ck3", feature = "vic3")))]
78        return GAME.get() == Some(&Game::Imperator);
79    }
80}
81
82bitflags! {
83    /// A set of bitflags to indicate for which game something is intended,
84    /// independent of which game we are validating.
85    ///
86    /// This way, error messages about things being used in the wrong game can be given at runtime.
87    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88    pub struct GameFlags: u8 {
89        const Ck3 = 0x01;
90        const Vic3 = 0x02;
91        const Imperator = 0x04;
92    }
93}
94
95impl GameFlags {
96    /// Get a [`GameFlags`] value representing the game being validated.
97    /// Useful for checking with `.contains`.
98    pub fn game() -> Self {
99        // Unfortunately we have to translate between the types here.
100        match Game::game() {
101            #[cfg(feature = "ck3")]
102            Game::Ck3 => GameFlags::Ck3,
103            #[cfg(feature = "vic3")]
104            Game::Vic3 => GameFlags::Vic3,
105            #[cfg(feature = "imperator")]
106            Game::Imperator => GameFlags::Imperator,
107        }
108    }
109}
110
111impl Display for GameFlags {
112    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
113        let mut vec = Vec::new();
114        if self.contains(Self::Ck3) {
115            vec.push("Crusader Kings 3");
116        }
117        if self.contains(Self::Vic3) {
118            vec.push("Victoria 3");
119        }
120        if self.contains(Self::Imperator) {
121            vec.push("Imperator: Rome");
122        }
123        display_choices(f, &vec, "and")
124    }
125}
126
127impl From<Game> for GameFlags {
128    /// Convert a [`Game`] into a [`GameFlags`] with just that game's flag set.
129    fn from(game: Game) -> Self {
130        match game {
131            #[cfg(feature = "ck3")]
132            Game::Ck3 => GameFlags::Ck3,
133            #[cfg(feature = "vic3")]
134            Game::Vic3 => GameFlags::Vic3,
135            #[cfg(feature = "imperator")]
136            Game::Imperator => GameFlags::Imperator,
137        }
138    }
139}