Skip to content

Commit bdf76ec

Browse files
committed
Broadcast item in player's main hand
1 parent f77170c commit bdf76ec

7 files changed

Lines changed: 236 additions & 11 deletions

File tree

core/src/inventory.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ pub const SLOT_OFFHAND: SlotIndex = 45;
2222
pub const SLOT_INVENTORY_OFFSET: SlotIndex = 9;
2323
pub const SLOT_HOTBAR_OFFSET: SlotIndex = 36;
2424

25+
pub const HOTBAR_SIZE: SlotIndex = 9;
26+
27+
pub const SLOT_ENTITY_EQUIPMENT_MAIN_HAND: SlotIndex = 0;
28+
pub const SLOT_ENTITY_EQUIPMENT_OFF_HAND: SlotIndex = 1;
29+
pub const SLOT_ENTITY_EQUIPMENT_BOOTS: SlotIndex = 2;
30+
pub const SLOT_ENTITY_EQUIPMENT_LEGGINGS: SlotIndex = 3;
31+
pub const SLOT_ENTITY_EQUIPMENT_CHESTPLATE: SlotIndex = 4;
32+
pub const SLOT_ENTITY_EQUIPMENT_HELMET: SlotIndex = 5;
33+
2534
/// The various types of inventories ("windows").
2635
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2736
pub enum InventoryType {

core/src/network/packet/implementation.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1482,6 +1482,13 @@ pub struct EntityHeadLook {
14821482
pub head_yaw: u8,
14831483
}
14841484

1485+
#[derive(Default, AsAny, new, Packet, Clone)]
1486+
pub struct EntityEquipment {
1487+
pub entity_id: VarInt,
1488+
pub slot: VarInt,
1489+
pub item: Slot,
1490+
}
1491+
14851492
// TODO Select Advancement Tab
14861493
// TODO World Border
14871494

core/src/network/packet/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,11 @@ lazy_static! {
513513
PacketType::EntityHeadLook,
514514
);
515515

516+
m.insert(
517+
PacketId(0x42, PacketDirection::Clientbound, PacketStage::Play),
518+
PacketType::EntityEquipment,
519+
);
520+
516521
m.insert(
517522
PacketId(0x49, PacketDirection::Clientbound, PacketStage::Play),
518523
PacketType::SpawnPosition,

server/src/main.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,16 @@ fn init_world<'a, 'b>(
256256
"creative_inventory",
257257
&["network"],
258258
)
259+
.with(
260+
player::HeldItemChangeSystem,
261+
"held_item_change",
262+
&["network"],
263+
)
264+
.with(
265+
player::HeldItemBroadcastSystem::default(),
266+
"held_item_broadcast",
267+
&["held_item_change", "creative_inventory"],
268+
)
259269
.build();
260270

261271
dispatcher.setup(&mut world);

server/src/player/broadcast.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
use crate::entity::{EntityComponent, PlayerComponent};
22
use crate::joinhandler::PlayerJoinEvent;
33
use crate::network::{send_packet_to_player, NetworkComponent};
4+
use crate::player::InventoryComponent;
45
use feather_core::entitymeta::{EntityMetadata, MetaEntry};
5-
use feather_core::network::packet::implementation::{PlayerInfo, PlayerInfoAction, SpawnPlayer};
6+
use feather_core::inventory::SLOT_ENTITY_EQUIPMENT_MAIN_HAND;
7+
use feather_core::network::packet::implementation::{
8+
EntityEquipment, PlayerInfo, PlayerInfoAction, SpawnPlayer,
9+
};
610
use feather_core::Gamemode;
711
use shrev::EventChannel;
812
use specs::SystemData;
@@ -29,11 +33,12 @@ impl<'a> System<'a> for JoinBroadcastSystem {
2933
ReadStorage<'a, EntityComponent>,
3034
ReadStorage<'a, PlayerComponent>,
3135
ReadStorage<'a, NetworkComponent>,
36+
ReadStorage<'a, InventoryComponent>,
3237
Entities<'a>,
3338
);
3439

3540
fn run(&mut self, data: Self::SystemData) {
36-
let (join_events, entity_comps, player_comps, net_comps, entities) = data;
41+
let (join_events, entity_comps, player_comps, net_comps, inventories, entities) = data;
3742

3843
for event in join_events.read(&mut self.reader.as_mut().unwrap()) {
3944
// Broadcast join
@@ -56,14 +61,22 @@ impl<'a> System<'a> for JoinBroadcastSystem {
5661
let net_comp = net_comps.get(event.player).unwrap();
5762

5863
// Send existing players to new player
59-
for (entity_comp, player_comp, entity) in
60-
(&entity_comps, &player_comps, &entities).join()
64+
for (entity_comp, player_comp, entity, inventory) in
65+
(&entity_comps, &player_comps, &entities, &inventories).join()
6166
{
6267
if entity != event.player {
6368
let (player_info, spawn_player) =
6469
get_player_initialization_packets(entity_comp, player_comp, entity);
6570
send_packet_to_player(net_comp, player_info);
6671
send_packet_to_player(net_comp, spawn_player);
72+
73+
let slot = inventory.item_at(inventory.held_item);
74+
let entity_equipment = EntityEquipment::new(
75+
entity.id() as i32,
76+
SLOT_ENTITY_EQUIPMENT_MAIN_HAND as i32,
77+
slot.cloned(),
78+
);
79+
send_packet_to_player(net_comp, entity_equipment);
6780
}
6881
}
6982
}

server/src/player/inventory.rs

Lines changed: 184 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,35 @@
11
use crate::disconnect_player;
22
use crate::entity::PlayerComponent;
3-
use crate::network::PacketQueue;
4-
use feather_core::inventory::{Inventory, InventoryType};
3+
use crate::network::{send_packet_to_all_players, NetworkComponent, PacketQueue};
4+
use feather_core::inventory::{
5+
Inventory, InventoryType, SlotIndex, HOTBAR_SIZE, SLOT_ENTITY_EQUIPMENT_MAIN_HAND,
6+
SLOT_HOTBAR_OFFSET,
7+
};
58
use feather_core::network::cast_packet;
6-
use feather_core::network::packet::implementation::CreativeInventoryAction;
9+
use feather_core::network::packet::implementation::{
10+
CreativeInventoryAction, EntityEquipment, HeldItemChangeServerbound,
11+
};
712
use feather_core::network::packet::PacketType;
813
use feather_core::Gamemode;
14+
use shrev::EventChannel;
915
use specs::storage::BTreeStorage;
10-
use specs::System;
11-
use specs::{Component, LazyUpdate, Read, ReadStorage, WriteStorage};
16+
use specs::SystemData;
17+
use specs::{Component, Entities, LazyUpdate, Read, ReadStorage, ReaderId, World, WriteStorage};
18+
use specs::{Entity, System, Write};
1219
use std::ops::{Deref, DerefMut};
1320

1421
/// Component for storing a player's inventory.
1522
#[derive(Clone, Debug)]
1623
pub struct InventoryComponent {
1724
inventory: Inventory,
25+
pub held_item: SlotIndex,
1826
}
1927

2028
impl InventoryComponent {
2129
pub fn new() -> Self {
2230
Self {
2331
inventory: Inventory::new(InventoryType::Player, 46),
32+
held_item: 0,
2433
}
2534
}
2635
}
@@ -49,19 +58,26 @@ impl Component for InventoryComponent {
4958
type Storage = BTreeStorage<Self>;
5059
}
5160

61+
/// Event which is triggered when a player updates
62+
/// their held item.
63+
pub struct HeldItemUpdateEvent {
64+
pub player: Entity,
65+
}
66+
5267
/// System for handling Creative Inventory Action packets.
5368
pub struct CreativeInventorySystem;
5469

5570
impl<'a> System<'a> for CreativeInventorySystem {
5671
type SystemData = (
5772
WriteStorage<'a, InventoryComponent>,
5873
ReadStorage<'a, PlayerComponent>,
74+
Write<'a, EventChannel<HeldItemUpdateEvent>>,
5975
Read<'a, PacketQueue>,
6076
Read<'a, LazyUpdate>,
6177
);
6278

6379
fn run(&mut self, data: Self::SystemData) {
64-
let (mut inventories, players, packet_queue, lazy) = data;
80+
let (mut inventories, players, mut events, packet_queue, lazy) = data;
6581

6682
let packets = packet_queue.for_packet(PacketType::CreativeInventoryAction);
6783

@@ -96,8 +112,91 @@ impl<'a> System<'a> for CreativeInventorySystem {
96112
inventory.clear_item_at(packet.slot as usize);
97113
}
98114
}
115+
116+
// If the updates slot was the player's held item,
117+
// we need to broadcast the equipment update
118+
if inventory.held_item == packet.slot as usize {
119+
let event = HeldItemUpdateEvent { player };
120+
events.single_write(event);
121+
}
122+
}
123+
}
124+
}
125+
126+
/// System for handling Held Item Change packets.
127+
pub struct HeldItemChangeSystem;
128+
129+
impl<'a> System<'a> for HeldItemChangeSystem {
130+
type SystemData = (
131+
WriteStorage<'a, InventoryComponent>,
132+
Write<'a, EventChannel<HeldItemUpdateEvent>>,
133+
Read<'a, PacketQueue>,
134+
Read<'a, LazyUpdate>,
135+
);
136+
137+
fn run(&mut self, data: Self::SystemData) {
138+
let (mut inventories, mut events, packet_queue, lazy) = data;
139+
140+
let packets = packet_queue.for_packet(PacketType::HeldItemChangeServerbound);
141+
142+
for (player, packet) in packets {
143+
let packet = cast_packet::<HeldItemChangeServerbound>(&*packet);
144+
145+
if packet.slot as usize >= HOTBAR_SIZE {
146+
disconnect_player(player, "Hotbar index out of bounds".to_string(), &lazy);
147+
continue;
148+
}
149+
150+
let inventory = inventories.get_mut(player).unwrap();
151+
inventory.held_item = packet.slot as usize;
152+
153+
// Trigger event
154+
let event = HeldItemUpdateEvent { player };
155+
events.single_write(event);
156+
}
157+
}
158+
}
159+
160+
/// System for broadcasting held item updates.
161+
#[derive(Default)]
162+
pub struct HeldItemBroadcastSystem {
163+
reader: Option<ReaderId<HeldItemUpdateEvent>>,
164+
}
165+
166+
impl<'a> System<'a> for HeldItemBroadcastSystem {
167+
type SystemData = (
168+
ReadStorage<'a, NetworkComponent>,
169+
ReadStorage<'a, InventoryComponent>,
170+
Read<'a, EventChannel<HeldItemUpdateEvent>>,
171+
Entities<'a>,
172+
);
173+
174+
fn run(&mut self, data: Self::SystemData) {
175+
let (networks, inventories, events, entities) = data;
176+
177+
for event in events.read(&mut self.reader.as_mut().unwrap()) {
178+
let inv = inventories.get(event.player).unwrap();
179+
let item = inv.item_at(SLOT_HOTBAR_OFFSET + inv.held_item).cloned();
180+
181+
let packet = EntityEquipment::new(
182+
event.player.id() as i32,
183+
SLOT_ENTITY_EQUIPMENT_MAIN_HAND as i32,
184+
item,
185+
);
186+
187+
send_packet_to_all_players(&networks, &entities, packet, Some(event.player));
99188
}
100189
}
190+
191+
fn setup(&mut self, world: &mut World) {
192+
Self::SystemData::setup(world);
193+
194+
self.reader = Some(
195+
world
196+
.fetch_mut::<EventChannel<HeldItemUpdateEvent>>()
197+
.register_reader(),
198+
);
199+
}
101200
}
102201

103202
#[cfg(test)]
@@ -118,6 +217,8 @@ mod tests {
118217

119218
t::receive_packet(&player, &w, packet);
120219

220+
let mut event_reader = t::reader(&w);
221+
121222
d.dispatch(&w);
122223
w.maintain();
123224

@@ -127,6 +228,15 @@ mod tests {
127228
let inv = inv_storage.get(player.entity).unwrap();
128229
assert_eq!(inv.item_at(0).unwrap(), &ItemStack::new(Item::IronSword, 1));
129230

231+
// Confirm that event was triggered
232+
{
233+
let channel = w.fetch::<EventChannel<HeldItemUpdateEvent>>();
234+
let events = channel.read(&mut event_reader).collect::<Vec<_>>();
235+
assert_eq!(events.len(), 1);
236+
let first = events.first().unwrap();
237+
assert_eq!(first.player, player.entity);
238+
}
239+
130240
drop(inv_storage);
131241

132242
let packet = CreativeInventoryAction::new(0, None);
@@ -172,4 +282,72 @@ mod tests {
172282

173283
t::assert_disconnected(&player);
174284
}
285+
286+
#[test]
287+
fn test_held_item_change_system() {
288+
let (mut w, mut d) = t::init_world();
289+
290+
let player = t::add_player(&mut w);
291+
292+
let slot = 4;
293+
294+
let mut event_reader = t::reader(&w);
295+
296+
let packet = HeldItemChangeServerbound::new(slot);
297+
t::receive_packet(&player, &w, packet);
298+
299+
d.dispatch(&w);
300+
w.maintain();
301+
302+
let channel = w.fetch::<EventChannel<HeldItemUpdateEvent>>();
303+
let events = channel.read(&mut event_reader).collect::<Vec<_>>();
304+
305+
assert_eq!(events.len(), 1);
306+
307+
let first = events.first().unwrap();
308+
assert_eq!(first.player, player.entity);
309+
310+
let inventories = w.read_component::<InventoryComponent>();
311+
let inv = inventories.get(player.entity).unwrap();
312+
313+
assert_eq!(inv.held_item, slot as usize);
314+
}
315+
316+
#[test]
317+
fn test_held_item_change_system_out_of_bounds() {
318+
let (mut w, mut d) = t::init_world();
319+
320+
let player = t::add_player(&mut w);
321+
322+
let slot = 9;
323+
324+
let packet = HeldItemChangeServerbound::new(slot);
325+
t::receive_packet(&player, &w, packet);
326+
327+
d.dispatch(&w);
328+
w.maintain();
329+
330+
t::assert_disconnected(&player); // Slot out of bounds
331+
}
332+
333+
#[test]
334+
fn test_held_item_broadcast_system() {
335+
let (mut w, mut d) = t::init_world();
336+
337+
let player = t::add_player(&mut w);
338+
let player2 = t::add_player(&mut w);
339+
340+
let event = HeldItemUpdateEvent {
341+
player: player.entity,
342+
};
343+
344+
w.fetch_mut::<EventChannel<HeldItemUpdateEvent>>()
345+
.single_write(event);
346+
347+
d.dispatch(&w);
348+
w.maintain();
349+
350+
t::assert_packet_received(&player2, PacketType::EntityEquipment);
351+
t::assert_packet_not_received(&player, PacketType::EntityEquipment);
352+
}
175353
}

server/src/player/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ pub use movement::{
2727

2828
pub use animation::{AnimationBroadcastSystem, PlayerAnimationEvent, PlayerAnimationSystem};
2929

30-
pub use inventory::{CreativeInventorySystem, InventoryComponent};
30+
pub use inventory::{
31+
CreativeInventorySystem, HeldItemBroadcastSystem, HeldItemChangeSystem, HeldItemUpdateEvent,
32+
InventoryComponent,
33+
};

0 commit comments

Comments
 (0)