1use std::{
2 fmt::{self, Debug, Display, Formatter},
3 ptr::from_ref,
4};
5
6use rustc_hash::FxHashMap;
7use serde_json::Value;
8use steel_utils::{Downcast as _, DowncastType, DowncastTypeKey, ErasedType, Identifier};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum GameRuleCategory {
13 Chat,
14 Drops,
15 Misc,
16 Mobs,
17 Player,
18 Spawning,
19 Updates,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum GameRuleType {
25 Bool,
26 Int,
27}
28
29pub trait GameRuleValueType: DowncastType + Clone + Debug + Display + Send + Sync {}
31
32impl<T> GameRuleValueType for T where T: DowncastType + Clone + Debug + Display + Send + Sync {}
33
34trait ErasedGameRuleValue: ErasedType + Debug + Display + Send + Sync {
35 fn clone_value(&self) -> Box<dyn ErasedGameRuleValue>;
36}
37
38impl<T: GameRuleValueType> ErasedGameRuleValue for T {
39 fn clone_value(&self) -> Box<dyn ErasedGameRuleValue> {
40 Box::new(self.clone())
41 }
42}
43
44pub struct GameRuleValue {
46 value: Box<dyn ErasedGameRuleValue>,
47}
48
49impl GameRuleValue {
50 #[must_use]
51 pub fn new(value: impl GameRuleValueType) -> Self {
52 Self {
53 value: Box::new(value),
54 }
55 }
56
57 #[must_use]
58 pub fn downcast_ref<T: DowncastType>(&self) -> Option<&T> {
59 self.value.downcast_ref::<T>()
60 }
61
62 #[must_use]
63 pub fn type_key(&self) -> DowncastTypeKey {
64 self.value.downcast_type_key()
65 }
66}
67
68impl Clone for GameRuleValue {
69 fn clone(&self) -> Self {
70 Self {
71 value: self.value.clone_value(),
72 }
73 }
74}
75
76impl Debug for GameRuleValue {
77 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
78 formatter
79 .debug_struct("GameRuleValue")
80 .field("type_key", &self.type_key())
81 .field("value", &self.value)
82 .finish()
83 }
84}
85
86impl Display for GameRuleValue {
87 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
88 Display::fmt(self.value.as_ref(), formatter)
89 }
90}
91
92#[derive(Debug)]
93struct GameRuleCodec<T: GameRuleValueType> {
94 serialize: fn(&T) -> Value,
95 deserialize: fn(&Value) -> Option<T>,
96 command_result: fn(&T) -> i32,
97 validates: fn(&GameRule<T>, &T) -> bool,
98}
99
100#[derive(Debug)]
102pub struct GameRule<T: GameRuleValueType> {
103 key: Identifier,
104 category: GameRuleCategory,
105 value_type: GameRuleType,
106 default_value: T,
107 min_value: Option<i32>,
108 max_value: Option<i32>,
109 codec: GameRuleCodec<T>,
110}
111
112impl<T: GameRuleValueType> GameRule<T> {
113 #[must_use]
114 pub const fn key(&self) -> &Identifier {
115 &self.key
116 }
117
118 #[must_use]
119 pub const fn category(&self) -> GameRuleCategory {
120 self.category
121 }
122
123 #[must_use]
124 pub const fn value_type(&self) -> GameRuleType {
125 self.value_type
126 }
127
128 #[must_use]
129 pub const fn default_value(&self) -> &T {
130 &self.default_value
131 }
132
133 #[must_use]
134 pub const fn min_value(&self) -> Option<i32> {
135 self.min_value
136 }
137
138 #[must_use]
139 pub const fn max_value(&self) -> Option<i32> {
140 self.max_value
141 }
142
143 fn validates(&self, value: &T) -> bool {
144 (self.codec.validates)(self, value)
145 }
146}
147
148impl GameRule<bool> {
149 #[must_use]
150 pub const fn boolean(key: Identifier, category: GameRuleCategory, default_value: bool) -> Self {
151 Self {
152 key,
153 category,
154 value_type: GameRuleType::Bool,
155 default_value,
156 min_value: None,
157 max_value: None,
158 codec: GameRuleCodec {
159 serialize: |value| Value::Bool(*value),
160 deserialize: Value::as_bool,
161 command_result: |value| i32::from(*value),
162 validates: |_, _| true,
163 },
164 }
165 }
166}
167
168impl GameRule<i32> {
169 #[must_use]
170 pub const fn integer(
171 key: Identifier,
172 category: GameRuleCategory,
173 default_value: i32,
174 min_value: Option<i32>,
175 max_value: Option<i32>,
176 ) -> Self {
177 if let Some(min) = min_value {
178 assert!(
179 default_value >= min,
180 "game rule default is below its minimum"
181 );
182 }
183 if let Some(max) = max_value {
184 assert!(
185 default_value <= max,
186 "game rule default is above its maximum"
187 );
188 }
189 if let (Some(min), Some(max)) = (min_value, max_value) {
190 assert!(min <= max, "game rule minimum is above its maximum");
191 }
192 Self {
193 key,
194 category,
195 value_type: GameRuleType::Int,
196 default_value,
197 min_value,
198 max_value,
199 codec: GameRuleCodec {
200 serialize: |value| Value::from(*value),
201 deserialize: |value| {
202 let value = value.as_i64()?;
203 i32::try_from(value).ok()
204 },
205 command_result: |value| *value,
206 validates: |rule, value| {
207 rule.min_value.is_none_or(|min| *value >= min)
208 && rule.max_value.is_none_or(|max| *value <= max)
209 },
210 },
211 }
212 }
213}
214
215impl<T: GameRuleValueType> PartialEq for GameRule<T> {
216 fn eq(&self, other: &Self) -> bool {
217 self.key == other.key
218 }
219}
220
221impl<T: GameRuleValueType> Eq for GameRule<T> {}
222
223pub trait ErasedGameRule: Debug + Send + Sync {
225 fn key(&self) -> &Identifier;
226 fn category(&self) -> GameRuleCategory;
227 fn value_type(&self) -> GameRuleType;
228 fn min_value(&self) -> Option<i32>;
229 fn max_value(&self) -> Option<i32>;
230 fn default_erased_value(&self) -> GameRuleValue;
231 fn validates_erased_value(&self, value: &GameRuleValue) -> bool;
232 fn serialize_erased_value(&self, value: &GameRuleValue) -> Value;
233 fn deserialize_erased_value(&self, value: &Value) -> Option<GameRuleValue>;
234 fn erased_command_result(&self, value: &GameRuleValue) -> i32;
235}
236
237impl<T: GameRuleValueType> ErasedGameRule for GameRule<T> {
238 fn key(&self) -> &Identifier {
239 self.key()
240 }
241
242 fn category(&self) -> GameRuleCategory {
243 self.category()
244 }
245
246 fn value_type(&self) -> GameRuleType {
247 self.value_type()
248 }
249
250 fn min_value(&self) -> Option<i32> {
251 self.min_value()
252 }
253
254 fn max_value(&self) -> Option<i32> {
255 self.max_value()
256 }
257
258 fn default_erased_value(&self) -> GameRuleValue {
259 GameRuleValue::new(self.default_value.clone())
260 }
261
262 fn validates_erased_value(&self, value: &GameRuleValue) -> bool {
263 value
264 .downcast_ref::<T>()
265 .is_some_and(|value| self.validates(value))
266 }
267
268 fn serialize_erased_value(&self, value: &GameRuleValue) -> Value {
269 let Some(value) = value.downcast_ref::<T>() else {
270 panic!("stored value type does not match game rule {}", self.key);
271 };
272 (self.codec.serialize)(value)
273 }
274
275 fn deserialize_erased_value(&self, value: &Value) -> Option<GameRuleValue> {
276 (self.codec.deserialize)(value).map(GameRuleValue::new)
277 }
278
279 fn erased_command_result(&self, value: &GameRuleValue) -> i32 {
280 let Some(value) = value.downcast_ref::<T>() else {
281 panic!("stored value type does not match game rule {}", self.key);
282 };
283 (self.codec.command_result)(value)
284 }
285}
286
287pub type GameRuleRef<T> = &'static GameRule<T>;
288pub type ErasedGameRuleRef = &'static dyn ErasedGameRule;
289
290pub struct GameRuleRegistry {
291 game_rules_by_id: Vec<ErasedGameRuleRef>,
292 game_rules_by_key: FxHashMap<Identifier, usize>,
293 allows_registering: bool,
294}
295
296impl GameRuleRegistry {
297 #[must_use]
298 pub fn new() -> Self {
299 Self {
300 game_rules_by_id: Vec::new(),
301 game_rules_by_key: FxHashMap::default(),
302 allows_registering: true,
303 }
304 }
305
306 pub fn register<T: GameRuleValueType>(&mut self, rule: GameRuleRef<T>) -> usize {
307 assert!(
308 self.allows_registering,
309 "Cannot register game rules after registry has been frozen"
310 );
311 assert!(
312 !self.game_rules_by_key.contains_key(rule.key()),
313 "Cannot register duplicate game rule {}",
314 rule.key()
315 );
316
317 let id = self.game_rules_by_id.len();
318 self.game_rules_by_key.insert(rule.key().clone(), id);
319 self.game_rules_by_id.push(rule);
320 id
321 }
322
323 pub const fn freeze(&mut self) {
324 self.allows_registering = false;
325 }
326
327 #[must_use]
328 pub fn by_id(&self, id: usize) -> Option<ErasedGameRuleRef> {
329 self.game_rules_by_id.get(id).copied()
330 }
331
332 #[must_use]
333 pub fn by_key(&self, key: &Identifier) -> Option<ErasedGameRuleRef> {
334 self.id_from_key(key).and_then(|id| self.by_id(id))
335 }
336
337 #[must_use]
338 pub fn id_from_key(&self, key: &Identifier) -> Option<usize> {
339 self.game_rules_by_key.get(key).copied()
340 }
341
342 #[must_use]
343 pub fn len(&self) -> usize {
344 self.game_rules_by_id.len()
345 }
346
347 #[must_use]
348 pub fn is_empty(&self) -> bool {
349 self.game_rules_by_id.is_empty()
350 }
351
352 pub fn iter(&self) -> impl Iterator<Item = (usize, ErasedGameRuleRef)> + '_ {
353 self.game_rules_by_id.iter().copied().enumerate()
354 }
355}
356
357impl Default for GameRuleRegistry {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363#[derive(Debug, Clone, Default)]
365pub struct GameRuleValues {
366 values: Vec<GameRuleValue>,
367}
368
369impl GameRuleValues {
370 #[must_use]
372 pub fn new(registry: &GameRuleRegistry) -> Self {
373 let values = registry
374 .iter()
375 .map(|(_, rule)| rule.default_erased_value())
376 .collect();
377 Self { values }
378 }
379
380 #[must_use]
382 pub fn get<T: GameRuleValueType>(&self, rule: &GameRule<T>, registry: &GameRuleRegistry) -> T {
383 let value = self.get_erased(rule, registry);
384 let Some(value) = value.downcast_ref::<T>() else {
385 panic!("stored value type does not match game rule {}", rule.key());
386 };
387 value.clone()
388 }
389
390 pub fn set<T: GameRuleValueType>(
392 &mut self,
393 rule: &GameRule<T>,
394 value: T,
395 registry: &GameRuleRegistry,
396 ) -> bool {
397 if !rule.validates(&value) {
398 return false;
399 }
400 let id = Self::rule_id(rule, registry);
401 self.values[id] = GameRuleValue::new(value);
402 true
403 }
404
405 #[must_use]
407 pub fn get_erased(
408 &self,
409 rule: &dyn ErasedGameRule,
410 registry: &GameRuleRegistry,
411 ) -> &GameRuleValue {
412 let id = Self::rule_id(rule, registry);
413 let Some(value) = self.values.get(id) else {
414 panic!("game rule {} has no stored value", rule.key());
415 };
416 value
417 }
418
419 pub fn set_erased(
421 &mut self,
422 rule: &dyn ErasedGameRule,
423 value: GameRuleValue,
424 registry: &GameRuleRegistry,
425 ) -> bool {
426 if !rule.validates_erased_value(&value) {
427 return false;
428 }
429 let id = Self::rule_id(rule, registry);
430 self.values[id] = value;
431 true
432 }
433
434 pub fn set_serialized_by_name(
436 &mut self,
437 name: &str,
438 value: &Value,
439 registry: &GameRuleRegistry,
440 ) -> bool {
441 let Ok(key) = name.parse::<Identifier>() else {
442 return false;
443 };
444 let Some(rule) = registry.by_key(&key) else {
445 return false;
446 };
447 let Some(value) = rule.deserialize_erased_value(value) else {
448 return false;
449 };
450 self.set_erased(rule, value, registry)
451 }
452
453 fn rule_id(rule: &dyn ErasedGameRule, registry: &GameRuleRegistry) -> usize {
454 let Some(id) = registry.id_from_key(rule.key()) else {
455 panic!("game rule {} is not registered", rule.key());
456 };
457 let Some(registered_rule) = registry.by_id(id) else {
458 panic!("game rule registry has no entry for {}", rule.key());
459 };
460 assert!(
461 from_ref(rule).cast::<()>() == from_ref(registered_rule).cast::<()>(),
462 "game rule {} is not the registered definition",
463 rule.key()
464 );
465 id
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use serde_json::Value;
472
473 use super::{GameRule, GameRuleCategory, GameRuleRegistry, GameRuleValue, GameRuleValues};
474 use steel_utils::Identifier;
475
476 static BOOL_RULE: GameRule<bool> = GameRule::boolean(
477 Identifier::vanilla_static("test_bool"),
478 GameRuleCategory::Misc,
479 true,
480 );
481 static INT_RULE: GameRule<i32> = GameRule::integer(
482 Identifier::vanilla_static("test_int"),
483 GameRuleCategory::Misc,
484 3,
485 Some(0),
486 Some(10),
487 );
488 static CUSTOM_BOOL_RULE: GameRule<bool> = GameRule::boolean(
489 Identifier::new_static("steel", "test_bool"),
490 GameRuleCategory::Misc,
491 true,
492 );
493
494 fn registry() -> GameRuleRegistry {
495 let mut registry = GameRuleRegistry::new();
496 registry.register(&BOOL_RULE);
497 registry.register(&INT_RULE);
498 registry.register(&CUSTOM_BOOL_RULE);
499 registry.freeze();
500 registry
501 }
502
503 #[test]
504 fn typed_values_use_defaults_and_update_without_callsite_downcasts() {
505 let registry = registry();
506 let mut values = GameRuleValues::new(®istry);
507
508 assert!(values.get(&BOOL_RULE, ®istry));
509 assert_eq!(values.get(&INT_RULE, ®istry), 3);
510 assert!(values.set(&BOOL_RULE, false, ®istry));
511 assert!(values.set(&INT_RULE, 7, ®istry));
512 assert!(!values.get(&BOOL_RULE, ®istry));
513 assert_eq!(values.get(&INT_RULE, ®istry), 7);
514 }
515
516 #[test]
517 fn typed_and_erased_sets_enforce_rule_bounds_and_value_type() {
518 let registry = registry();
519 let mut values = GameRuleValues::new(®istry);
520
521 assert!(!values.set(&INT_RULE, 11, ®istry));
522 assert_eq!(values.get(&INT_RULE, ®istry), 3);
523
524 let Some(int_rule) = registry.by_key(INT_RULE.key()) else {
525 panic!("test int rule should be registered");
526 };
527 assert!(!values.set_erased(int_rule, GameRuleValue::new(false), ®istry));
528 assert_eq!(values.get(&INT_RULE, ®istry), 3);
529 }
530
531 #[test]
532 fn erased_rule_codecs_round_trip_serialized_values() {
533 let registry = registry();
534 let mut values = GameRuleValues::new(®istry);
535
536 assert!(values.set_serialized_by_name("test_bool", &Value::Bool(false), ®istry));
537 assert!(values.set_serialized_by_name("test_int", &Value::from(8), ®istry));
538 assert!(values.set_serialized_by_name("steel:test_bool", &Value::Bool(false), ®istry,));
539 assert!(!values.set_serialized_by_name("test_int", &Value::from(12), ®istry));
540
541 for (_, rule) in registry.iter() {
542 let serialized = rule.serialize_erased_value(values.get_erased(rule, ®istry));
543 let Some(decoded) = rule.deserialize_erased_value(&serialized) else {
544 panic!("serialized game rule should decode");
545 };
546 assert!(rule.validates_erased_value(&decoded));
547 }
548
549 assert!(!values.get(&BOOL_RULE, ®istry));
550 assert_eq!(values.get(&INT_RULE, ®istry), 8);
551 assert!(!values.get(&CUSTOM_BOOL_RULE, ®istry));
552 }
553
554 #[test]
555 #[should_panic(expected = "is not the registered definition")]
556 fn same_key_does_not_allow_forging_different_rule_metadata() {
557 let registry = registry();
558 let values = GameRuleValues::new(®istry);
559 let forged_rule = GameRule::integer(
560 INT_RULE.key().clone(),
561 GameRuleCategory::Misc,
562 3,
563 None,
564 None,
565 );
566
567 let _ = values.get(&forged_rule, ®istry);
568 }
569}