chunkedge_equipment/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use bevy_app::prelude::*;
4use bevy_ecs::prelude::*;
5mod interaction_broadcast;
6pub use interaction_broadcast::EquipmentInteractionBroadcast;
7mod inventory_sync;
8use chunkedge_server::client::{Client, FlushPacketsSet, LoadEntityForClientEvent};
9use chunkedge_server::entity::living::LivingEntity;
10use chunkedge_server::entity::{EntityId, EntityLayerId, Position};
11use chunkedge_server::protocol::packets::play::set_equipment_s2c::{EquipmentEntry, EquipmentSlot};
12use chunkedge_server::protocol::packets::play::SetEquipmentS2c;
13use chunkedge_server::protocol::WritePacket;
14use chunkedge_server::{EntityLayer, ItemStack, Layer};
15pub use inventory_sync::EquipmentInventorySync;
16
17pub struct EquipmentPlugin;
18
19impl Plugin for EquipmentPlugin {
20    fn build(&self, app: &mut App) {
21        app.add_systems(
22            PreUpdate,
23            (
24                on_entity_init,
25                interaction_broadcast::start_interaction,
26                interaction_broadcast::stop_interaction,
27                inventory_sync::on_attach_inventory_sync,
28                inventory_sync::equipment_inventory_sync,
29                inventory_sync::equipment_held_item_sync_from_client,
30            ),
31        )
32        .add_systems(
33            PostUpdate,
34            (
35                update_equipment.before(FlushPacketsSet),
36                on_entity_load.before(FlushPacketsSet),
37            ),
38        )
39        .add_event::<EquipmentChangeEvent>();
40    }
41}
42
43/// Contains the visible equipment of a [`LivingEntity`], such as armor and held
44/// items. By default this is not synced with a player's
45/// [`chunkedge_inventory::Inventory`], so the armor the player has equipped in
46/// their inventory, will not be visible by other players. You would have to
47/// change the equipment in this component here or attach the
48/// [`EquipmentInventorySync`] component to the player entity to sync the
49/// equipment with the inventory.
50#[derive(Debug, Default, Clone, Component)]
51pub struct Equipment {
52    equipment: [ItemStack; Self::SLOT_COUNT],
53    /// Contains a set bit for each modified slot in `slots`.
54    #[doc(hidden)]
55    pub(crate) changed: u8,
56}
57
58#[allow(clippy::large_stack_arrays)] // About 10kb but I think its worth it.
59impl Equipment {
60    pub const SLOT_COUNT: usize = EquipmentSlot::number_of_members();
61
62    #[allow(clippy::too_many_arguments)]
63    pub fn new(
64        main_hand: ItemStack,
65        off_hand: ItemStack,
66        boots: ItemStack,
67        leggings: ItemStack,
68        chestplate: ItemStack,
69        helmet: ItemStack,
70        body: ItemStack,
71        saddle: ItemStack,
72    ) -> Self {
73        let equipment = [
74            main_hand, off_hand, boots, leggings, chestplate, helmet, body, saddle,
75        ];
76        Self {
77            equipment,
78            changed: 0,
79        }
80    }
81
82    pub fn new_empty() -> Self {
83        let equipment = [ItemStack::EMPTY; 8];
84        Self {
85            equipment,
86            changed: 0,
87        }
88    }
89
90    pub fn slot(&self, idx: EquipmentSlot) -> &ItemStack {
91        &self.equipment[idx as usize]
92    }
93
94    pub fn set_slot(&mut self, idx: EquipmentSlot, item: ItemStack) {
95        if self.equipment[idx as usize] != item {
96            self.equipment[idx as usize] = item;
97            self.changed |= 1 << idx as u8;
98        }
99    }
100
101    pub fn main_hand(&self) -> &ItemStack {
102        self.slot(EquipmentSlot::MainHand)
103    }
104
105    pub fn off_hand(&self) -> &ItemStack {
106        self.slot(EquipmentSlot::OffHand)
107    }
108
109    pub fn feet(&self) -> &ItemStack {
110        self.slot(EquipmentSlot::Boots)
111    }
112
113    pub fn legs(&self) -> &ItemStack {
114        self.slot(EquipmentSlot::Leggings)
115    }
116
117    pub fn chest(&self) -> &ItemStack {
118        self.slot(EquipmentSlot::Chestplate)
119    }
120
121    pub fn head(&self) -> &ItemStack {
122        self.slot(EquipmentSlot::Helmet)
123    }
124
125    pub fn body(&self) -> &ItemStack {
126        self.slot(EquipmentSlot::Body)
127    }
128
129    pub fn saddle(&self) -> &ItemStack {
130        self.slot(EquipmentSlot::Saddle)
131    }
132
133    pub fn set_main_hand(&mut self, item: ItemStack) {
134        self.set_slot(EquipmentSlot::MainHand, item);
135    }
136
137    pub fn set_off_hand(&mut self, item: ItemStack) {
138        self.set_slot(EquipmentSlot::OffHand, item);
139    }
140
141    pub fn set_feet(&mut self, item: ItemStack) {
142        self.set_slot(EquipmentSlot::Boots, item);
143    }
144
145    pub fn set_legs(&mut self, item: ItemStack) {
146        self.set_slot(EquipmentSlot::Leggings, item);
147    }
148
149    pub fn set_chest(&mut self, item: ItemStack) {
150        self.set_slot(EquipmentSlot::Chestplate, item);
151    }
152
153    pub fn set_head(&mut self, item: ItemStack) {
154        self.set_slot(EquipmentSlot::Helmet, item);
155    }
156
157    pub fn set_body(&mut self, item: ItemStack) {
158        self.set_slot(EquipmentSlot::Body, item);
159    }
160
161    pub fn set_saddle(&mut self, item: ItemStack) {
162        self.set_slot(EquipmentSlot::Saddle, item);
163    }
164
165    pub fn clear(&mut self) {
166        self.equipment
167            .iter_mut()
168            .for_each(|itm| *itm = ItemStack::EMPTY);
169        self.changed = u8::MAX
170    }
171
172    pub fn is_default(&self) -> bool {
173        self.equipment.iter().all(|item| item.is_empty())
174    }
175}
176
177#[derive(Debug, Clone)]
178pub struct EquipmentSlotChange {
179    idx: u8,
180    stack: ItemStack,
181}
182
183#[derive(Debug, Clone, Event)]
184pub struct EquipmentChangeEvent {
185    pub client: Entity,
186    pub changed: Vec<EquipmentSlotChange>,
187}
188
189fn update_equipment(
190    mut clients: Query<
191        (Entity, &EntityId, &EntityLayerId, &Position, &mut Equipment),
192        Changed<Equipment>,
193    >,
194    mut event_writer: EventWriter<EquipmentChangeEvent>,
195    mut entity_layer: Query<&mut EntityLayer>,
196) {
197    for (entity, entity_id, entity_layer_id, position, mut equipment) in &mut clients {
198        let Ok(mut entity_layer) = entity_layer.get_mut(entity_layer_id.0) else {
199            continue;
200        };
201
202        if equipment.changed != 0 {
203            let mut slots_changed: Vec<EquipmentSlotChange> =
204                Vec::with_capacity(Equipment::SLOT_COUNT);
205
206            for slot in 0..Equipment::SLOT_COUNT {
207                if equipment.changed & (1 << slot) != 0 {
208                    slots_changed.push(EquipmentSlotChange {
209                        idx: slot as u8,
210                        stack: equipment.equipment[slot].clone(),
211                    });
212                }
213            }
214
215            entity_layer
216                .view_except_writer(position.0, entity)
217                .write_packet(&SetEquipmentS2c {
218                    entity_id: entity_id.get().into(),
219                    equipment: slots_changed
220                        .iter()
221                        .map(|change| EquipmentEntry {
222                            slot: change.idx.into(),
223                            item: change.stack.clone(),
224                        })
225                        .collect(),
226                });
227
228            event_writer.send(EquipmentChangeEvent {
229                client: entity,
230                changed: slots_changed,
231            });
232
233            equipment.changed = 0;
234        }
235    }
236}
237
238/// Gets called when the player loads an entity, for example
239/// when the player gets in range of the entity.
240fn on_entity_load(
241    mut clients: Query<&mut Client>,
242    entities: Query<(&EntityId, &Equipment)>,
243    mut events: EventReader<LoadEntityForClientEvent>,
244) {
245    for event in events.read() {
246        let Ok(mut client) = clients.get_mut(event.client) else {
247            continue;
248        };
249
250        let Ok((entity_id, equipment)) = entities.get(event.entity_loaded) else {
251            continue;
252        };
253
254        if equipment.is_default() {
255            continue;
256        }
257
258        let mut entries: Vec<EquipmentEntry> = Vec::with_capacity(Equipment::SLOT_COUNT);
259        for (idx, stack) in equipment.equipment.iter().enumerate() {
260            entries.push(EquipmentEntry {
261                slot: (idx as u8).into(),
262                item: stack.clone(),
263            });
264        }
265
266        client.write_packet(&SetEquipmentS2c {
267            entity_id: entity_id.get().into(),
268            equipment: entries,
269        });
270    }
271}
272
273/// Add a default equipment component to all living entities when they are
274/// initialized.
275fn on_entity_init(
276    mut commands: Commands,
277    mut entities: Query<Entity, (Added<LivingEntity>, Without<Equipment>)>,
278) {
279    for entity in &mut entities {
280        commands.entity(entity).insert(Equipment::default());
281    }
282}