Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ default = [
"advancement",
"anvil",
"boss_bar",
"equipment",
"inventory",
"log",
"network",
Expand All @@ -32,6 +33,7 @@ default = [
advancement = ["dep:valence_advancement"]
anvil = ["dep:valence_anvil"]
boss_bar = ["dep:valence_boss_bar"]
equipment = ["dep:valence_equipment"]
inventory = ["dep:valence_inventory"]
log = ["dep:bevy_log"]
network = ["dep:valence_network"]
Expand Down Expand Up @@ -59,6 +61,7 @@ valence_command = { workspace = true, optional = true }
valence_command_macros = { workspace = true, optional = true }
valence_ident_macros.workspace = true
valence_ident.workspace = true
valence_equipment = { workspace = true, optional = true }
valence_inventory = { workspace = true, optional = true }
valence_lang.workspace = true
valence_network = { workspace = true, optional = true }
Expand Down Expand Up @@ -192,6 +195,7 @@ valence_entity = { path = "crates/valence_entity", version = "0.2.0-alpha.1" }
valence_generated = { path = "crates/valence_generated", version = "0.2.0-alpha.1" }
valence_ident = { path = "crates/valence_ident", version = "0.2.0-alpha.1" }
valence_ident_macros = { path = "crates/valence_ident_macros", version = "0.2.0-alpha.1" }
valence_equipment = { path = "crates/valence_equipment", version = "0.2.0-alpha.1" }
valence_inventory = { path = "crates/valence_inventory", version = "0.2.0-alpha.1" }
valence_lang = { path = "crates/valence_lang", version = "0.2.0-alpha.1" }
valence_math = { path = "crates/valence_math", version = "0.2.0-alpha.1" }
Expand Down
362 changes: 187 additions & 175 deletions assets/depgraph.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions crates/valence_equipment/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "valence_equipment"
description = "Equipment support for Valence"
readme = "README.md"
version.workspace = true
edition.workspace = true
repository.workspace = true
documentation.workspace = true
license.workspace = true

[lints]
workspace = true

[dependencies]
bevy_app.workspace = true
bevy_ecs.workspace = true
derive_more.workspace = true
tracing.workspace = true
valence_server.workspace = true
valence_inventory.workspace = true
41 changes: 41 additions & 0 deletions crates/valence_equipment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# `valence_equipment`
Manages Minecraft's entity equipment (armor, held items) via the `Equipment` component.
By default this is separated from an entities `Inventory` (which means that changes are only visible to other players), but it can be synced by attaching the `EquipmentInventorySync`
component to a entity (currently only Players).

## Example

```rust
use bevy_ecs::prelude::*;
use valence_equipment::*;
use valence_server::{
ItemStack, ItemKind,
entity::player::PlayerEntity,
};
// Add equipment to players when they are added to the world.
fn init_equipment(
mut clients: Query<
&mut Equipment,
(
Added<Equipment>,
With<PlayerEntity>,
),
>,
) {
for mut equipment in &mut clients
{
equipment.set_main_hand(ItemStack::new(ItemKind::DiamondSword, 1, None));
equipment.set_off_hand(ItemStack::new(ItemKind::Shield, 1, None));
equipment.set_feet(ItemStack::new(ItemKind::DiamondBoots, 1, None));
equipment.set_legs(ItemStack::new(ItemKind::DiamondLeggings, 1, None));
equipment.set_chest(ItemStack::new(ItemKind::DiamondChestplate, 1, None));
equipment.set_head(ItemStack::new(ItemKind::DiamondHelmet, 1, None));
}
}
```

### See also

Examples related to inventories in the `valence/examples/` directory:
- `equipment`

92 changes: 92 additions & 0 deletions crates/valence_equipment/src/inventory_sync.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use valence_inventory::player_inventory::PlayerInventory;
use valence_inventory::{HeldItem, Inventory, UpdateSelectedSlotEvent};
use valence_server::entity::player::PlayerEntity;

use super::*;

/// This component will sync a player's [`Equipment`], which is visible to other
/// players, with the player [`Inventory`].
#[derive(Debug, Default, Clone, Component)]
pub struct EquipmentInventorySync;
Comment thread
maxomatic458 marked this conversation as resolved.

/// Syncs the player [`Equipment`] with the [`Inventory`].
/// If a change in the player's inventory and in the equipment occurs in the
/// same tick, the equipment change has priority.
/// Note: This system only handles direct changes to the held item (not actual
/// changes from the client) see [`equipment_held_item_sync_from_client`]
pub(crate) fn equipment_inventory_sync(
mut clients: Query<
(&mut Equipment, &mut Inventory, &mut HeldItem),
(
Or<(Changed<Equipment>, Changed<Inventory>, Changed<HeldItem>)>,
With<EquipmentInventorySync>,
With<PlayerEntity>,
),
>,
) {
for (mut equipment, mut inventory, held_item) in &mut clients {
// Equipment change has priority over held item changes
if equipment.changed & (1 << Equipment::MAIN_HAND_IDX) != 0 {
let item = equipment.main_hand().clone();
inventory.set_slot(held_item.slot(), item);
} else {
// If we change the inventory (e.g by pickung up an item)
// then the HeldItem slot wont be changed

// This will only be called if we change the held item from valence,
// the client change is handled in `equipment_held_item_sync_from_client`
let item = inventory.slot(held_item.slot()).clone();
equipment.set_main_hand(item);
}

let slots = [
(Equipment::OFF_HAND_IDX, PlayerInventory::SLOT_OFFHAND),
(Equipment::HEAD_IDX, PlayerInventory::SLOT_HEAD),
(Equipment::CHEST_IDX, PlayerInventory::SLOT_CHEST),
(Equipment::LEGS_IDX, PlayerInventory::SLOT_LEGS),
(Equipment::FEET_IDX, PlayerInventory::SLOT_FEET),
];

// We cant rely on the changed bitfield of inventory here
// because that gets reset when the client changes the inventory

for (equipment_slot, inventory_slot) in slots {
// Equipment has priority over inventory changes
if equipment.changed & (1 << equipment_slot) != 0 {
let item = equipment.slot(equipment_slot).clone();
inventory.set_slot(inventory_slot, item);
} else if inventory.is_changed() {
let item = inventory.slot(inventory_slot).clone();
equipment.set_slot(equipment_slot, item);
}
}
}
}

/// Handles the case where the client changes the slot (the bevy change is
/// suppressed for this)
pub(crate) fn equipment_held_item_sync_from_client(
mut clients: Query<(&HeldItem, &Inventory, &mut Equipment), With<EquipmentInventorySync>>,
mut events: EventReader<UpdateSelectedSlotEvent>,
) {
for event in events.read() {
let Ok((held_item, inventory, mut equipment)) = clients.get_mut(event.client) else {
continue;
};

let item = inventory.slot(held_item.slot()).clone();
equipment.set_main_hand(item);
}
}

pub(crate) fn on_attach_inventory_sync(
entities: Query<Option<&PlayerEntity>, (Added<EquipmentInventorySync>, With<Inventory>)>,
) {
for entity in &entities {
if entity.is_none() {
tracing::warn!(
"EquipmentInventorySync attached to non-player entity, this will have no effect"
);
}
}
}
Loading