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::Event;
21use bevy_ecs::schedule::SystemSet;
22use bevy_ecs::system::Commands;
23use bevy_ecs::{
24 component::Component, entity::Entity, event::EventReader, query::With,
25 schedule::IntoSystemConfigs, system::Query,
26};
27use thiserror::Error;
28
29use crate::client::Client;
30use crate::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] std::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_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
72 send_player_input_packet,
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(&mut 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(&mut 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 forward_impulse: f32,
144 pub left_impulse: f32,
145}
146
147#[allow(clippy::type_complexity)]
148pub fn send_position(
149 mut query: Query<
150 (
151 Entity,
152 &Position,
153 &LookDirection,
154 &mut PhysicsState,
155 &mut LastSentPosition,
156 &mut Physics,
157 &mut LastSentLookDirection,
158 ),
159 With<InLoadedChunk>,
160 >,
161 mut commands: Commands,
162) {
163 for (
164 entity,
165 position,
166 direction,
167 mut physics_state,
168 mut last_sent_position,
169 mut physics,
170 mut last_direction,
171 ) in query.iter_mut()
172 {
173 let packet = {
174 let x_delta = position.x - last_sent_position.x;
178 let y_delta = position.y - last_sent_position.y;
179 let z_delta = position.z - last_sent_position.z;
180 let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
181 let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
182
183 physics_state.position_remainder += 1;
184
185 let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
188 > 2.0e-4f64.powi(2))
189 || physics_state.position_remainder >= 20;
190 let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
191
192 let packet = if sending_position && sending_direction {
196 Some(
197 ServerboundMovePlayerPosRot {
198 pos: **position,
199 look_direction: *direction,
200 on_ground: physics.on_ground(),
201 }
202 .into_variant(),
203 )
204 } else if sending_position {
205 Some(
206 ServerboundMovePlayerPos {
207 pos: **position,
208 on_ground: physics.on_ground(),
209 }
210 .into_variant(),
211 )
212 } else if sending_direction {
213 Some(
214 ServerboundMovePlayerRot {
215 look_direction: *direction,
216 on_ground: physics.on_ground(),
217 }
218 .into_variant(),
219 )
220 } else if physics.last_on_ground() != physics.on_ground() {
221 Some(
222 ServerboundMovePlayerStatusOnly {
223 on_ground: physics.on_ground(),
224 }
225 .into_variant(),
226 )
227 } else {
228 None
229 };
230
231 if sending_position {
232 **last_sent_position = **position;
233 physics_state.position_remainder = 0;
234 }
235 if sending_direction {
236 last_direction.y_rot = direction.y_rot;
237 last_direction.x_rot = direction.x_rot;
238 }
239
240 let on_ground = physics.on_ground();
241 physics.set_last_on_ground(on_ground);
242 packet
245 };
246
247 if let Some(packet) = packet {
248 commands.trigger(SendPacketEvent {
249 sent_by: entity,
250 packet,
251 });
252 }
253 }
254}
255
256#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
257pub struct LastSentInput(pub ServerboundPlayerInput);
258pub fn send_player_input_packet(
259 mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
260 mut commands: Commands,
261) {
262 for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
263 let dir = physics_state.move_direction;
264 type D = WalkDirection;
265 let input = ServerboundPlayerInput {
266 forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
267 backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
268 left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
269 right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
270 jump: **jumping,
271 shift: false,
273 sprint: physics_state.trying_to_sprint,
274 };
275
276 let last_sent_input = last_sent_input.cloned().unwrap_or_default();
279
280 if input != last_sent_input.0 {
281 commands.trigger(SendPacketEvent {
282 sent_by: entity,
283 packet: input.clone().into_variant(),
284 });
285 commands.entity(entity).insert(LastSentInput(input));
286 }
287 }
288}
289
290fn send_sprinting_if_needed(
291 mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
292 mut commands: Commands,
293) {
294 for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
295 let was_sprinting = physics_state.was_sprinting;
296 if **sprinting != was_sprinting {
297 let sprinting_action = if **sprinting {
298 azalea_protocol::packets::game::s_player_command::Action::StartSprinting
299 } else {
300 azalea_protocol::packets::game::s_player_command::Action::StopSprinting
301 };
302 commands.trigger(SendPacketEvent::new(
303 entity,
304 ServerboundPlayerCommand {
305 id: *minecraft_entity_id,
306 action: sprinting_action,
307 data: 0,
308 },
309 ));
310 physics_state.was_sprinting = **sprinting;
311 }
312 }
313}
314
315pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
318 for mut physics_state in query.iter_mut() {
319 let multiplier: Option<f32> = None;
320
321 let mut forward_impulse: f32 = 0.;
322 let mut left_impulse: f32 = 0.;
323 let move_direction = physics_state.move_direction;
324 match move_direction {
325 WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
326 forward_impulse += 1.;
327 }
328 WalkDirection::Backward
329 | WalkDirection::BackwardRight
330 | WalkDirection::BackwardLeft => {
331 forward_impulse -= 1.;
332 }
333 _ => {}
334 };
335 match move_direction {
336 WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
337 left_impulse += 1.;
338 }
339 WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
340 left_impulse -= 1.;
341 }
342 _ => {}
343 };
344 physics_state.forward_impulse = forward_impulse;
345 physics_state.left_impulse = left_impulse;
346
347 if let Some(multiplier) = multiplier {
348 physics_state.forward_impulse *= multiplier;
349 physics_state.left_impulse *= multiplier;
350 }
351 }
352}
353
354pub fn local_player_ai_step(
357 mut query: Query<
358 (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
359 With<InLoadedChunk>,
360 >,
361) {
362 for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
363 physics.x_acceleration = physics_state.left_impulse;
365 physics.z_acceleration = physics_state.forward_impulse;
366
367 let has_enough_food_to_sprint = true;
371
372 let trying_to_sprint = physics_state.trying_to_sprint;
375
376 if !**sprinting
377 && (
378 has_enough_impulse_to_start_sprinting(physics_state)
381 && has_enough_food_to_sprint
382 && trying_to_sprint
385 )
386 {
387 set_sprinting(true, &mut sprinting, &mut attributes);
388 }
389 }
390}
391
392impl Client {
393 pub fn walk(&mut self, direction: WalkDirection) {
410 let mut ecs = self.ecs.lock();
411 ecs.send_event(StartWalkEvent {
412 entity: self.entity,
413 direction,
414 });
415 }
416
417 pub fn sprint(&mut self, direction: SprintDirection) {
433 let mut ecs = self.ecs.lock();
434 ecs.send_event(StartSprintEvent {
435 entity: self.entity,
436 direction,
437 });
438 }
439}
440
441#[derive(Event, Debug)]
446pub struct StartWalkEvent {
447 pub entity: Entity,
448 pub direction: WalkDirection,
449}
450
451pub fn handle_walk(
454 mut events: EventReader<StartWalkEvent>,
455 mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
456) {
457 for event in events.read() {
458 if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
459 {
460 physics_state.move_direction = event.direction;
461 physics_state.trying_to_sprint = false;
462 set_sprinting(false, &mut sprinting, &mut attributes);
463 }
464 }
465}
466
467#[derive(Event)]
470pub struct StartSprintEvent {
471 pub entity: Entity,
472 pub direction: SprintDirection,
473}
474pub fn handle_sprint(
477 mut query: Query<&mut PhysicsState>,
478 mut events: EventReader<StartSprintEvent>,
479) {
480 for event in events.read() {
481 if let Ok(mut physics_state) = query.get_mut(event.entity) {
482 physics_state.move_direction = WalkDirection::from(event.direction);
483 physics_state.trying_to_sprint = true;
484 }
485 }
486}
487
488fn set_sprinting(
492 sprinting: bool,
493 currently_sprinting: &mut Sprinting,
494 attributes: &mut Attributes,
495) -> bool {
496 **currently_sprinting = sprinting;
497 if sprinting {
498 attributes
499 .speed
500 .try_insert(azalea_entity::attributes::sprinting_modifier())
501 .is_ok()
502 } else {
503 attributes
504 .speed
505 .remove(&azalea_entity::attributes::sprinting_modifier().id)
506 .is_none()
507 }
508}
509
510fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
512 physics_state.forward_impulse > 0.8
516 }
518
519#[derive(Event)]
524pub struct KnockbackEvent {
525 pub entity: Entity,
526 pub knockback: KnockbackType,
527}
528
529pub enum KnockbackType {
530 Set(Vec3),
531 Add(Vec3),
532}
533
534pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
535 for event in events.read() {
536 if let Ok(mut physics) = query.get_mut(event.entity) {
537 match event.knockback {
538 KnockbackType::Set(velocity) => {
539 physics.velocity = velocity;
540 }
541 KnockbackType::Add(velocity) => {
542 physics.velocity += velocity;
543 }
544 }
545 }
546 }
547}
548
549#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
550pub enum WalkDirection {
551 #[default]
552 None,
553 Forward,
554 Backward,
555 Left,
556 Right,
557 ForwardRight,
558 ForwardLeft,
559 BackwardRight,
560 BackwardLeft,
561}
562
563#[derive(Clone, Copy, Debug)]
565pub enum SprintDirection {
566 Forward,
567 ForwardRight,
568 ForwardLeft,
569}
570
571impl From<SprintDirection> for WalkDirection {
572 fn from(d: SprintDirection) -> Self {
573 match d {
574 SprintDirection::Forward => WalkDirection::Forward,
575 SprintDirection::ForwardRight => WalkDirection::ForwardRight,
576 SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
577 }
578 }
579}