1use std::backtrace::Backtrace;
2
3use azalea_core::position::Vec3;
4use azalea_core::tick::GameTick;
5use azalea_entity::{Attributes, Jumping, metadata::Sprinting};
6use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
7use azalea_physics::{PhysicsSet, ai_step};
8use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
9use azalea_protocol::packets::{
10 Packet,
11 game::{
12 s_move_player_pos::ServerboundMovePlayerPos,
13 s_move_player_pos_rot::ServerboundMovePlayerPosRot,
14 s_move_player_rot::ServerboundMovePlayerRot,
15 s_move_player_status_only::ServerboundMovePlayerStatusOnly,
16 },
17};
18use azalea_world::{MinecraftEntityId, MoveEntityError};
19use bevy_app::{App, Plugin, Update};
20use bevy_ecs::prelude::*;
21use thiserror::Error;
22
23use crate::client::Client;
24use crate::packet::game::SendPacketEvent;
25
26#[derive(Error, Debug)]
27pub enum MovePlayerError {
28 #[error("Player is not in world")]
29 PlayerNotInWorld(Backtrace),
30 #[error("{0}")]
31 Io(#[from] std::io::Error),
32}
33
34impl From<MoveEntityError> for MovePlayerError {
35 fn from(err: MoveEntityError) -> Self {
36 match err {
37 MoveEntityError::EntityDoesNotExist(backtrace) => {
38 MovePlayerError::PlayerNotInWorld(backtrace)
39 }
40 }
41 }
42}
43
44pub struct MovementPlugin;
45
46impl Plugin for MovementPlugin {
47 fn build(&self, app: &mut App) {
48 app.add_event::<StartWalkEvent>()
49 .add_event::<StartSprintEvent>()
50 .add_event::<KnockbackEvent>()
51 .add_systems(
52 Update,
53 (handle_sprint, handle_walk, handle_knockback)
54 .chain()
55 .in_set(MoveEventsSet),
56 )
57 .add_systems(
58 GameTick,
59 (
60 (tick_controls, local_player_ai_step)
61 .chain()
62 .in_set(PhysicsSet)
63 .before(ai_step)
64 .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
65 send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
66 send_player_input_packet,
67 send_position.after(PhysicsSet),
68 )
69 .chain(),
70 );
71 }
72}
73
74#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
75pub struct MoveEventsSet;
76
77impl Client {
78 pub fn set_jumping(&self, jumping: bool) {
84 let mut ecs = self.ecs.lock();
85 let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs);
86 **jumping_mut = jumping;
87 }
88
89 pub fn jumping(&self) -> bool {
91 *self.component::<Jumping>()
92 }
93
94 pub fn set_direction(&self, y_rot: f32, x_rot: f32) {
99 let mut ecs = self.ecs.lock();
100 let mut look_direction = self.query::<&mut LookDirection>(&mut ecs);
101
102 (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
103 }
104
105 pub fn direction(&self) -> (f32, f32) {
109 let look_direction = self.component::<LookDirection>();
110 (look_direction.y_rot, look_direction.x_rot)
111 }
112}
113
114#[derive(Debug, Component, Clone, Default)]
117pub struct LastSentLookDirection {
118 pub x_rot: f32,
119 pub y_rot: f32,
120}
121
122#[derive(Default, Component, Clone)]
127pub struct PhysicsState {
128 pub position_remainder: u32,
131 pub was_sprinting: bool,
132 pub trying_to_sprint: bool,
135
136 pub move_direction: WalkDirection,
137 pub forward_impulse: f32,
138 pub left_impulse: f32,
139}
140
141#[allow(clippy::type_complexity)]
142pub fn send_position(
143 mut query: Query<
144 (
145 Entity,
146 &Position,
147 &LookDirection,
148 &mut PhysicsState,
149 &mut LastSentPosition,
150 &mut Physics,
151 &mut LastSentLookDirection,
152 ),
153 With<InLoadedChunk>,
154 >,
155 mut commands: Commands,
156) {
157 for (
158 entity,
159 position,
160 direction,
161 mut physics_state,
162 mut last_sent_position,
163 mut physics,
164 mut last_direction,
165 ) in query.iter_mut()
166 {
167 let packet = {
168 let x_delta = position.x - last_sent_position.x;
172 let y_delta = position.y - last_sent_position.y;
173 let z_delta = position.z - last_sent_position.z;
174 let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
175 let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
176
177 physics_state.position_remainder += 1;
178
179 let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
182 > 2.0e-4f64.powi(2))
183 || physics_state.position_remainder >= 20;
184 let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
185
186 let packet = if sending_position && sending_direction {
190 Some(
191 ServerboundMovePlayerPosRot {
192 pos: **position,
193 look_direction: *direction,
194 on_ground: physics.on_ground(),
195 }
196 .into_variant(),
197 )
198 } else if sending_position {
199 Some(
200 ServerboundMovePlayerPos {
201 pos: **position,
202 on_ground: physics.on_ground(),
203 }
204 .into_variant(),
205 )
206 } else if sending_direction {
207 Some(
208 ServerboundMovePlayerRot {
209 look_direction: *direction,
210 on_ground: physics.on_ground(),
211 }
212 .into_variant(),
213 )
214 } else if physics.last_on_ground() != physics.on_ground() {
215 Some(
216 ServerboundMovePlayerStatusOnly {
217 on_ground: physics.on_ground(),
218 }
219 .into_variant(),
220 )
221 } else {
222 None
223 };
224
225 if sending_position {
226 **last_sent_position = **position;
227 physics_state.position_remainder = 0;
228 }
229 if sending_direction {
230 last_direction.y_rot = direction.y_rot;
231 last_direction.x_rot = direction.x_rot;
232 }
233
234 let on_ground = physics.on_ground();
235 physics.set_last_on_ground(on_ground);
236 packet
239 };
240
241 if let Some(packet) = packet {
242 commands.trigger(SendPacketEvent {
243 sent_by: entity,
244 packet,
245 });
246 }
247 }
248}
249
250#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
251pub struct LastSentInput(pub ServerboundPlayerInput);
252pub fn send_player_input_packet(
253 mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
254 mut commands: Commands,
255) {
256 for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
257 let dir = physics_state.move_direction;
258 type D = WalkDirection;
259 let input = ServerboundPlayerInput {
260 forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
261 backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
262 left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
263 right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
264 jump: **jumping,
265 shift: false,
267 sprint: physics_state.trying_to_sprint,
268 };
269
270 let last_sent_input = last_sent_input.cloned().unwrap_or_default();
273
274 if input != last_sent_input.0 {
275 commands.trigger(SendPacketEvent {
276 sent_by: entity,
277 packet: input.clone().into_variant(),
278 });
279 commands.entity(entity).insert(LastSentInput(input));
280 }
281 }
282}
283
284pub fn send_sprinting_if_needed(
285 mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
286 mut commands: Commands,
287) {
288 for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
289 let was_sprinting = physics_state.was_sprinting;
290 if **sprinting != was_sprinting {
291 let sprinting_action = if **sprinting {
292 azalea_protocol::packets::game::s_player_command::Action::StartSprinting
293 } else {
294 azalea_protocol::packets::game::s_player_command::Action::StopSprinting
295 };
296 commands.trigger(SendPacketEvent::new(
297 entity,
298 ServerboundPlayerCommand {
299 id: *minecraft_entity_id,
300 action: sprinting_action,
301 data: 0,
302 },
303 ));
304 physics_state.was_sprinting = **sprinting;
305 }
306 }
307}
308
309pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
312 for mut physics_state in query.iter_mut() {
313 let multiplier: Option<f32> = None;
314
315 let mut forward_impulse: f32 = 0.;
316 let mut left_impulse: f32 = 0.;
317 let move_direction = physics_state.move_direction;
318 match move_direction {
319 WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
320 forward_impulse += 1.;
321 }
322 WalkDirection::Backward
323 | WalkDirection::BackwardRight
324 | WalkDirection::BackwardLeft => {
325 forward_impulse -= 1.;
326 }
327 _ => {}
328 };
329 match move_direction {
330 WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
331 left_impulse += 1.;
332 }
333 WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
334 left_impulse -= 1.;
335 }
336 _ => {}
337 };
338 physics_state.forward_impulse = forward_impulse;
339 physics_state.left_impulse = left_impulse;
340
341 if let Some(multiplier) = multiplier {
342 physics_state.forward_impulse *= multiplier;
343 physics_state.left_impulse *= multiplier;
344 }
345 }
346}
347
348pub fn local_player_ai_step(
351 mut query: Query<
352 (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
353 With<InLoadedChunk>,
354 >,
355) {
356 for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
357 physics.x_acceleration = physics_state.left_impulse;
359 physics.z_acceleration = physics_state.forward_impulse;
360
361 let has_enough_food_to_sprint = true;
365
366 let trying_to_sprint = physics_state.trying_to_sprint;
369
370 if !**sprinting
371 && (
372 has_enough_impulse_to_start_sprinting(physics_state)
375 && has_enough_food_to_sprint
376 && trying_to_sprint
379 )
380 {
381 set_sprinting(true, &mut sprinting, &mut attributes);
382 }
383 }
384}
385
386impl Client {
387 pub fn walk(&self, direction: WalkDirection) {
404 let mut ecs = self.ecs.lock();
405 ecs.send_event(StartWalkEvent {
406 entity: self.entity,
407 direction,
408 });
409 }
410
411 pub fn sprint(&self, direction: SprintDirection) {
427 let mut ecs = self.ecs.lock();
428 ecs.send_event(StartSprintEvent {
429 entity: self.entity,
430 direction,
431 });
432 }
433}
434
435#[derive(Event, Debug)]
440pub struct StartWalkEvent {
441 pub entity: Entity,
442 pub direction: WalkDirection,
443}
444
445pub fn handle_walk(
448 mut events: EventReader<StartWalkEvent>,
449 mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
450) {
451 for event in events.read() {
452 if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
453 {
454 physics_state.move_direction = event.direction;
455 physics_state.trying_to_sprint = false;
456 set_sprinting(false, &mut sprinting, &mut attributes);
457 }
458 }
459}
460
461#[derive(Event)]
464pub struct StartSprintEvent {
465 pub entity: Entity,
466 pub direction: SprintDirection,
467}
468pub fn handle_sprint(
471 mut query: Query<&mut PhysicsState>,
472 mut events: EventReader<StartSprintEvent>,
473) {
474 for event in events.read() {
475 if let Ok(mut physics_state) = query.get_mut(event.entity) {
476 physics_state.move_direction = WalkDirection::from(event.direction);
477 physics_state.trying_to_sprint = true;
478 }
479 }
480}
481
482fn set_sprinting(
486 sprinting: bool,
487 currently_sprinting: &mut Sprinting,
488 attributes: &mut Attributes,
489) -> bool {
490 **currently_sprinting = sprinting;
491 if sprinting {
492 attributes
493 .speed
494 .try_insert(azalea_entity::attributes::sprinting_modifier())
495 .is_ok()
496 } else {
497 attributes
498 .speed
499 .remove(&azalea_entity::attributes::sprinting_modifier().id)
500 .is_none()
501 }
502}
503
504fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
506 physics_state.forward_impulse > 0.8
510 }
512
513#[derive(Event)]
518pub struct KnockbackEvent {
519 pub entity: Entity,
520 pub knockback: KnockbackType,
521}
522
523pub enum KnockbackType {
524 Set(Vec3),
525 Add(Vec3),
526}
527
528pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
529 for event in events.read() {
530 if let Ok(mut physics) = query.get_mut(event.entity) {
531 match event.knockback {
532 KnockbackType::Set(velocity) => {
533 physics.velocity = velocity;
534 }
535 KnockbackType::Add(velocity) => {
536 physics.velocity += velocity;
537 }
538 }
539 }
540 }
541}
542
543#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
544pub enum WalkDirection {
545 #[default]
546 None,
547 Forward,
548 Backward,
549 Left,
550 Right,
551 ForwardRight,
552 ForwardLeft,
553 BackwardRight,
554 BackwardLeft,
555}
556
557#[derive(Clone, Copy, Debug)]
559pub enum SprintDirection {
560 Forward,
561 ForwardRight,
562 ForwardLeft,
563}
564
565impl From<SprintDirection> for WalkDirection {
566 fn from(d: SprintDirection) -> Self {
567 match d {
568 SprintDirection::Forward => WalkDirection::Forward,
569 SprintDirection::ForwardRight => WalkDirection::ForwardRight,
570 SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
571 }
572 }
573}