steel_protocol/packets/game/
c_set_equipment.rs1use std::io::{Error, ErrorKind, Result, Write};
4
5use steel_macros::ClientPacket;
6use steel_registry::{
7 equipment::EquipmentSlot, item_stack::ItemStack, packets::play::C_SET_EQUIPMENT,
8};
9use steel_utils::{codec::VarInt, serial::WriteTo};
10
11const CONTINUE_MASK: u8 = 0x80;
12
13const fn vanilla_equipment_slot_id(slot: EquipmentSlot) -> u8 {
14 match slot {
15 EquipmentSlot::MainHand => 0,
16 EquipmentSlot::OffHand => 1,
17 EquipmentSlot::Feet => 2,
18 EquipmentSlot::Legs => 3,
19 EquipmentSlot::Chest => 4,
20 EquipmentSlot::Head => 5,
21 EquipmentSlot::Body => 6,
22 EquipmentSlot::Saddle => 7,
23 }
24}
25
26const fn equipment_slot_packet_id(slot: EquipmentSlot, has_next: bool) -> u8 {
27 if has_next {
28 vanilla_equipment_slot_id(slot) | CONTINUE_MASK
29 } else {
30 vanilla_equipment_slot_id(slot)
31 }
32}
33
34#[derive(Clone, Debug, PartialEq)]
36pub struct EquipmentSlotItem {
37 pub slot: EquipmentSlot,
39 pub item_stack: ItemStack,
41}
42
43#[derive(ClientPacket, Clone, Debug)]
45#[packet_id(Play = C_SET_EQUIPMENT)]
46pub struct CSetEquipment {
47 pub entity_id: i32,
49 pub slots: Vec<EquipmentSlotItem>,
51}
52
53impl CSetEquipment {
54 #[must_use]
56 pub const fn new(entity_id: i32, slots: Vec<EquipmentSlotItem>) -> Self {
57 Self { entity_id, slots }
58 }
59}
60
61impl WriteTo for CSetEquipment {
62 fn write(&self, writer: &mut impl Write) -> Result<()> {
63 if self.slots.is_empty() {
64 return Err(Error::new(
65 ErrorKind::InvalidInput,
66 "CSetEquipment requires at least one slot",
67 ));
68 }
69 VarInt(self.entity_id).write(writer)?;
70 let last_index = self.slots.len().saturating_sub(1);
71 for (index, slot_item) in self.slots.iter().enumerate() {
72 writer.write_all(&[equipment_slot_packet_id(
73 slot_item.slot,
74 index != last_index,
75 )])?;
76 slot_item.item_stack.write(writer)?;
77 }
78 Ok(())
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn equipment_packet_uses_vanilla_continue_bit() {
88 let packet = CSetEquipment::new(
89 42,
90 vec![
91 EquipmentSlotItem {
92 slot: EquipmentSlot::MainHand,
93 item_stack: ItemStack::empty(),
94 },
95 EquipmentSlotItem {
96 slot: EquipmentSlot::Head,
97 item_stack: ItemStack::empty(),
98 },
99 ],
100 );
101 let mut bytes = Vec::new();
102
103 packet.write(&mut bytes).expect("packet should encode");
104
105 assert_eq!(bytes, vec![42, 0x80, 0, 5, 0]);
106 }
107
108 #[test]
109 fn equipment_packet_rejects_empty_slot_updates() {
110 let packet = CSetEquipment::new(42, Vec::new());
111
112 let error = packet.write(&mut Vec::new()).unwrap_err();
113
114 assert_eq!(error.kind(), std::io::ErrorKind::InvalidInput);
115 }
116}