Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Equipment support #663

Merged
merged 24 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 20 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
maxomatic458 marked this conversation as resolved.
Show resolved Hide resolved
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`

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

use super::*;
#[derive(Debug, Default, Clone, Component)]
pub struct EquipmentInventorySync;
maxomatic458 marked this conversation as resolved.
Show resolved Hide 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
Loading