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