1use std::{backtrace::Backtrace, io};
2
3use azalea_core::{
4 position::{Vec2, Vec3},
5 tick::GameTick,
6};
7use azalea_entity::{
8 Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position,
9 metadata::Sprinting,
10};
11use azalea_physics::{PhysicsSet, ai_step};
12use azalea_protocol::{
13 common::movements::MoveFlags,
14 packets::{
15 Packet,
16 game::{
17 ServerboundPlayerCommand, ServerboundPlayerInput,
18 s_move_player_pos::ServerboundMovePlayerPos,
19 s_move_player_pos_rot::ServerboundMovePlayerPosRot,
20 s_move_player_rot::ServerboundMovePlayerRot,
21 s_move_player_status_only::ServerboundMovePlayerStatusOnly,
22 },
23 },
24};
25use azalea_world::{MinecraftEntityId, MoveEntityError};
26use bevy_app::{App, Plugin, Update};
27use bevy_ecs::prelude::*;
28use thiserror::Error;
29
30use crate::{client::Client, packet::game::SendPacketEvent};
31
32#[derive(Error, Debug)]
33pub enum MovePlayerError {
34 #[error("Player is not in world")]
35 PlayerNotInWorld(Backtrace),
36 #[error("{0}")]
37 Io(#[from] io::Error),
38}
39
40impl From<MoveEntityError> for MovePlayerError {
41 fn from(err: MoveEntityError) -> Self {
42 match err {
43 MoveEntityError::EntityDoesNotExist(backtrace) => {
44 MovePlayerError::PlayerNotInWorld(backtrace)
45 }
46 }
47 }
48}
49
50pub struct MovementPlugin;
51
52impl Plugin for MovementPlugin {
53 fn build(&self, app: &mut App) {
54 app.add_event::<StartWalkEvent>()
55 .add_event::<StartSprintEvent>()
56 .add_event::<KnockbackEvent>()
57 .add_systems(
58 Update,
59 (handle_sprint, handle_walk, handle_knockback)
60 .chain()
61 .in_set(MoveEventsSet),
62 )
63 .add_systems(
64 GameTick,
65 (
66 (tick_controls, local_player_ai_step)
67 .chain()
68 .in_set(PhysicsSet)
69 .before(ai_step)
70 .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
71 send_player_input_packet,
72 send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
73 send_position.after(PhysicsSet),
74 )
75 .chain(),
76 );
77 }
78}
79
80#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
81pub struct MoveEventsSet;
82
83impl Client {
84 pub fn set_jumping(&self, jumping: bool) {
90 let mut ecs = self.ecs.lock();
91 let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs);
92 **jumping_mut = jumping;
93 }
94
95 pub fn jumping(&self) -> bool {
97 *self.component::<Jumping>()
98 }
99
100 pub fn set_direction(&self, y_rot: f32, x_rot: f32) {
105 let mut ecs = self.ecs.lock();
106 let mut look_direction = self.query::<&mut LookDirection>(&mut ecs);
107
108 (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
109 }
110
111 pub fn direction(&self) -> (f32, f32) {
115 let look_direction = self.component::<LookDirection>();
116 (look_direction.y_rot, look_direction.x_rot)
117 }
118}
119
120#[derive(Debug, Component, Clone, Default)]
123pub struct LastSentLookDirection {
124 pub x_rot: f32,
125 pub y_rot: f32,
126}
127
128#[derive(Default, Component, Clone)]
133pub struct PhysicsState {
134 pub position_remainder: u32,
137 pub was_sprinting: bool,
138 pub trying_to_sprint: bool,
141
142 pub move_direction: WalkDirection,
143 pub move_vector: Vec2,
144}
145
146#[allow(clippy::type_complexity)]
147pub fn send_position(
148 mut query: Query<
149 (
150 Entity,
151 &Position,
152 &LookDirection,
153 &mut PhysicsState,
154 &mut LastSentPosition,
155 &mut Physics,
156 &mut LastSentLookDirection,
157 ),
158 With<InLoadedChunk>,
159 >,
160 mut commands: Commands,
161) {
162 for (
163 entity,
164 position,
165 direction,
166 mut physics_state,
167 mut last_sent_position,
168 mut physics,
169 mut last_direction,
170 ) in query.iter_mut()
171 {
172 let packet = {
173 let x_delta = position.x - last_sent_position.x;
177 let y_delta = position.y - last_sent_position.y;
178 let z_delta = position.z - last_sent_position.z;
179 let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
180 let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
181
182 physics_state.position_remainder += 1;
183
184 let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
187 > 2.0e-4f64.powi(2))
188 || physics_state.position_remainder >= 20;
189 let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
190
191 let flags = MoveFlags {
195 on_ground: physics.on_ground(),
196 horizontal_collision: physics.horizontal_collision,
197 };
198 let packet = if sending_position && sending_direction {
199 Some(
200 ServerboundMovePlayerPosRot {
201 pos: **position,
202 look_direction: *direction,
203 flags,
204 }
205 .into_variant(),
206 )
207 } else if sending_position {
208 Some(
209 ServerboundMovePlayerPos {
210 pos: **position,
211 flags,
212 }
213 .into_variant(),
214 )
215 } else if sending_direction {
216 Some(
217 ServerboundMovePlayerRot {
218 look_direction: *direction,
219 flags,
220 }
221 .into_variant(),
222 )
223 } else if physics.last_on_ground() != physics.on_ground() {
224 Some(ServerboundMovePlayerStatusOnly { flags }.into_variant())
225 } else {
226 None
227 };
228
229 if sending_position {
230 **last_sent_position = **position;
231 physics_state.position_remainder = 0;
232 }
233 if sending_direction {
234 last_direction.y_rot = direction.y_rot;
235 last_direction.x_rot = direction.x_rot;
236 }
237
238 let on_ground = physics.on_ground();
239 physics.set_last_on_ground(on_ground);
240 packet
243 };
244
245 if let Some(packet) = packet {
246 commands.trigger(SendPacketEvent {
247 sent_by: entity,
248 packet,
249 });
250 }
251 }
252}
253
254#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
255pub struct LastSentInput(pub ServerboundPlayerInput);
256pub fn send_player_input_packet(
257 mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
258 mut commands: Commands,
259) {
260 for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
261 let dir = physics_state.move_direction;
262 type D = WalkDirection;
263 let input = ServerboundPlayerInput {
264 forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
265 backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
266 left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
267 right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
268 jump: **jumping,
269 shift: false,
271 sprint: physics_state.trying_to_sprint,
272 };
273
274 let last_sent_input = last_sent_input.cloned().unwrap_or_default();
277
278 if input != last_sent_input.0 {
279 commands.trigger(SendPacketEvent {
280 sent_by: entity,
281 packet: input.clone().into_variant(),
282 });
283 commands.entity(entity).insert(LastSentInput(input));
284 }
285 }
286}
287
288pub fn send_sprinting_if_needed(
289 mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
290 mut commands: Commands,
291) {
292 for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
293 let was_sprinting = physics_state.was_sprinting;
294 if **sprinting != was_sprinting {
295 let sprinting_action = if **sprinting {
296 azalea_protocol::packets::game::s_player_command::Action::StartSprinting
297 } else {
298 azalea_protocol::packets::game::s_player_command::Action::StopSprinting
299 };
300 commands.trigger(SendPacketEvent::new(
301 entity,
302 ServerboundPlayerCommand {
303 id: *minecraft_entity_id,
304 action: sprinting_action,
305 data: 0,
306 },
307 ));
308 physics_state.was_sprinting = **sprinting;
309 }
310 }
311}
312
313pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
316 for mut physics_state in query.iter_mut() {
317 let mut forward_impulse: f32 = 0.;
318 let mut left_impulse: f32 = 0.;
319 let move_direction = physics_state.move_direction;
320 match move_direction {
321 WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
322 forward_impulse += 1.;
323 }
324 WalkDirection::Backward
325 | WalkDirection::BackwardRight
326 | WalkDirection::BackwardLeft => {
327 forward_impulse -= 1.;
328 }
329 _ => {}
330 };
331 match move_direction {
332 WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
333 left_impulse += 1.;
334 }
335 WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
336 left_impulse -= 1.;
337 }
338 _ => {}
339 };
340
341 let move_vector = Vec2::new(left_impulse, forward_impulse).normalized();
342 physics_state.move_vector = move_vector;
343 }
344}
345
346pub fn local_player_ai_step(
349 mut query: Query<
350 (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
351 With<InLoadedChunk>,
352 >,
353) {
354 for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
355 let move_vector = modify_input(physics_state.move_vector, false, false, false, &attributes);
360 physics.x_acceleration = move_vector.x;
361 physics.z_acceleration = move_vector.y;
362
363 let has_enough_food_to_sprint = true;
367
368 let trying_to_sprint = physics_state.trying_to_sprint;
371
372 if !**sprinting
373 && (
374 has_enough_impulse_to_start_sprinting(physics_state)
377 && has_enough_food_to_sprint
378 && trying_to_sprint
381 )
382 {
383 set_sprinting(true, &mut sprinting, &mut attributes);
384 }
385 }
386}
387
388fn modify_input(
390 mut move_vector: Vec2,
391 is_using_item: bool,
392 is_passenger: bool,
393 moving_slowly: bool,
394 attributes: &Attributes,
395) -> Vec2 {
396 if move_vector.length_squared() == 0. {
397 return move_vector;
398 }
399
400 move_vector *= 0.98;
401 if is_using_item && !is_passenger {
402 move_vector *= 0.2;
403 }
404
405 if moving_slowly {
406 let sneaking_speed = attributes.sneaking_speed.calculate() as f32;
407 move_vector *= sneaking_speed;
408 }
409
410 modify_input_speed_for_square_movement(move_vector)
411}
412fn modify_input_speed_for_square_movement(move_vector: Vec2) -> Vec2 {
413 let length = move_vector.length();
414 if length == 0. {
415 return move_vector;
416 }
417 let scaled_to_inverse_length = move_vector * (1. / length);
418 let dist = distance_to_unit_square(scaled_to_inverse_length);
419 let scale = (length * dist).min(1.);
420 scaled_to_inverse_length * scale
421}
422fn distance_to_unit_square(v: Vec2) -> f32 {
423 let x = v.x.abs();
424 let y = v.y.abs();
425 let ratio = if y > x { x / y } else { y / x };
426 (1.0 + ratio * ratio).sqrt()
427}
428
429impl Client {
430 pub fn walk(&self, direction: WalkDirection) {
447 let mut ecs = self.ecs.lock();
448 ecs.send_event(StartWalkEvent {
449 entity: self.entity,
450 direction,
451 });
452 }
453
454 pub fn sprint(&self, direction: SprintDirection) {
470 let mut ecs = self.ecs.lock();
471 ecs.send_event(StartSprintEvent {
472 entity: self.entity,
473 direction,
474 });
475 }
476}
477
478#[derive(Event, Debug)]
483pub struct StartWalkEvent {
484 pub entity: Entity,
485 pub direction: WalkDirection,
486}
487
488pub fn handle_walk(
491 mut events: EventReader<StartWalkEvent>,
492 mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
493) {
494 for event in events.read() {
495 if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
496 {
497 physics_state.move_direction = event.direction;
498 physics_state.trying_to_sprint = false;
499 set_sprinting(false, &mut sprinting, &mut attributes);
500 }
501 }
502}
503
504#[derive(Event)]
507pub struct StartSprintEvent {
508 pub entity: Entity,
509 pub direction: SprintDirection,
510}
511pub fn handle_sprint(
514 mut query: Query<&mut PhysicsState>,
515 mut events: EventReader<StartSprintEvent>,
516) {
517 for event in events.read() {
518 if let Ok(mut physics_state) = query.get_mut(event.entity) {
519 physics_state.move_direction = WalkDirection::from(event.direction);
520 physics_state.trying_to_sprint = true;
521 }
522 }
523}
524
525fn set_sprinting(
529 sprinting: bool,
530 currently_sprinting: &mut Sprinting,
531 attributes: &mut Attributes,
532) -> bool {
533 **currently_sprinting = sprinting;
534 if sprinting {
535 attributes
536 .speed
537 .try_insert(azalea_entity::attributes::sprinting_modifier())
538 .is_ok()
539 } else {
540 attributes
541 .speed
542 .remove(&azalea_entity::attributes::sprinting_modifier().id)
543 .is_none()
544 }
545}
546
547fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
549 physics_state.move_vector.y > 0.8
553 }
555
556#[derive(Event)]
561pub struct KnockbackEvent {
562 pub entity: Entity,
563 pub knockback: KnockbackType,
564}
565
566pub enum KnockbackType {
567 Set(Vec3),
568 Add(Vec3),
569}
570
571pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
572 for event in events.read() {
573 if let Ok(mut physics) = query.get_mut(event.entity) {
574 match event.knockback {
575 KnockbackType::Set(velocity) => {
576 physics.velocity = velocity;
577 }
578 KnockbackType::Add(velocity) => {
579 physics.velocity += velocity;
580 }
581 }
582 }
583 }
584}
585
586#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
587pub enum WalkDirection {
588 #[default]
589 None,
590 Forward,
591 Backward,
592 Left,
593 Right,
594 ForwardRight,
595 ForwardLeft,
596 BackwardRight,
597 BackwardLeft,
598}
599
600#[derive(Clone, Copy, Debug)]
602pub enum SprintDirection {
603 Forward,
604 ForwardRight,
605 ForwardLeft,
606}
607
608impl From<SprintDirection> for WalkDirection {
609 fn from(d: SprintDirection) -> Self {
610 match d {
611 SprintDirection::Forward => WalkDirection::Forward,
612 SprintDirection::ForwardRight => WalkDirection::ForwardRight,
613 SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
614 }
615 }
616}