1use std::{backtrace::Backtrace, io};
2
3use azalea_core::{position::Vec3, tick::GameTick};
4use azalea_entity::{
5 Attributes, InLoadedChunk, Jumping, LastSentPosition, LookDirection, Physics, Position,
6 metadata::Sprinting,
7};
8use azalea_physics::{PhysicsSet, ai_step};
9use azalea_protocol::{
10 common::movements::MoveFlags,
11 packets::{
12 Packet,
13 game::{
14 ServerboundPlayerCommand, ServerboundPlayerInput,
15 s_move_player_pos::ServerboundMovePlayerPos,
16 s_move_player_pos_rot::ServerboundMovePlayerPosRot,
17 s_move_player_rot::ServerboundMovePlayerRot,
18 s_move_player_status_only::ServerboundMovePlayerStatusOnly,
19 },
20 },
21};
22use azalea_world::{MinecraftEntityId, MoveEntityError};
23use bevy_app::{App, Plugin, Update};
24use bevy_ecs::prelude::*;
25use thiserror::Error;
26
27use crate::{client::Client, packet::game::SendPacketEvent};
28
29#[derive(Error, Debug)]
30pub enum MovePlayerError {
31 #[error("Player is not in world")]
32 PlayerNotInWorld(Backtrace),
33 #[error("{0}")]
34 Io(#[from] io::Error),
35}
36
37impl From<MoveEntityError> for MovePlayerError {
38 fn from(err: MoveEntityError) -> Self {
39 match err {
40 MoveEntityError::EntityDoesNotExist(backtrace) => {
41 MovePlayerError::PlayerNotInWorld(backtrace)
42 }
43 }
44 }
45}
46
47pub struct MovementPlugin;
48
49impl Plugin for MovementPlugin {
50 fn build(&self, app: &mut App) {
51 app.add_event::<StartWalkEvent>()
52 .add_event::<StartSprintEvent>()
53 .add_event::<KnockbackEvent>()
54 .add_systems(
55 Update,
56 (handle_sprint, handle_walk, handle_knockback)
57 .chain()
58 .in_set(MoveEventsSet),
59 )
60 .add_systems(
61 GameTick,
62 (
63 (tick_controls, local_player_ai_step)
64 .chain()
65 .in_set(PhysicsSet)
66 .before(ai_step)
67 .before(azalea_physics::fluids::update_in_water_state_and_do_fluid_pushing),
68 send_player_input_packet,
69 send_sprinting_if_needed.after(azalea_entity::update_in_loaded_chunk),
70 send_position.after(PhysicsSet),
71 )
72 .chain(),
73 );
74 }
75}
76
77#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
78pub struct MoveEventsSet;
79
80impl Client {
81 pub fn set_jumping(&self, jumping: bool) {
87 let mut ecs = self.ecs.lock();
88 let mut jumping_mut = self.query::<&mut Jumping>(&mut ecs);
89 **jumping_mut = jumping;
90 }
91
92 pub fn jumping(&self) -> bool {
94 *self.component::<Jumping>()
95 }
96
97 pub fn set_direction(&self, y_rot: f32, x_rot: f32) {
102 let mut ecs = self.ecs.lock();
103 let mut look_direction = self.query::<&mut LookDirection>(&mut ecs);
104
105 (look_direction.y_rot, look_direction.x_rot) = (y_rot, x_rot);
106 }
107
108 pub fn direction(&self) -> (f32, f32) {
112 let look_direction = self.component::<LookDirection>();
113 (look_direction.y_rot, look_direction.x_rot)
114 }
115}
116
117#[derive(Debug, Component, Clone, Default)]
120pub struct LastSentLookDirection {
121 pub x_rot: f32,
122 pub y_rot: f32,
123}
124
125#[derive(Default, Component, Clone)]
130pub struct PhysicsState {
131 pub position_remainder: u32,
134 pub was_sprinting: bool,
135 pub trying_to_sprint: bool,
138
139 pub move_direction: WalkDirection,
140 pub forward_impulse: f32,
141 pub left_impulse: f32,
142}
143
144#[allow(clippy::type_complexity)]
145pub fn send_position(
146 mut query: Query<
147 (
148 Entity,
149 &Position,
150 &LookDirection,
151 &mut PhysicsState,
152 &mut LastSentPosition,
153 &mut Physics,
154 &mut LastSentLookDirection,
155 ),
156 With<InLoadedChunk>,
157 >,
158 mut commands: Commands,
159) {
160 for (
161 entity,
162 position,
163 direction,
164 mut physics_state,
165 mut last_sent_position,
166 mut physics,
167 mut last_direction,
168 ) in query.iter_mut()
169 {
170 let packet = {
171 let x_delta = position.x - last_sent_position.x;
175 let y_delta = position.y - last_sent_position.y;
176 let z_delta = position.z - last_sent_position.z;
177 let y_rot_delta = (direction.y_rot - last_direction.y_rot) as f64;
178 let x_rot_delta = (direction.x_rot - last_direction.x_rot) as f64;
179
180 physics_state.position_remainder += 1;
181
182 let sending_position = ((x_delta.powi(2) + y_delta.powi(2) + z_delta.powi(2))
185 > 2.0e-4f64.powi(2))
186 || physics_state.position_remainder >= 20;
187 let sending_direction = y_rot_delta != 0.0 || x_rot_delta != 0.0;
188
189 let flags = MoveFlags {
193 on_ground: physics.on_ground(),
194 horizontal_collision: physics.horizontal_collision,
195 };
196 let packet = if sending_position && sending_direction {
197 Some(
198 ServerboundMovePlayerPosRot {
199 pos: **position,
200 look_direction: *direction,
201 flags,
202 }
203 .into_variant(),
204 )
205 } else if sending_position {
206 Some(
207 ServerboundMovePlayerPos {
208 pos: **position,
209 flags,
210 }
211 .into_variant(),
212 )
213 } else if sending_direction {
214 Some(
215 ServerboundMovePlayerRot {
216 look_direction: *direction,
217 flags,
218 }
219 .into_variant(),
220 )
221 } else if physics.last_on_ground() != physics.on_ground() {
222 Some(ServerboundMovePlayerStatusOnly { flags }.into_variant())
223 } else {
224 None
225 };
226
227 if sending_position {
228 **last_sent_position = **position;
229 physics_state.position_remainder = 0;
230 }
231 if sending_direction {
232 last_direction.y_rot = direction.y_rot;
233 last_direction.x_rot = direction.x_rot;
234 }
235
236 let on_ground = physics.on_ground();
237 physics.set_last_on_ground(on_ground);
238 packet
241 };
242
243 if let Some(packet) = packet {
244 commands.trigger(SendPacketEvent {
245 sent_by: entity,
246 packet,
247 });
248 }
249 }
250}
251
252#[derive(Debug, Default, Component, Clone, PartialEq, Eq)]
253pub struct LastSentInput(pub ServerboundPlayerInput);
254pub fn send_player_input_packet(
255 mut query: Query<(Entity, &PhysicsState, &Jumping, Option<&LastSentInput>)>,
256 mut commands: Commands,
257) {
258 for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
259 let dir = physics_state.move_direction;
260 type D = WalkDirection;
261 let input = ServerboundPlayerInput {
262 forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
263 backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
264 left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
265 right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
266 jump: **jumping,
267 shift: false,
269 sprint: physics_state.trying_to_sprint,
270 };
271
272 let last_sent_input = last_sent_input.cloned().unwrap_or_default();
275
276 if input != last_sent_input.0 {
277 commands.trigger(SendPacketEvent {
278 sent_by: entity,
279 packet: input.clone().into_variant(),
280 });
281 commands.entity(entity).insert(LastSentInput(input));
282 }
283 }
284}
285
286pub fn send_sprinting_if_needed(
287 mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
288 mut commands: Commands,
289) {
290 for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
291 let was_sprinting = physics_state.was_sprinting;
292 if **sprinting != was_sprinting {
293 let sprinting_action = if **sprinting {
294 azalea_protocol::packets::game::s_player_command::Action::StartSprinting
295 } else {
296 azalea_protocol::packets::game::s_player_command::Action::StopSprinting
297 };
298 commands.trigger(SendPacketEvent::new(
299 entity,
300 ServerboundPlayerCommand {
301 id: *minecraft_entity_id,
302 action: sprinting_action,
303 data: 0,
304 },
305 ));
306 physics_state.was_sprinting = **sprinting;
307 }
308 }
309}
310
311pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
314 for mut physics_state in query.iter_mut() {
315 let multiplier: Option<f32> = None;
316
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 physics_state.forward_impulse = forward_impulse;
341 physics_state.left_impulse = left_impulse;
342
343 if let Some(multiplier) = multiplier {
344 physics_state.forward_impulse *= multiplier;
345 physics_state.left_impulse *= multiplier;
346 }
347 }
348}
349
350pub fn local_player_ai_step(
353 mut query: Query<
354 (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
355 With<InLoadedChunk>,
356 >,
357) {
358 for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
359 physics.x_acceleration = physics_state.left_impulse;
361 physics.z_acceleration = physics_state.forward_impulse;
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
388impl Client {
389 pub fn walk(&self, direction: WalkDirection) {
406 let mut ecs = self.ecs.lock();
407 ecs.send_event(StartWalkEvent {
408 entity: self.entity,
409 direction,
410 });
411 }
412
413 pub fn sprint(&self, direction: SprintDirection) {
429 let mut ecs = self.ecs.lock();
430 ecs.send_event(StartSprintEvent {
431 entity: self.entity,
432 direction,
433 });
434 }
435}
436
437#[derive(Event, Debug)]
442pub struct StartWalkEvent {
443 pub entity: Entity,
444 pub direction: WalkDirection,
445}
446
447pub fn handle_walk(
450 mut events: EventReader<StartWalkEvent>,
451 mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
452) {
453 for event in events.read() {
454 if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
455 {
456 physics_state.move_direction = event.direction;
457 physics_state.trying_to_sprint = false;
458 set_sprinting(false, &mut sprinting, &mut attributes);
459 }
460 }
461}
462
463#[derive(Event)]
466pub struct StartSprintEvent {
467 pub entity: Entity,
468 pub direction: SprintDirection,
469}
470pub fn handle_sprint(
473 mut query: Query<&mut PhysicsState>,
474 mut events: EventReader<StartSprintEvent>,
475) {
476 for event in events.read() {
477 if let Ok(mut physics_state) = query.get_mut(event.entity) {
478 physics_state.move_direction = WalkDirection::from(event.direction);
479 physics_state.trying_to_sprint = true;
480 }
481 }
482}
483
484fn set_sprinting(
488 sprinting: bool,
489 currently_sprinting: &mut Sprinting,
490 attributes: &mut Attributes,
491) -> bool {
492 **currently_sprinting = sprinting;
493 if sprinting {
494 attributes
495 .speed
496 .try_insert(azalea_entity::attributes::sprinting_modifier())
497 .is_ok()
498 } else {
499 attributes
500 .speed
501 .remove(&azalea_entity::attributes::sprinting_modifier().id)
502 .is_none()
503 }
504}
505
506fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
508 physics_state.forward_impulse > 0.8
512 }
514
515#[derive(Event)]
520pub struct KnockbackEvent {
521 pub entity: Entity,
522 pub knockback: KnockbackType,
523}
524
525pub enum KnockbackType {
526 Set(Vec3),
527 Add(Vec3),
528}
529
530pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
531 for event in events.read() {
532 if let Ok(mut physics) = query.get_mut(event.entity) {
533 match event.knockback {
534 KnockbackType::Set(velocity) => {
535 physics.velocity = velocity;
536 }
537 KnockbackType::Add(velocity) => {
538 physics.velocity += velocity;
539 }
540 }
541 }
542 }
543}
544
545#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
546pub enum WalkDirection {
547 #[default]
548 None,
549 Forward,
550 Backward,
551 Left,
552 Right,
553 ForwardRight,
554 ForwardLeft,
555 BackwardRight,
556 BackwardLeft,
557}
558
559#[derive(Clone, Copy, Debug)]
561pub enum SprintDirection {
562 Forward,
563 ForwardRight,
564 ForwardLeft,
565}
566
567impl From<SprintDirection> for WalkDirection {
568 fn from(d: SprintDirection) -> Self {
569 match d {
570 SprintDirection::Forward => WalkDirection::Forward,
571 SprintDirection::ForwardRight => WalkDirection::ForwardRight,
572 SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
573 }
574 }
575}