Skip to content

Commit fdf5565

Browse files
authored
Fix position syncronization issues for entities and fixes loading and saving of playerdata to use the correct inventory slot indexes (#498)
* Add a previousOnGround component to track changes. * Add OnGround check for entity movement. * Check if OnGround has changed and send a different packet. * remove clone * Fixes saving and loading playerdata to use correct indexes. * Add doc linking to from_network_index and from_inventory_index. * Fix comment links. * Explain panic. * Add more comments and links. * fix typo * Add documentation for set_item on window. * Add comment about why the unwraps are safe. * Add comment explaining why unwrap is safe.
1 parent 00f5f10 commit fdf5565

File tree

8 files changed

+90
-34
lines changed

8 files changed

+90
-34
lines changed

feather/base/src/anvil/player.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ pub struct InventorySlot {
7070
}
7171

7272
impl InventorySlot {
73-
/// Converts an `ItemStack` and network protocol index into an `InventorySlot`.
74-
pub fn from_network_index(network: usize, stack: ItemStack) -> Option<Self> {
73+
/// Converts an [`ItemStack`] and network protocol index into an [`InventorySlot`].
74+
pub fn from_network_index(network: usize, stack: &ItemStack) -> Option<Self> {
7575
let slot = if SLOT_HOTBAR_OFFSET <= network && network < SLOT_HOTBAR_OFFSET + HOTBAR_SIZE {
7676
// Hotbar
7777
(network - SLOT_HOTBAR_OFFSET) as i8
@@ -90,8 +90,8 @@ impl InventorySlot {
9090
Some(Self::from_inventory_index(slot, stack))
9191
}
9292

93-
/// Converts an `ItemStack` and inventory position index into an `InventorySlot`.
94-
pub fn from_inventory_index(slot: i8, stack: ItemStack) -> Self {
93+
/// Converts an [`ItemStack`] and inventory position index into an [`InventorySlot`].
94+
pub fn from_inventory_index(slot: i8, stack: &ItemStack) -> Self {
9595
let nbt = stack.clone().into();
9696
let nbt = if nbt == Default::default() {
9797
None
@@ -287,7 +287,7 @@ mod tests {
287287
assert_eq!(
288288
InventorySlot::from_network_index(
289289
expected,
290-
ItemStack::new(Item::Stone, 1).unwrap()
290+
&ItemStack::new(Item::Stone, 1).unwrap()
291291
),
292292
Some(slot),
293293
);

feather/common/src/game.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl Game {
104104
self.entity_spawn_callbacks.push(Box::new(callback));
105105
}
106106

107-
/// Creates an emtpy entity builder to create entities in
107+
/// Creates an empty entity builder to create entities in
108108
/// the ecs world.
109109
pub fn create_empty_entity_builder(&mut self) -> EntityBuilder {
110110
mem::take(&mut self.entity_builder)

feather/common/src/window.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ impl Window {
353353
self.inner.item(index)
354354
}
355355

356+
/// Sets an [`InventorySlot`] at the index.
357+
/// # Error
358+
/// Returns an error if the index is [`WindowError::OutOfBounds`]
356359
pub fn set_item(&self, index: usize, item: InventorySlot) -> Result<(), WindowError> {
357360
self.inner.set_item(index, item)
358361
}

feather/server/src/client.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use common::{
2020
use libcraft_items::InventorySlot;
2121
use packets::server::{Particle, SetSlot, SpawnLivingEntity, UpdateLight, WindowConfirmation};
2222
use protocol::packets::server::{
23-
EntityPosition, EntityPositionAndRotation, HeldItemChange, PlayerAbilities,
23+
EntityPosition, EntityPositionAndRotation, EntityTeleport, HeldItemChange, PlayerAbilities,
2424
};
2525
use protocol::{
2626
packets::{
@@ -37,7 +37,10 @@ use protocol::{
3737
use quill_common::components::{OnGround, PreviousGamemode};
3838

3939
use crate::{
40-
entities::PreviousPosition, initial_handler::NewPlayer, network_id_registry::NetworkId, Options,
40+
entities::{PreviousOnGround, PreviousPosition},
41+
initial_handler::NewPlayer,
42+
network_id_registry::NetworkId,
43+
Options,
4144
};
4245
use slab::Slab;
4346

@@ -386,6 +389,7 @@ impl Client {
386389
position: Position,
387390
prev_position: PreviousPosition,
388391
on_ground: OnGround,
392+
prev_on_ground: PreviousOnGround,
389393
) {
390394
if self.network_id == Some(network_id) {
391395
// This entity is the client. Only update
@@ -400,6 +404,21 @@ impl Client {
400404
let no_change_yaw = (position.yaw - prev_position.0.yaw).abs() < 0.001;
401405
let no_change_pitch = (position.pitch - prev_position.0.pitch).abs() < 0.001;
402406

407+
// If the entity jumps or falls we should send a teleport packet instead to keep relative movement in sync.
408+
if on_ground != prev_on_ground.0 {
409+
self.send_packet(EntityTeleport {
410+
entity_id: network_id.0,
411+
x: position.x,
412+
y: position.y,
413+
z: position.z,
414+
yaw: position.yaw,
415+
pitch: position.pitch,
416+
on_ground: *on_ground,
417+
});
418+
419+
return;
420+
}
421+
403422
if no_change_yaw && no_change_pitch {
404423
self.send_packet(EntityPosition {
405424
entity_id: network_id.0,

feather/server/src/entities.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use base::{EntityKind, Position};
22
use ecs::{EntityBuilder, EntityRef, SysResult};
3-
use quill_common::entity_init::EntityInit;
3+
use quill_common::{components::OnGround, entity_init::EntityInit};
44
use uuid::Uuid;
55

66
use crate::{Client, NetworkId};
@@ -15,17 +15,31 @@ impl SpawnPacketSender {
1515
}
1616
}
1717

18-
/// Stores the position of an entity on
18+
/// Stores the [`Position`] of an entity on
1919
/// the previous tick. Used to determine
2020
/// when to send movement updates.
2121
#[derive(Copy, Clone, Debug)]
2222
pub struct PreviousPosition(pub Position);
23+
/// Stores the [`OnGround`] status of an entity on
24+
/// the previous tick. Used to determine
25+
/// what movement packet to send.
26+
#[derive(Copy, Clone, Debug)]
27+
pub struct PreviousOnGround(pub OnGround);
2328

2429
pub fn add_entity_components(builder: &mut EntityBuilder, init: &EntityInit) {
2530
if !builder.has::<NetworkId>() {
2631
builder.add(NetworkId::new());
2732
}
28-
builder.add(PreviousPosition(*builder.get::<Position>().unwrap()));
33+
34+
// can't panic because this is only called after both position and onground is added to all entities.
35+
// Position is added in the caller of this function and on_ground is added in the
36+
// build default function. All entity builder functions call the build default function.
37+
let prev_position = *builder.get::<Position>().unwrap();
38+
let on_ground = *builder.get::<OnGround>().unwrap();
39+
40+
builder
41+
.add(PreviousPosition(prev_position))
42+
.add(PreviousOnGround(on_ground));
2943
add_spawn_packet(builder, init);
3044
}
3145

feather/server/src/systems/entity.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use quill_common::{
1212
events::{SneakEvent, SprintEvent},
1313
};
1414

15-
use crate::{entities::PreviousPosition, NetworkId, Server};
15+
use crate::{
16+
entities::{PreviousOnGround, PreviousPosition},
17+
NetworkId, Server,
18+
};
1619

1720
mod spawn_packet;
1821

@@ -27,17 +30,32 @@ pub fn register(game: &mut Game, systems: &mut SystemExecutor<Game>) {
2730

2831
/// Sends entity movement packets.
2932
fn send_entity_movement(game: &mut Game, server: &mut Server) -> SysResult {
30-
for (_, (&position, prev_position, &on_ground, &network_id)) in game
33+
for (_, (&position, prev_position, &on_ground, &network_id, prev_on_ground)) in game
3134
.ecs
32-
.query::<(&Position, &mut PreviousPosition, &OnGround, &NetworkId)>()
35+
.query::<(
36+
&Position,
37+
&mut PreviousPosition,
38+
&OnGround,
39+
&NetworkId,
40+
&mut PreviousOnGround,
41+
)>()
3342
.iter()
3443
{
3544
if position != prev_position.0 {
3645
server.broadcast_nearby_with(position, |client| {
37-
client.update_entity_position(network_id, position, *prev_position, on_ground);
46+
client.update_entity_position(
47+
network_id,
48+
position,
49+
*prev_position,
50+
on_ground,
51+
*prev_on_ground,
52+
);
3853
});
3954
prev_position.0 = position;
4055
}
56+
if on_ground != prev_on_ground.0 {
57+
prev_on_ground.0 = on_ground;
58+
}
4159
}
4260
Ok(())
4361
}

feather/server/src/systems/player_join.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,19 @@ fn accept_new_player(game: &mut Game, server: &mut Server, client_id: ClientId)
8383
player: inventory.new_handle(),
8484
});
8585
if let Ok(data) = player_data.as_ref() {
86-
for slot in data.inventory.iter() {
86+
for inventory_slot in data.inventory.iter() {
87+
let net_slot = inventory_slot.convert_index();
88+
let slot = match net_slot {
89+
Some(slot) => slot,
90+
None => {
91+
log::error!("Failed to convert saved slot into network slot");
92+
continue;
93+
}
94+
};
95+
96+
// This can't fail since the earlier match filters out all incorrect indexes.
8797
window
88-
.set_item(
89-
slot.slot as usize,
90-
InventorySlot::Filled(ItemStack::from(slot)),
91-
)
98+
.set_item(slot, InventorySlot::Filled(ItemStack::from(inventory_slot)))
9299
.unwrap();
93100
}
94101
}

feather/server/src/systems/player_leave.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use num_traits::cast::ToPrimitive;
22

3-
use base::anvil::entity::{AnimalData, BaseEntityData, ItemNbt};
3+
use base::anvil::entity::{AnimalData, BaseEntityData};
44
use base::anvil::player::{InventorySlot, PlayerAbilities, PlayerData};
55
use base::{Gamemode, Inventory, Position, Text};
66
use common::entities::player::HotbarSlot;
@@ -130,21 +130,16 @@ fn create_player_data(
130130
// Here we filter out all empty slots.
131131
.filter_map(|(slot, item)| {
132132
match item {
133-
libcraft_items::InventorySlot::Filled(item) => Some(InventorySlot {
134-
count: item.count() as i8,
135-
slot: slot as i8,
136-
item: item.item().name().to_owned(),
137-
nbt: {
138-
let nbt = ItemNbt {
139-
damage: item.damage_taken().map(|damage| damage as i32),
140-
};
141-
if nbt.damage.is_none() {
133+
libcraft_items::InventorySlot::Filled(item) => {
134+
let res = InventorySlot::from_network_index(slot, item);
135+
match res {
136+
Some(i) => Some(i),
137+
None => {
138+
log::error!("Failed to convert the slot into anvil format.");
142139
None
143-
} else {
144-
Some(nbt)
145140
}
146-
},
147-
}),
141+
}
142+
}
148143
libcraft_items::InventorySlot::Empty => {
149144
// Empty items are filtered out.
150145
None

0 commit comments

Comments
 (0)