Skip to content

Commit 473d35f

Browse files
fix: better player movement handling (#831)
This PR aims to standardize player position handling notably teleportations and velocity changes. Right now every module does its own thing and sends packets directly to the client so it is impossible for the server to keep track of the player's state. By normalizing the way to change player position/velocity it could allow us to implement better movement verification and better on_ground calculation in the future.
1 parent b427410 commit 473d35f

File tree

15 files changed

+543
-219
lines changed

15 files changed

+543
-219
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/hyperion-respawn/src/lib.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ use hyperion::{
66
prelude::Module,
77
},
88
net::{ConnectionId, DataBundle},
9-
protocol::{game_mode::OptGameMode, packets::play, ByteAngle, VarInt},
10-
server::{ident, GameMode},
9+
protocol::{
10+
game_mode::OptGameMode,
11+
packets::play::{self, PlayerAbilitiesS2c},
12+
BlockPos, ByteAngle, GlobalPos, VarInt,
13+
},
14+
server::{abilities::PlayerAbilitiesFlags, ident, GameMode},
1115
simulation::{
1216
event::{ClientStatusCommand, ClientStatusEvent},
1317
handlers::PacketSwitchQuery,
1418
metadata::{entity::Pose, living_entity::Health},
1519
packet::HandlerRegistry,
16-
Pitch, Position, Uuid, Xp, Yaw,
20+
Flight, FlyingSpeed, Pitch, Position, Uuid, Xp, Yaw,
1721
},
1822
};
1923
use hyperion_utils::{EntityExt, LifetimeHandle};
@@ -43,8 +47,21 @@ impl Module for RespawnModule {
4347
&Yaw,
4448
&Pitch,
4549
&Xp,
50+
&Flight,
51+
&FlyingSpeed,
4652
)>(
47-
|(connection, health, pose, uuid, position, yaw, pitch, xp)| {
53+
|(
54+
connection,
55+
health,
56+
pose,
57+
uuid,
58+
position,
59+
yaw,
60+
pitch,
61+
xp,
62+
flight,
63+
flying_speed,
64+
)| {
4865
health.heal(20.);
4966

5067
*pose = Pose::Standing;
@@ -65,7 +82,10 @@ impl Module for RespawnModule {
6582
is_debug: false,
6683
is_flat: false,
6784
copy_metadata: false,
68-
last_death_location: None,
85+
last_death_location: Option::from(GlobalPos {
86+
dimension_name: ident!("minecraft:overworld").into(),
87+
position: BlockPos::from(position.as_dvec3()),
88+
}),
6989
portal_cooldown: VarInt::default(),
7090
};
7191

@@ -75,6 +95,14 @@ impl Module for RespawnModule {
7595
total_xp: VarInt::default(),
7696
};
7797

98+
let pkt_abilities = PlayerAbilitiesS2c {
99+
flags: PlayerAbilitiesFlags::default()
100+
.with_flying(flight.is_flying)
101+
.with_allow_flying(flight.allow),
102+
flying_speed: flying_speed.speed,
103+
fov_modifier: 0.0,
104+
};
105+
78106
let pkt_add_player = play::PlayerSpawnS2c {
79107
entity_id: VarInt(client.minecraft_id()),
80108
player_uuid: uuid.0,
@@ -87,11 +115,13 @@ impl Module for RespawnModule {
87115
bundle.add_packet(&pkt_health).unwrap();
88116
bundle.add_packet(&pkt_respawn).unwrap();
89117
bundle.add_packet(&pkt_xp).unwrap();
118+
bundle.add_packet(&pkt_abilities).unwrap();
90119

91120
bundle.unicast(*connection).unwrap();
92121
query
93122
.compose
94123
.broadcast(&pkt_add_player, query.system)
124+
.exclude(*connection)
95125
.send()
96126
.unwrap();
97127
},

crates/hyperion/src/egress/player_join/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{borrow::Cow, collections::BTreeSet, ops::Index};
22

33
use anyhow::Context;
44
use flecs_ecs::prelude::*;
5+
use glam::DVec3;
56
use hyperion_crafting::{Action, CraftingRegistry, RecipeBookState};
67
use hyperion_utils::EntityExt;
78
use rayon::iter::{IntoParallelIterator, ParallelIterator};
@@ -20,7 +21,7 @@ use valence_registry::{BiomeRegistry, RegistryCodec};
2021
use valence_server::entity::EntityKind;
2122
use valence_text::IntoText;
2223

23-
use crate::simulation::{PacketState, Pitch};
24+
use crate::simulation::{MovementTracking, PacketState, Pitch};
2425

2526
mod list;
2627
pub use list::*;
@@ -76,6 +77,16 @@ pub fn player_join_world(
7677

7778
let id = entity.minecraft_id();
7879

80+
entity.set(MovementTracking {
81+
received_movement_packets: 0,
82+
last_tick_flying: false,
83+
last_tick_position: **position,
84+
fall_start_y: position.y,
85+
server_velocity: DVec3::ZERO,
86+
sprinting: false,
87+
was_on_ground: false,
88+
});
89+
7990
let registry_codec = registry_codec_raw();
8091
let codec = RegistryCodec::default();
8192

@@ -567,6 +578,7 @@ impl Module for PlayerJoinModule {
567578
|(uuid, name, position, yaw, pitch, &stream_id)| {
568579
let query = &query;
569580
let query = &query.0;
581+
entity.set_name(name);
570582

571583
// if we get an error joining, we should kick the player
572584
if let Err(e) = player_join_world(

crates/hyperion/src/egress/sync_entity_state.rs

Lines changed: 124 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Debug;
22

33
use flecs_ecs::prelude::*;
4-
use glam::Vec3;
4+
use glam::{IVec3, Vec3};
55
use hyperion_utils::EntityExt;
66
use itertools::Either;
77
use valence_protocol::{
@@ -13,11 +13,11 @@ use crate::{
1313
Prev,
1414
net::{Compose, ConnectionId, DataBundle},
1515
simulation::{
16-
Owner, Pitch, Position, Velocity, Xp, Yaw,
16+
Flight, MovementTracking, Owner, PendingTeleportation, Pitch, Position, Velocity, Xp, Yaw,
1717
animation::ActiveAnimation,
1818
blocks::Blocks,
1919
entity_kind::EntityKind,
20-
event,
20+
event::{self, HitGroundEvent},
2121
handlers::is_grounded,
2222
metadata::{MetadataChanges, get_and_clear_metadata},
2323
},
@@ -143,8 +143,16 @@ impl Module for EntityStateSyncModule {
143143
entity_id,
144144
tracked_values: RawBytes(&view),
145145
};
146-
147-
// todo(perf): do so locally
146+
if entity.has::<Position>() {
147+
entity.get::<&Position>(|position| {
148+
compose
149+
.broadcast_local(&pkt, position.to_chunk(), system)
150+
.send()
151+
.unwrap();
152+
});
153+
return;
154+
}
155+
// Should never be reached but who knows
148156
compose.broadcast(&pkt, system).send().unwrap();
149157
}
150158
});
@@ -188,46 +196,92 @@ impl Module for EntityStateSyncModule {
188196
"sync_player_entity",
189197
world,
190198
&Compose($),
191-
&mut (Prev, Position),
199+
&mut Events($),
192200
&mut (Prev, Yaw),
193201
&mut (Prev, Pitch),
194202
&mut Position,
195203
&mut Velocity,
196204
&Yaw,
197205
&Pitch,
206+
?&mut PendingTeleportation,
207+
&mut MovementTracking,
208+
&Flight,
198209
)
199-
.multi_threaded()
200-
.kind::<flecs::pipeline::PreStore>()
201-
.each_iter(
202-
|it,
203-
row,
204-
(
205-
compose,
206-
prev_position,
207-
prev_yaw,
208-
prev_pitch,
209-
position,
210-
velocity,
211-
yaw,
212-
pitch,
213-
)| {
214-
let world = it.system().world();
215-
let system = it.system();
216-
let entity = it.entity(row);
217-
let entity_id = VarInt(entity.minecraft_id());
210+
.multi_threaded()
211+
.kind::<flecs::pipeline::PreStore>()
212+
.each_iter(
213+
|it,
214+
row,
215+
(
216+
compose,
217+
events,
218+
prev_yaw,
219+
prev_pitch,
220+
position,
221+
velocity,
222+
yaw,
223+
pitch,
224+
pending_teleport,
225+
tracking,
226+
flight,
227+
)| {
228+
let world = it.system().world();
229+
let system = it.system();
230+
let entity = it.entity(row);
231+
let entity_id = VarInt(entity.minecraft_id());
218232

233+
if let Some(pending_teleport) = pending_teleport {
234+
if pending_teleport.ttl == 0 {
235+
entity.set::<PendingTeleportation>(PendingTeleportation::new(
236+
pending_teleport.destination,
237+
));
238+
}
239+
pending_teleport.ttl -= 1;
240+
} else {
219241
let chunk_pos = position.to_chunk();
220242

221-
let position_delta = **position - **prev_position;
243+
let position_delta = **position - tracking.last_tick_position;
222244
let needs_teleport = position_delta.abs().max_element() >= 8.0;
223-
let changed_position = **position != **prev_position;
245+
let changed_position = **position != tracking.last_tick_position;
224246

225-
let look_changed = (**yaw - **prev_yaw).abs() >= 0.01 || (**pitch - **prev_pitch).abs() >= 0.01;
247+
let look_changed = (**yaw - **prev_yaw).abs() >= 0.01
248+
|| (**pitch - **prev_pitch).abs() >= 0.01;
226249

227250
let mut bundle = DataBundle::new(compose, system);
228251

252+
// Maximum number of movement packets allowed during 1 tick is 5
253+
if tracking.received_movement_packets > 5 {
254+
tracking.received_movement_packets = 1;
255+
}
256+
257+
// Replace 100 by 300 if fall flying (aka elytra)
258+
if f64::from(position_delta.length_squared())
259+
- tracking.server_velocity.length_squared()
260+
> 100f64 * f64::from(tracking.received_movement_packets)
261+
{
262+
entity.set(PendingTeleportation::new(tracking.last_tick_position));
263+
tracking.received_movement_packets = 0;
264+
return;
265+
}
266+
229267
world.get::<&mut Blocks>(|blocks| {
230268
let grounded = is_grounded(position, blocks);
269+
tracking.was_on_ground = grounded;
270+
if grounded
271+
&& !tracking.last_tick_flying
272+
&& tracking.fall_start_y - position.y > 3.
273+
{
274+
let event = HitGroundEvent {
275+
client: *entity,
276+
fall_distance: tracking.fall_start_y - position.y,
277+
};
278+
events.push(event, &world);
279+
tracking.fall_start_y = position.y;
280+
}
281+
282+
if (tracking.last_tick_flying && flight.allow) || position_delta.y >= 0. {
283+
tracking.fall_start_y = position.y;
284+
}
231285

232286
if changed_position && !needs_teleport && look_changed {
233287
let packet = play::RotateAndMoveRelativeS2c {
@@ -290,11 +344,51 @@ impl Module for EntityStateSyncModule {
290344
};
291345

292346
bundle.add_packet(&packet).unwrap();
347+
velocity.0 = Vec3::ZERO;
293348
}
294349

295350
bundle.broadcast_local(chunk_pos).unwrap();
296-
},
297-
);
351+
}
352+
353+
tracking.received_movement_packets = 0;
354+
tracking.last_tick_position = **position;
355+
tracking.last_tick_flying = flight.is_flying;
356+
357+
let mut friction = 0.91;
358+
359+
if tracking.was_on_ground {
360+
tracking.server_velocity.y = 0.;
361+
#[allow(clippy::cast_possible_truncation)]
362+
world.get::<&mut Blocks>(|blocks| {
363+
let block_x = position.x as i32;
364+
let block_y = (position.y.ceil() - 1.0) as i32; // Check the block directly below
365+
let block_z = position.z as i32;
366+
367+
if let Some(state) = blocks.get_block(IVec3::new(block_x, block_y, block_z))
368+
{
369+
let kind = state.to_kind();
370+
friction = f64::from(0.91 * kind.slipperiness() * kind.speed_factor());
371+
}
372+
});
373+
}
374+
375+
tracking.server_velocity.x *= friction * 0.98;
376+
tracking.server_velocity.y -= 0.08 * 0.980_000_019_073_486_3;
377+
tracking.server_velocity.z *= friction * 0.98;
378+
379+
if tracking.server_velocity.x.abs() < 0.003 {
380+
tracking.server_velocity.x = 0.;
381+
}
382+
383+
if tracking.server_velocity.y.abs() < 0.003 {
384+
tracking.server_velocity.y = 0.;
385+
}
386+
387+
if tracking.server_velocity.z.abs() < 0.003 {
388+
tracking.server_velocity.z = 0.;
389+
}
390+
},
391+
);
298392

299393
system!(
300394
"update_projectile_positions",

crates/hyperion/src/simulation/blocks/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,12 +359,18 @@ impl Blocks {
359359
#[must_use]
360360
pub fn get_block(&self, position: IVec3) -> Option<BlockState> {
361361
const START_Y: i32 = -64;
362+
const MAX_Y: i32 = 383;
362363

364+
// Todo should probably return none
363365
if position.y < START_Y {
364366
// This block is in the void.
365367
return Some(BlockState::VOID_AIR);
366368
}
367369

370+
if position.y > MAX_Y {
371+
return Some(BlockState::VOID_AIR);
372+
}
373+
368374
let chunk_pos: IVec2 = IVec2::new(position.x, position.z) >> 4;
369375
let chunk_start_block: IVec2 = chunk_pos << 4;
370376

crates/hyperion/src/simulation/event.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Flecs components which are used for events.
22
3-
use derive_more::Constructor;
43
use flecs_ecs::{core::Entity, macros::Component};
54
use glam::{IVec3, Vec3};
65
use hyperion_utils::{Lifetime, RuntimeLifetime};
@@ -48,12 +47,6 @@ pub struct AttackEntity {
4847
pub damage: f32,
4948
}
5049

51-
#[derive(Copy, Clone, Debug, PartialEq, Constructor)]
52-
pub struct HealthUpdate {
53-
pub from: f32,
54-
pub to: f32,
55-
}
56-
5750
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
5851
pub struct StartDestroyBlock {
5952
pub position: IVec3,
@@ -181,3 +174,10 @@ pub struct UpdateSelectedSlotEvent {
181174
pub client: Entity,
182175
pub slot: u8,
183176
}
177+
178+
#[derive(Clone, Debug)]
179+
pub struct HitGroundEvent {
180+
pub client: Entity,
181+
/// This is at least 3
182+
pub fall_distance: f32,
183+
}

0 commit comments

Comments
 (0)