Skip to main content

steel_registry/
equipment.rs

1//! Shared equipment slot definitions.
2
3use crate::item_stack::ItemStack;
4use steel_utils::entity_events::EntityStatus;
5
6/// Equipment slot types for categorization.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum EquipmentSlotType {
9    /// Hand slots (main hand, off hand).
10    Hand,
11    /// Humanoid armor slots (head, chest, legs, feet).
12    HumanoidArmor,
13    /// Animal armor slot (body).
14    AnimalArmor,
15    /// Saddle slot.
16    Saddle,
17}
18
19/// Equipment slots for entities.
20///
21/// Based on Minecraft's `EquipmentSlot` enum.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum EquipmentSlot {
24    /// The main hand slot.
25    MainHand,
26    /// The off hand slot.
27    OffHand,
28    /// The feet armor slot (boots).
29    Feet,
30    /// The legs armor slot (leggings).
31    Legs,
32    /// The chest armor slot (chestplate).
33    Chest,
34    /// The head armor slot (helmet).
35    Head,
36    /// The body armor slot (for animals like horses).
37    Body,
38    /// The saddle slot (for rideable animals).
39    Saddle,
40}
41
42impl EquipmentSlot {
43    /// All equipment slots in order.
44    pub const ALL: [EquipmentSlot; 8] = [
45        EquipmentSlot::MainHand,
46        EquipmentSlot::OffHand,
47        EquipmentSlot::Feet,
48        EquipmentSlot::Legs,
49        EquipmentSlot::Chest,
50        EquipmentSlot::Head,
51        EquipmentSlot::Body,
52        EquipmentSlot::Saddle,
53    ];
54
55    /// Humanoid armor slots (head, chest, legs, feet).
56    pub const ARMOR_SLOTS: [EquipmentSlot; 4] = [
57        EquipmentSlot::Head,
58        EquipmentSlot::Chest,
59        EquipmentSlot::Legs,
60        EquipmentSlot::Feet,
61    ];
62
63    /// Returns the slot type for this equipment slot.
64    #[must_use]
65    pub const fn slot_type(self) -> EquipmentSlotType {
66        match self {
67            EquipmentSlot::MainHand | EquipmentSlot::OffHand => EquipmentSlotType::Hand,
68            EquipmentSlot::Feet
69            | EquipmentSlot::Legs
70            | EquipmentSlot::Chest
71            | EquipmentSlot::Head => EquipmentSlotType::HumanoidArmor,
72            EquipmentSlot::Body => EquipmentSlotType::AnimalArmor,
73            EquipmentSlot::Saddle => EquipmentSlotType::Saddle,
74        }
75    }
76
77    /// Returns vanilla `EquipmentSlot.canIncreaseExperience`.
78    #[must_use]
79    pub const fn can_increase_experience(self) -> bool {
80        !matches!(self.slot_type(), EquipmentSlotType::Saddle)
81    }
82
83    /// Returns the index of this slot for array storage (0-7).
84    #[must_use]
85    pub const fn index(self) -> usize {
86        match self {
87            EquipmentSlot::MainHand => 0,
88            EquipmentSlot::OffHand => 1,
89            EquipmentSlot::Feet => 2,
90            EquipmentSlot::Legs => 3,
91            EquipmentSlot::Chest => 4,
92            EquipmentSlot::Head => 5,
93            EquipmentSlot::Body => 6,
94            EquipmentSlot::Saddle => 7,
95        }
96    }
97
98    /// Returns vanilla's protocol ID for this slot.
99    #[must_use]
100    pub const fn id(self) -> i32 {
101        match self {
102            EquipmentSlot::MainHand => 0,
103            EquipmentSlot::OffHand => 5,
104            EquipmentSlot::Feet => 1,
105            EquipmentSlot::Legs => 2,
106            EquipmentSlot::Chest => 3,
107            EquipmentSlot::Head => 4,
108            EquipmentSlot::Body => 6,
109            EquipmentSlot::Saddle => 7,
110        }
111    }
112
113    /// Returns the equipment slot with the given vanilla protocol ID.
114    #[must_use]
115    pub const fn by_id(id: i32) -> Self {
116        match id {
117            1 => EquipmentSlot::Feet,
118            2 => EquipmentSlot::Legs,
119            3 => EquipmentSlot::Chest,
120            4 => EquipmentSlot::Head,
121            5 => EquipmentSlot::OffHand,
122            6 => EquipmentSlot::Body,
123            7 => EquipmentSlot::Saddle,
124            _ => EquipmentSlot::MainHand,
125        }
126    }
127
128    /// Returns vanilla `EquipmentSlot.countLimit`: the maximum stack size this
129    /// slot will hold, or `0` for no limit. Hand slots are unlimited; armor,
130    /// body, and saddle slots hold a single item.
131    #[must_use]
132    pub const fn count_limit(self) -> i32 {
133        match self.slot_type() {
134            EquipmentSlotType::Hand => 0,
135            _ => 1,
136        }
137    }
138
139    /// Returns vanilla `EquipmentSlot.limit`: splits `to_equip` down to this
140    /// slot's [`count_limit`](Self::count_limit), leaving the remainder in place.
141    /// An unlimited slot takes the whole stack.
142    pub fn limit(self, to_equip: &mut ItemStack) -> ItemStack {
143        let count_limit = self.count_limit();
144        if count_limit > 0 {
145            to_equip.split(count_limit)
146        } else {
147            to_equip.split(to_equip.count())
148        }
149    }
150
151    /// Returns true if this is an armor slot (humanoid or animal).
152    #[must_use]
153    pub const fn is_armor(self) -> bool {
154        matches!(
155            self.slot_type(),
156            EquipmentSlotType::HumanoidArmor | EquipmentSlotType::AnimalArmor
157        )
158    }
159
160    /// Returns the equipment slot with the given name, or None if not found.
161    #[must_use]
162    pub const fn by_name(name: &str) -> Option<Self> {
163        match name {
164            "mainhand" => Some(EquipmentSlot::MainHand),
165            "offhand" => Some(EquipmentSlot::OffHand),
166            "feet" => Some(EquipmentSlot::Feet),
167            "legs" => Some(EquipmentSlot::Legs),
168            "chest" => Some(EquipmentSlot::Chest),
169            "head" => Some(EquipmentSlot::Head),
170            "body" => Some(EquipmentSlot::Body),
171            "saddle" => Some(EquipmentSlot::Saddle),
172            _ => None,
173        }
174    }
175
176    /// Returns the name of this equipment slot.
177    #[must_use]
178    pub const fn name(self) -> &'static str {
179        match self {
180            EquipmentSlot::MainHand => "mainhand",
181            EquipmentSlot::OffHand => "offhand",
182            EquipmentSlot::Feet => "feet",
183            EquipmentSlot::Legs => "legs",
184            EquipmentSlot::Chest => "chest",
185            EquipmentSlot::Head => "head",
186            EquipmentSlot::Body => "body",
187            EquipmentSlot::Saddle => "saddle",
188        }
189    }
190}
191
192impl From<EquipmentSlot> for EntityStatus {
193    fn from(value: EquipmentSlot) -> Self {
194        match value {
195            EquipmentSlot::MainHand => EntityStatus::MainhandBreak,
196            EquipmentSlot::OffHand => EntityStatus::OffhandBreak,
197            EquipmentSlot::Head => EntityStatus::HeadBreak,
198            EquipmentSlot::Chest => EntityStatus::ChestBreak,
199            EquipmentSlot::Legs => EntityStatus::LegsBreak,
200            EquipmentSlot::Feet => EntityStatus::FeetBreak,
201            EquipmentSlot::Body => EntityStatus::BodyBreak,
202            EquipmentSlot::Saddle => EntityStatus::SaddleBreak,
203        }
204    }
205}
206
207/// Equipment slot groups used by vanilla item attributes, loot, and enchantments.
208///
209/// Vanilla's `EquipmentSlotGroup` is a predicate over concrete equipment slots:
210/// `Hand` matches both hand slots, `Armor` matches humanoid and animal armor,
211/// and `Any` matches every slot.
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
213pub enum EquipmentSlotGroup {
214    Any,
215    MainHand,
216    OffHand,
217    Hand,
218    Feet,
219    Legs,
220    Chest,
221    Head,
222    Armor,
223    Body,
224    Saddle,
225}
226
227impl EquipmentSlotGroup {
228    #[must_use]
229    pub const fn id(self) -> i32 {
230        match self {
231            Self::Any => 0,
232            Self::MainHand => 1,
233            Self::OffHand => 2,
234            Self::Hand => 3,
235            Self::Feet => 4,
236            Self::Legs => 5,
237            Self::Chest => 6,
238            Self::Head => 7,
239            Self::Armor => 8,
240            Self::Body => 9,
241            Self::Saddle => 10,
242        }
243    }
244
245    #[must_use]
246    pub const fn by_id(id: i32) -> Self {
247        match id {
248            1 => Self::MainHand,
249            2 => Self::OffHand,
250            3 => Self::Hand,
251            4 => Self::Feet,
252            5 => Self::Legs,
253            6 => Self::Chest,
254            7 => Self::Head,
255            8 => Self::Armor,
256            9 => Self::Body,
257            10 => Self::Saddle,
258            _ => Self::Any,
259        }
260    }
261
262    #[must_use]
263    pub const fn by_name(name: &str) -> Option<Self> {
264        match name {
265            "any" => Some(Self::Any),
266            "mainhand" | "main_hand" => Some(Self::MainHand),
267            "offhand" | "off_hand" => Some(Self::OffHand),
268            "hand" => Some(Self::Hand),
269            "feet" => Some(Self::Feet),
270            "legs" => Some(Self::Legs),
271            "chest" => Some(Self::Chest),
272            "head" => Some(Self::Head),
273            "armor" => Some(Self::Armor),
274            "body" => Some(Self::Body),
275            "saddle" => Some(Self::Saddle),
276            _ => None,
277        }
278    }
279
280    #[must_use]
281    pub const fn name(self) -> &'static str {
282        match self {
283            Self::Any => "any",
284            Self::MainHand => "mainhand",
285            Self::OffHand => "offhand",
286            Self::Hand => "hand",
287            Self::Feet => "feet",
288            Self::Legs => "legs",
289            Self::Chest => "chest",
290            Self::Head => "head",
291            Self::Armor => "armor",
292            Self::Body => "body",
293            Self::Saddle => "saddle",
294        }
295    }
296
297    #[must_use]
298    pub const fn as_str(self) -> &'static str {
299        self.name()
300    }
301
302    #[must_use]
303    pub const fn by_slot(slot: EquipmentSlot) -> Self {
304        match slot {
305            EquipmentSlot::MainHand => Self::MainHand,
306            EquipmentSlot::OffHand => Self::OffHand,
307            EquipmentSlot::Feet => Self::Feet,
308            EquipmentSlot::Legs => Self::Legs,
309            EquipmentSlot::Chest => Self::Chest,
310            EquipmentSlot::Head => Self::Head,
311            EquipmentSlot::Body => Self::Body,
312            EquipmentSlot::Saddle => Self::Saddle,
313        }
314    }
315
316    #[must_use]
317    pub const fn test(self, slot: EquipmentSlot) -> bool {
318        match self {
319            Self::Any => true,
320            Self::MainHand => matches!(slot, EquipmentSlot::MainHand),
321            Self::OffHand => matches!(slot, EquipmentSlot::OffHand),
322            Self::Hand => matches!(slot.slot_type(), EquipmentSlotType::Hand),
323            Self::Feet => matches!(slot, EquipmentSlot::Feet),
324            Self::Legs => matches!(slot, EquipmentSlot::Legs),
325            Self::Chest => matches!(slot, EquipmentSlot::Chest),
326            Self::Head => matches!(slot, EquipmentSlot::Head),
327            Self::Armor => slot.is_armor(),
328            Self::Body => matches!(slot, EquipmentSlot::Body),
329            Self::Saddle => matches!(slot, EquipmentSlot::Saddle),
330        }
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::EquipmentSlot;
337
338    #[test]
339    fn equipment_slot_protocol_ids_match_vanilla() {
340        let slots = [
341            (EquipmentSlot::MainHand, 0),
342            (EquipmentSlot::Feet, 1),
343            (EquipmentSlot::Legs, 2),
344            (EquipmentSlot::Chest, 3),
345            (EquipmentSlot::Head, 4),
346            (EquipmentSlot::OffHand, 5),
347            (EquipmentSlot::Body, 6),
348            (EquipmentSlot::Saddle, 7),
349        ];
350
351        for (slot, id) in slots {
352            assert_eq!(slot.id(), id);
353            assert_eq!(EquipmentSlot::by_id(id), slot);
354        }
355        assert_eq!(EquipmentSlot::by_id(-1), EquipmentSlot::MainHand);
356        assert_eq!(EquipmentSlot::by_id(8), EquipmentSlot::MainHand);
357    }
358
359    #[test]
360    fn only_saddle_slot_does_not_increase_experience() {
361        for slot in EquipmentSlot::ALL {
362            assert_eq!(
363                slot.can_increase_experience(),
364                slot != EquipmentSlot::Saddle
365            );
366        }
367    }
368}