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

Entity equipment #495

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 20 additions & 2 deletions feather/protocol/src/packets/server/play.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ pub use chunk_data::{ChunkData, ChunkDataKind};
use quill_common::components::PreviousGamemode;
pub use update_light::UpdateLight;

use crate::{io::VarLong, Readable, Writeable};
use crate::{
io::VarLong, packets::server::EquipmentSlot::MainHand, InventorySlot::Empty, Readable,
Writeable,
};

use super::*;

Expand Down Expand Up @@ -947,7 +950,22 @@ packets! {
#[derive(Debug, Clone)]
pub struct EntityEquipment {
tim-kt marked this conversation as resolved.
Show resolved Hide resolved
pub entity_id: i32,
pub entries: Vec<EquipmentEntry>,
/// The entries in this `EntityEquipment` packet
///
/// Must not be empty. If nothing is equipped, send an empty main hand.
entries: Vec<EquipmentEntry>,
}

impl EntityEquipment {
pub fn new(entity_id: i32, mut entries: Vec<EquipmentEntry>) -> Self {
if entries.is_empty() {
entries.push(EquipmentEntry {
slot: MainHand,
item: Empty,
})
}
EntityEquipment { entity_id, entries }
}
}

impl Readable for EntityEquipment {
Expand Down
80 changes: 69 additions & 11 deletions feather/server/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ use ahash::AHashSet;
use flume::{Receiver, Sender};
use uuid::Uuid;

use crate::{
entities::PreviousPosition, initial_handler::NewPlayer, network_id_registry::NetworkId, Options,
};
use base::{
BlockId, ChunkHandle, ChunkPosition, EntityKind, EntityMetadata, Gamemode, Position,
ProfileProperty, Text, ValidBlockPosition,
Area, BlockId, ChunkHandle, ChunkPosition, EntityKind, EntityMetadata, Gamemode, Inventory,
Position, ProfileProperty, Text, ValidBlockPosition,
};
use common::{
chat::{ChatKind, ChatMessage},
entities::player::HotbarSlot,
Window,
};
use libcraft_items::InventorySlot;
Expand All @@ -27,18 +31,16 @@ use protocol::{
self,
server::{
AddPlayer, Animation, BlockChange, ChatPosition, ChunkData, ChunkDataKind,
DestroyEntities, Disconnect, EntityAnimation, EntityHeadLook, JoinGame, KeepAlive,
PlayerInfo, PlayerPositionAndLook, PluginMessage, SendEntityMetadata, SpawnPlayer,
Title, UnloadChunk, UpdateViewPosition, WindowItems,
DestroyEntities, Disconnect, EntityAnimation, EntityEquipment, EntityHeadLook,
EquipmentEntry,
EquipmentSlot::{Boots, Chestplate, Helmet, Leggings, MainHand, OffHand},
JoinGame, KeepAlive, PlayerInfo, PlayerPositionAndLook, PluginMessage,
SendEntityMetadata, SpawnPlayer, Title, UnloadChunk, UpdateViewPosition, WindowItems,
},
},
ClientPlayPacket, Nbt, ProtocolVersion, ServerPlayPacket, Writeable,
};
use quill_common::components::{OnGround, PreviousGamemode};

use crate::{
entities::PreviousPosition, initial_handler::NewPlayer, network_id_registry::NetworkId, Options,
};
use slab::Slab;

/// Max number of chunks to send to a client per tick.
Expand Down Expand Up @@ -539,11 +541,11 @@ impl Client {
self.set_slot(-1, item);
}

pub fn send_player_model_flags(&self, netowrk_id: NetworkId, model_flags: u8) {
pub fn send_player_model_flags(&self, network_id: NetworkId, model_flags: u8) {
let mut entity_metadata = EntityMetadata::new();
entity_metadata.set(16, model_flags);
self.send_packet(SendEntityMetadata {
entity_id: netowrk_id.0,
entity_id: network_id.0,
entries: entity_metadata,
});
}
Expand All @@ -558,6 +560,62 @@ impl Client {
});
}

// TODO this probably needs to be more general (i.e. applicable for all entities and not just players)
pub fn send_entity_equipment(
&self,
network_id: NetworkId,
inventory: &Inventory,
hotbar_slot: &HotbarSlot,
) {
if self.network_id == Some(network_id) {
return;
}

let mut equipment = Vec::<EquipmentEntry>::new();
if let Some(m) = inventory.item(Area::Hotbar, hotbar_slot.get()) {
equipment.push(EquipmentEntry {
slot: MainHand,
item: m.clone(),
})
};
if let Some(m) = inventory.item(Area::Offhand, 0) {
equipment.push(EquipmentEntry {
slot: OffHand,
item: m.clone(),
})
};
if let Some(m) = inventory.item(Area::Boots, 0) {
equipment.push(EquipmentEntry {
slot: Boots,
item: m.clone(),
})
};
if let Some(m) = inventory.item(Area::Leggings, 0) {
equipment.push(EquipmentEntry {
slot: Leggings,
item: m.clone(),
})
};
if let Some(m) = inventory.item(Area::Chestplate, 0) {
equipment.push(EquipmentEntry {
slot: Chestplate,
item: m.clone(),
})
};
if let Some(m) = inventory.item(Area::Helmet, 0) {
equipment.push(EquipmentEntry {
slot: Helmet,
item: m.clone(),
})
};

if equipment.is_empty() {
log::warn!("Could not get entity equipment");
}

self.send_packet(EntityEquipment::new(network_id.0, equipment));
}

pub fn send_abilities(&self, abilities: &base::anvil::player::PlayerAbilities) {
let mut bitfield = 0;
if *abilities.invulnerable {
Expand Down
2 changes: 1 addition & 1 deletion feather/server/src/packet_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub fn handle_packet(
handle_player_block_placement(game, server, packet, player_id)
}

ClientPlayPacket::HeldItemChange(packet) => handle_held_item_change(player, packet),
ClientPlayPacket::HeldItemChange(packet) => handle_held_item_change(server, player, packet),
ClientPlayPacket::InteractEntity(packet) => {
handle_interact_entity(game, server, packet, player_id)
}
Expand Down
42 changes: 17 additions & 25 deletions feather/server/src/packet_handlers/interaction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{ClientId, NetworkId, Server};
use base::inventory::{SLOT_HOTBAR_OFFSET, SLOT_OFFHAND};
use base::{
inventory::{SLOT_HOTBAR_OFFSET, SLOT_OFFHAND},
Inventory, Position,
};
use common::entities::player::HotbarSlot;
use common::interactable::InteractableRegistry;
use common::{Game, Window};
Expand Down Expand Up @@ -224,36 +227,25 @@ pub fn handle_interact_entity(
Ok(())
}

pub fn handle_held_item_change(player: EntityRef, packet: HeldItemChange) -> SysResult {
pub fn handle_held_item_change(
server: &mut Server,
player: EntityRef,
packet: HeldItemChange,
) -> SysResult {
let new_id = packet.slot as usize;
let mut slot = player.get_mut::<HotbarSlot>()?;

log::trace!("Got player slot change from {} to {}", slot.get(), new_id);

slot.set(new_id)?;
Ok(())
}

#[cfg(test)]
mod tests {
use common::Game;
use protocol::packets::client::HeldItemChange;

use super::*;
// Send an entity equipment packet to nearby players
let position = *player.get::<Position>()?;
let network_id = *player.get::<NetworkId>()?;
let inventory = player.get::<Inventory>()?;
server.broadcast_nearby_with(position, |client| {
client.send_entity_equipment(network_id, &inventory, &slot);
});

#[test]
fn held_item_change() {
let mut game = Game::new();
let entity = game.ecs.spawn((HotbarSlot::new(0),));
let player = game.ecs.entity(entity).unwrap();

let packet = HeldItemChange { slot: 8 };

handle_held_item_change(player, packet).unwrap();

assert_eq!(
*game.ecs.get::<HotbarSlot>(entity).unwrap(),
HotbarSlot::new(8)
);
}
Ok(())
}
37 changes: 33 additions & 4 deletions feather/server/src/packet_handlers/inventory.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use anyhow::bail;
use base::Gamemode;
use common::{window::BackingWindow, Window};
use base::{Area, Gamemode, Inventory, Position};
use common::{entities::player::HotbarSlot, window::BackingWindow, Window};
use ecs::{EntityRef, SysResult};
use protocol::packets::client::{ClickWindow, CreativeInventoryAction};

use crate::{ClientId, Server};
use crate::{ClientId, NetworkId, Server};

pub fn handle_creative_inventory_action(
player: EntityRef,
Expand All @@ -30,6 +30,35 @@ pub fn handle_creative_inventory_action(
let client_id = *player.get::<ClientId>()?;
let client = server.clients.get(client_id).unwrap();
client.send_window_items(&window);

let visible_change =
if let Some((_, area, slot)) = window.inner().index_to_slot(packet.slot as usize) {
match area {
Area::Hotbar => {
let hotbar_slot = *player.get::<HotbarSlot>()?;
hotbar_slot.get() == slot
}
Area::Helmet => true,
Area::Chestplate => true,
Area::Leggings => true,
Area::Boots => true,
Area::Offhand => true,
_ => false,
}
} else {
false
};

if visible_change {
// Send an entity equipment packet to nearby players
let position = *player.get::<Position>()?;
let network_id = *player.get::<NetworkId>()?;
let inventory = player.get::<Inventory>()?;
let hotbar_slot = player.get::<HotbarSlot>()?;
server.broadcast_nearby_with(position, |client| {
client.send_entity_equipment(network_id, &inventory, &hotbar_slot);
});
}
}

Ok(())
Expand Down Expand Up @@ -67,7 +96,7 @@ fn _handle_click_window(player: &EntityRef, packet: &ClickWindow) -> SysResult {
0 => match packet.button {
0 => window.left_click(packet.slot as usize)?,
1 => window.right_click(packet.slot as usize)?,
_ => bail!("unrecgonized click"),
_ => bail!("unrecognized click"),
},
1 => window.shift_click(packet.slot as usize)?,
5 => match packet.button {
Expand Down
16 changes: 15 additions & 1 deletion feather/server/src/systems/entity/spawn_packet.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use ahash::AHashSet;
use anyhow::Context;
use base::Position;
use base::{Inventory, Position};
use common::{
entities::player::HotbarSlot,
events::{ChunkCrossEvent, EntityCreateEvent, EntityRemoveEvent, ViewUpdateEvent},
Game,
};
use ecs::{SysResult, SystemExecutor};
use quill_common::entities::Player;

use crate::{entities::SpawnPacketSender, ClientId, NetworkId, Server};

Expand Down Expand Up @@ -37,6 +39,18 @@ pub fn update_visible_entities(game: &mut Game, server: &mut Server) -> SysResul
.send(&entity_ref, client)
.context("failed to send spawn packet")?;
}

// If the current entity is a player, send an entity equipment packet to nearby players
// TODO this should also update other entities
if entity_ref.get::<Player>().is_ok() {
let position = *entity_ref.get::<Position>()?;
let network_id = *entity_ref.get::<NetworkId>()?;
let inventory = entity_ref.get::<Inventory>()?;
let hotbar_slot = entity_ref.get::<HotbarSlot>()?;
server.broadcast_nearby_with(position, |client| {
client.send_entity_equipment(network_id, &inventory, &hotbar_slot);
});
}
}
}
}
Expand Down