valence_equipment/
inventory_sync.rs

1use valence_inventory::player_inventory::PlayerInventory;
2use valence_inventory::{HeldItem, Inventory, UpdateSelectedSlotEvent};
3use valence_server::entity::player::PlayerEntity;
4
5use super::*;
6
7/// This component will sync a player's [`Equipment`], which is visible to other
8/// players, with the player [`Inventory`].
9#[derive(Debug, Default, Clone, Component)]
10pub struct EquipmentInventorySync;
11
12/// Syncs the player [`Equipment`] with the [`Inventory`].
13/// If a change in the player's inventory and in the equipment occurs in the
14/// same tick, the equipment change has priority.
15/// Note: This system only handles direct changes to the held item (not actual
16/// changes from the client) see [`equipment_held_item_sync_from_client`]
17pub(crate) fn equipment_inventory_sync(
18    mut clients: Query<
19        (&mut Equipment, &mut Inventory, &mut HeldItem),
20        (
21            Or<(Changed<Equipment>, Changed<Inventory>, Changed<HeldItem>)>,
22            With<EquipmentInventorySync>,
23            With<PlayerEntity>,
24        ),
25    >,
26) {
27    for (mut equipment, mut inventory, held_item) in &mut clients {
28        // Equipment change has priority over held item changes
29        if equipment.changed & (1 << Equipment::MAIN_HAND_IDX) != 0 {
30            let item = equipment.main_hand().clone();
31            inventory.set_slot(held_item.slot(), item);
32        } else {
33            // If we change the inventory (e.g by pickung up an item)
34            // then the HeldItem slot wont be changed
35
36            // This will only be called if we change the held item from valence,
37            // the client change is handled in `equipment_held_item_sync_from_client`
38            let item = inventory.slot(held_item.slot()).clone();
39            equipment.set_main_hand(item);
40        }
41
42        let slots = [
43            (Equipment::OFF_HAND_IDX, PlayerInventory::SLOT_OFFHAND),
44            (Equipment::HEAD_IDX, PlayerInventory::SLOT_HEAD),
45            (Equipment::CHEST_IDX, PlayerInventory::SLOT_CHEST),
46            (Equipment::LEGS_IDX, PlayerInventory::SLOT_LEGS),
47            (Equipment::FEET_IDX, PlayerInventory::SLOT_FEET),
48        ];
49
50        // We cant rely on the changed bitfield of inventory here
51        // because that gets reset when the client changes the inventory
52
53        for (equipment_slot, inventory_slot) in slots {
54            // Equipment has priority over inventory changes
55            if equipment.changed & (1 << equipment_slot) != 0 {
56                let item = equipment.slot(equipment_slot).clone();
57                inventory.set_slot(inventory_slot, item);
58            } else if inventory.is_changed() {
59                let item = inventory.slot(inventory_slot).clone();
60                equipment.set_slot(equipment_slot, item);
61            }
62        }
63    }
64}
65
66/// Handles the case where the client changes the slot (the bevy change is
67/// suppressed for this)
68pub(crate) fn equipment_held_item_sync_from_client(
69    mut clients: Query<(&HeldItem, &Inventory, &mut Equipment), With<EquipmentInventorySync>>,
70    mut events: EventReader<UpdateSelectedSlotEvent>,
71) {
72    for event in events.read() {
73        let Ok((held_item, inventory, mut equipment)) = clients.get_mut(event.client) else {
74            continue;
75        };
76
77        let item = inventory.slot(held_item.slot()).clone();
78        equipment.set_main_hand(item);
79    }
80}
81
82pub(crate) fn on_attach_inventory_sync(
83    entities: Query<Option<&PlayerEntity>, (Added<EquipmentInventorySync>, With<Inventory>)>,
84) {
85    for entity in &entities {
86        if entity.is_none() {
87            tracing::warn!(
88                "EquipmentInventorySync attached to non-player entity, this will have no effect"
89            );
90        }
91    }
92}