Skip to main content

steel_registry/
attribute.rs

1use rustc_hash::FxHashMap;
2use steel_utils::{
3    Identifier,
4    codec::VarInt,
5    serial::{ReadFrom, WriteTo},
6};
7
8use crate::RegistryTags;
9
10/// The operation type for an attribute modifier.
11///
12/// Matches vanilla `AttributeModifier.Operation`:
13/// - `AddValue` (0): `total += amount`
14/// - `AddMultipliedBase` (1): `total += base * amount`
15/// - `AddMultipliedTotal` (2): `total *= 1 + amount`
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
17#[repr(u8)]
18#[expect(
19    clippy::enum_variant_names,
20    reason = "matches vanilla `AttributeModifier.Operation` names"
21)]
22pub enum AttributeModifierOperation {
23    AddValue = 0,
24    AddMultipliedBase = 1,
25    AddMultipliedTotal = 2,
26}
27
28impl AttributeModifierOperation {
29    #[must_use]
30    pub const fn from_id(id: i32) -> Option<Self> {
31        match id {
32            0 => Some(Self::AddValue),
33            1 => Some(Self::AddMultipliedBase),
34            2 => Some(Self::AddMultipliedTotal),
35            _ => None,
36        }
37    }
38
39    #[must_use]
40    pub const fn by_name(name: &str) -> Option<Self> {
41        match name {
42            "add_value" => Some(Self::AddValue),
43            "add_multiplied_base" => Some(Self::AddMultipliedBase),
44            "add_multiplied_total" => Some(Self::AddMultipliedTotal),
45            _ => None,
46        }
47    }
48
49    #[must_use]
50    pub const fn name(self) -> &'static str {
51        match self {
52            Self::AddValue => "add_value",
53            Self::AddMultipliedBase => "add_multiplied_base",
54            Self::AddMultipliedTotal => "add_multiplied_total",
55        }
56    }
57}
58
59impl WriteTo for AttributeModifierOperation {
60    fn write(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
61        VarInt(*self as i32).write(writer)
62    }
63}
64
65impl ReadFrom for AttributeModifierOperation {
66    fn read(data: &mut std::io::Cursor<&[u8]>) -> std::io::Result<Self> {
67        let id = VarInt::read(data)?.0;
68        Ok(Self::from_id(id).unwrap_or(Self::AddValue))
69    }
70}
71
72/// Vanilla entity attribute definition
73///
74/// Unlike vanilla's separate `Attribute` / `RangedAttribute` hierarchy, we
75/// fold min/max directly into the struct since every attribute is ranged
76#[derive(Debug)]
77pub struct Attribute {
78    pub key: Identifier,
79    pub translation_key: &'static str,
80    pub default_value: f64,
81    pub min_value: f64,
82    pub max_value: f64,
83    pub syncable: bool,
84}
85
86impl Attribute {
87    /// Clamps a value to this attribute's valid range
88    #[must_use]
89    pub const fn sanitize_value(&self, value: f64) -> f64 {
90        value.clamp(self.min_value, self.max_value)
91    }
92}
93
94pub type AttributeRef = &'static Attribute;
95
96pub struct AttributeRegistry {
97    attributes_by_id: Vec<AttributeRef>,
98    attributes_by_key: FxHashMap<Identifier, usize>,
99    tags: RegistryTags,
100    allows_registering: bool,
101}
102
103impl Default for AttributeRegistry {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109impl AttributeRegistry {
110    #[must_use]
111    pub fn new() -> Self {
112        Self {
113            attributes_by_id: Vec::new(),
114            attributes_by_key: FxHashMap::default(),
115            tags: RegistryTags::default(),
116            allows_registering: true,
117        }
118    }
119
120    /// Registers a new attribute
121    pub fn register(&mut self, attribute: AttributeRef) {
122        assert!(
123            self.allows_registering,
124            "Cannot register attributes after the registry has been frozen"
125        );
126        let idx = self.attributes_by_id.len();
127        self.attributes_by_key.insert(attribute.key.clone(), idx);
128        self.attributes_by_id.push(attribute);
129    }
130
131    /// Replaces an attribute at a given index
132    #[must_use]
133    pub fn replace(&mut self, attribute: AttributeRef, id: usize) -> bool {
134        if id >= self.attributes_by_id.len() {
135            return false;
136        }
137        self.attributes_by_id[id] = attribute;
138        true
139    }
140
141    pub fn iter(&self) -> impl Iterator<Item = (usize, AttributeRef)> + '_ {
142        self.attributes_by_id
143            .iter()
144            .enumerate()
145            .map(|(id, &attr)| (id, attr))
146    }
147}
148
149crate::impl_registry!(
150    AttributeRegistry,
151    Attribute,
152    attributes_by_id,
153    attributes_by_key,
154    attributes
155);
156crate::impl_tagged_registry!(AttributeRegistry, attributes_by_key, "attribute");