Skip to main content

steel_registry/
game_rules.rs

1use crate::{RegistryEntry, RegistryExt};
2use rustc_hash::FxHashMap;
3use serde::{Deserialize, Serialize};
4use steel_utils::Identifier;
5
6/// Categories for game rules, used for organization in the UI.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum GameRuleCategory {
9    Chat,
10    Drops,
11    Misc,
12    Mobs,
13    Player,
14    Spawning,
15    Updates,
16}
17
18/// The type of a game rule value.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum GameRuleType {
21    Bool,
22    Int,
23}
24
25/// A game rule value - either a boolean or an integer.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum GameRuleValue {
29    Bool(bool),
30    Int(i32),
31}
32
33impl GameRuleValue {
34    /// Returns the boolean value if this is a Bool variant.
35    #[must_use]
36    pub fn as_bool(&self) -> Option<bool> {
37        match self {
38            Self::Bool(b) => Some(*b),
39            Self::Int(_) => None,
40        }
41    }
42
43    /// Returns the integer value if this is an Int variant.
44    #[must_use]
45    pub fn as_int(&self) -> Option<i32> {
46        match self {
47            Self::Bool(_) => None,
48            Self::Int(i) => Some(*i),
49        }
50    }
51
52    /// Returns true if this is a Bool variant.
53    #[must_use]
54    pub fn is_bool(&self) -> bool {
55        matches!(self, Self::Bool(_))
56    }
57
58    /// Returns true if this is an Int variant.
59    #[must_use]
60    pub fn is_int(&self) -> bool {
61        matches!(self, Self::Int(_))
62    }
63
64    /// Returns true if this value matches the given type.
65    #[must_use]
66    pub fn matches_type(&self, value_type: GameRuleType) -> bool {
67        matches!(
68            (self, value_type),
69            (Self::Bool(_), GameRuleType::Bool) | (Self::Int(_), GameRuleType::Int)
70        )
71    }
72}
73
74impl std::fmt::Display for GameRuleValue {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        match self {
77            Self::Bool(b) => write!(f, "{b}"),
78            Self::Int(i) => write!(f, "{i}"),
79        }
80    }
81}
82
83/// A game rule definition.
84#[derive(Debug)]
85pub struct GameRule {
86    /// The key/name of the game rule (e.g., "keep_inventory").
87    pub key: Identifier,
88    /// The category this game rule belongs to.
89    pub category: GameRuleCategory,
90    /// The type of this game rule (bool or int).
91    pub value_type: GameRuleType,
92    /// The default value of this game rule.
93    pub default_value: GameRuleValue,
94    /// Minimum value for integer game rules (None means no limit).
95    pub min_value: Option<i32>,
96    /// Maximum value for integer game rules (None means no limit).
97    pub max_value: Option<i32>,
98}
99
100pub type GameRuleRef = &'static GameRule;
101
102pub struct GameRuleRegistry {
103    game_rules_by_id: Vec<GameRuleRef>,
104    game_rules_by_key: FxHashMap<Identifier, usize>,
105    allows_registering: bool,
106}
107
108impl GameRuleRegistry {
109    #[must_use]
110    pub fn new() -> Self {
111        Self {
112            game_rules_by_id: Vec::new(),
113            game_rules_by_key: FxHashMap::default(),
114            allows_registering: true,
115        }
116    }
117}
118
119crate::impl_standard_methods!(
120    GameRuleRegistry,
121    GameRuleRef,
122    game_rules_by_id,
123    game_rules_by_key,
124    allows_registering
125);
126
127crate::impl_registry!(
128    GameRuleRegistry,
129    GameRule,
130    game_rules_by_id,
131    game_rules_by_key,
132    game_rules
133);
134
135/// Stores per-world game rule values.
136///
137/// This is separate from the registry - the registry holds static definitions,
138/// while `GameRuleValues` holds the actual mutable values for a specific world.
139#[derive(Debug, Clone, Default)]
140pub struct GameRuleValues {
141    /// Values indexed by game rule registry ID.
142    values: Vec<GameRuleValue>,
143}
144
145impl GameRuleValues {
146    /// Creates a new `GameRuleValues` with all default values from the registry.
147    #[must_use]
148    pub fn new(registry: &GameRuleRegistry) -> Self {
149        let values = registry
150            .iter()
151            .map(|(_, rule)| rule.default_value)
152            .collect();
153        Self { values }
154    }
155
156    /// Gets the value of a game rule.
157    #[must_use]
158    pub fn get(&self, rule: GameRuleRef, _registry: &GameRuleRegistry) -> GameRuleValue {
159        let id = rule.id();
160        self.values[id]
161    }
162
163    /// Sets the value of a game rule.
164    ///
165    /// Returns `true` if the value was set, `false` if the type didn't match
166    /// or the value is out of bounds.
167    pub fn set(
168        &mut self,
169        rule: GameRuleRef,
170        value: GameRuleValue,
171        _registry: &GameRuleRegistry,
172    ) -> bool {
173        if !value.matches_type(rule.value_type) {
174            return false;
175        }
176        // Check bounds for integer values
177        if let GameRuleValue::Int(v) = value {
178            if let Some(min) = rule.min_value
179                && v < min
180            {
181                return false;
182            }
183            if let Some(max) = rule.max_value
184                && v > max
185            {
186                return false;
187            }
188        }
189        let id = rule.id();
190        self.values[id] = value;
191        true
192    }
193
194    /// Gets a game rule value by name.
195    #[must_use]
196    pub fn get_by_name(&self, name: &str, registry: &GameRuleRegistry) -> Option<GameRuleValue> {
197        let key = Identifier::vanilla(name.to_string());
198        let id = registry.id_from_key(&key)?;
199        self.values.get(id).copied()
200    }
201
202    /// Sets a game rule value by name.
203    ///
204    /// Returns `true` if the game rule was found and set, `false` if the rule
205    /// doesn't exist, the value type doesn't match, or the value is out of bounds.
206    pub fn set_by_name(
207        &mut self,
208        name: &str,
209        value: GameRuleValue,
210        registry: &GameRuleRegistry,
211    ) -> bool {
212        let key = Identifier::vanilla(name.to_string());
213        if let Some(rule) = registry.by_key(&key) {
214            self.set(rule, value, registry)
215        } else {
216            false
217        }
218    }
219}