1use std::backtrace::Backtrace;
2
3use azalea_core::position::Vec3;
4use azalea_core::tick::GameTick;
5use azalea_entity::{metadata::Sprinting, Attributes, Jumping};
6use azalea_entity::{InLoadedChunk, LastSentPosition, LookDirection, Physics, Position};
7use azalea_physics::{ai_step, PhysicsSet};
8use azalea_protocol::packets::game::{ServerboundPlayerCommand, ServerboundPlayerInput};
9use azalea_protocol::packets::{
10 game::{
11 s_move_player_pos::ServerboundMovePlayerPos,
12 s_move_player_pos_rot::ServerboundMovePlayerPosRot,
13 s_move_player_rot::ServerboundMovePlayerRot,
14 s_move_player_status_only::ServerboundMovePlayerStatusOnly,
15 },
16 Packet,
17};
18use azalea_world::{MinecraftEntityId, MoveEntityError};
19use bevy_app::{App, Plugin, Update};
20use bevy_ecs::prelude::{Event, EventWriter};
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_handling::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 PlayerMovePlugin;
51
52impl Plugin for PlayerMovePlugin {
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 send_packet_events: EventWriter<SendPacketEvent>,
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 send_packet_events.send(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 send_packet_events: EventWriter<SendPacketEvent>,
261 mut commands: Commands,
262) {
263 for (entity, physics_state, jumping, last_sent_input) in query.iter_mut() {
264 let dir = physics_state.move_direction;
265 type D = WalkDirection;
266 let input = ServerboundPlayerInput {
267 forward: matches!(dir, D::Forward | D::ForwardLeft | D::ForwardRight),
268 backward: matches!(dir, D::Backward | D::BackwardLeft | D::BackwardRight),
269 left: matches!(dir, D::Left | D::ForwardLeft | D::BackwardLeft),
270 right: matches!(dir, D::Right | D::ForwardRight | D::BackwardRight),
271 jump: **jumping,
272 shift: false,
274 sprint: physics_state.trying_to_sprint,
275 };
276
277 let last_sent_input = last_sent_input.cloned().unwrap_or_default();
280
281 if input != last_sent_input.0 {
282 send_packet_events.send(SendPacketEvent {
283 sent_by: entity,
284 packet: input.clone().into_variant(),
285 });
286 commands.entity(entity).insert(LastSentInput(input));
287 }
288 }
289}
290
291fn send_sprinting_if_needed(
292 mut query: Query<(Entity, &MinecraftEntityId, &Sprinting, &mut PhysicsState)>,
293 mut send_packet_events: EventWriter<SendPacketEvent>,
294) {
295 for (entity, minecraft_entity_id, sprinting, mut physics_state) in query.iter_mut() {
296 let was_sprinting = physics_state.was_sprinting;
297 if **sprinting != was_sprinting {
298 let sprinting_action = if **sprinting {
299 azalea_protocol::packets::game::s_player_command::Action::StartSprinting
300 } else {
301 azalea_protocol::packets::game::s_player_command::Action::StopSprinting
302 };
303 send_packet_events.send(SendPacketEvent::new(
304 entity,
305 ServerboundPlayerCommand {
306 id: *minecraft_entity_id,
307 action: sprinting_action,
308 data: 0,
309 },
310 ));
311 physics_state.was_sprinting = **sprinting;
312 }
313 }
314}
315
316pub(crate) fn tick_controls(mut query: Query<&mut PhysicsState>) {
319 for mut physics_state in query.iter_mut() {
320 let multiplier: Option<f32> = None;
321
322 let mut forward_impulse: f32 = 0.;
323 let mut left_impulse: f32 = 0.;
324 let move_direction = physics_state.move_direction;
325 match move_direction {
326 WalkDirection::Forward | WalkDirection::ForwardRight | WalkDirection::ForwardLeft => {
327 forward_impulse += 1.;
328 }
329 WalkDirection::Backward
330 | WalkDirection::BackwardRight
331 | WalkDirection::BackwardLeft => {
332 forward_impulse -= 1.;
333 }
334 _ => {}
335 };
336 match move_direction {
337 WalkDirection::Right | WalkDirection::ForwardRight | WalkDirection::BackwardRight => {
338 left_impulse += 1.;
339 }
340 WalkDirection::Left | WalkDirection::ForwardLeft | WalkDirection::BackwardLeft => {
341 left_impulse -= 1.;
342 }
343 _ => {}
344 };
345 physics_state.forward_impulse = forward_impulse;
346 physics_state.left_impulse = left_impulse;
347
348 if let Some(multiplier) = multiplier {
349 physics_state.forward_impulse *= multiplier;
350 physics_state.left_impulse *= multiplier;
351 }
352 }
353}
354
355pub fn local_player_ai_step(
358 mut query: Query<
359 (&PhysicsState, &mut Physics, &mut Sprinting, &mut Attributes),
360 With<InLoadedChunk>,
361 >,
362) {
363 for (physics_state, mut physics, mut sprinting, mut attributes) in query.iter_mut() {
364 physics.x_acceleration = physics_state.left_impulse;
366 physics.z_acceleration = physics_state.forward_impulse;
367
368 let has_enough_food_to_sprint = true;
372
373 let trying_to_sprint = physics_state.trying_to_sprint;
376
377 if !**sprinting
378 && (
379 has_enough_impulse_to_start_sprinting(physics_state)
382 && has_enough_food_to_sprint
383 && trying_to_sprint
386 )
387 {
388 set_sprinting(true, &mut sprinting, &mut attributes);
389 }
390 }
391}
392
393impl Client {
394 pub fn walk(&mut self, direction: WalkDirection) {
411 let mut ecs = self.ecs.lock();
412 ecs.send_event(StartWalkEvent {
413 entity: self.entity,
414 direction,
415 });
416 }
417
418 pub fn sprint(&mut self, direction: SprintDirection) {
434 let mut ecs = self.ecs.lock();
435 ecs.send_event(StartSprintEvent {
436 entity: self.entity,
437 direction,
438 });
439 }
440}
441
442#[derive(Event, Debug)]
447pub struct StartWalkEvent {
448 pub entity: Entity,
449 pub direction: WalkDirection,
450}
451
452pub fn handle_walk(
455 mut events: EventReader<StartWalkEvent>,
456 mut query: Query<(&mut PhysicsState, &mut Sprinting, &mut Attributes)>,
457) {
458 for event in events.read() {
459 if let Ok((mut physics_state, mut sprinting, mut attributes)) = query.get_mut(event.entity)
460 {
461 physics_state.move_direction = event.direction;
462 physics_state.trying_to_sprint = false;
463 set_sprinting(false, &mut sprinting, &mut attributes);
464 }
465 }
466}
467
468#[derive(Event)]
471pub struct StartSprintEvent {
472 pub entity: Entity,
473 pub direction: SprintDirection,
474}
475pub fn handle_sprint(
478 mut query: Query<&mut PhysicsState>,
479 mut events: EventReader<StartSprintEvent>,
480) {
481 for event in events.read() {
482 if let Ok(mut physics_state) = query.get_mut(event.entity) {
483 physics_state.move_direction = WalkDirection::from(event.direction);
484 physics_state.trying_to_sprint = true;
485 }
486 }
487}
488
489fn set_sprinting(
493 sprinting: bool,
494 currently_sprinting: &mut Sprinting,
495 attributes: &mut Attributes,
496) -> bool {
497 **currently_sprinting = sprinting;
498 if sprinting {
499 attributes
500 .speed
501 .try_insert(azalea_entity::attributes::sprinting_modifier())
502 .is_ok()
503 } else {
504 attributes
505 .speed
506 .remove(&azalea_entity::attributes::sprinting_modifier().id)
507 .is_none()
508 }
509}
510
511fn has_enough_impulse_to_start_sprinting(physics_state: &PhysicsState) -> bool {
513 physics_state.forward_impulse > 0.8
517 }
519
520#[derive(Event)]
525pub struct KnockbackEvent {
526 pub entity: Entity,
527 pub knockback: KnockbackType,
528}
529
530pub enum KnockbackType {
531 Set(Vec3),
532 Add(Vec3),
533}
534
535pub fn handle_knockback(mut query: Query<&mut Physics>, mut events: EventReader<KnockbackEvent>) {
536 for event in events.read() {
537 if let Ok(mut physics) = query.get_mut(event.entity) {
538 match event.knockback {
539 KnockbackType::Set(velocity) => {
540 physics.velocity = velocity;
541 }
542 KnockbackType::Add(velocity) => {
543 physics.velocity += velocity;
544 }
545 }
546 }
547 }
548}
549
550#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
551pub enum WalkDirection {
552 #[default]
553 None,
554 Forward,
555 Backward,
556 Left,
557 Right,
558 ForwardRight,
559 ForwardLeft,
560 BackwardRight,
561 BackwardLeft,
562}
563
564#[derive(Clone, Copy, Debug)]
566pub enum SprintDirection {
567 Forward,
568 ForwardRight,
569 ForwardLeft,
570}
571
572impl From<SprintDirection> for WalkDirection {
573 fn from(d: SprintDirection) -> Self {
574 match d {
575 SprintDirection::Forward => WalkDirection::Forward,
576 SprintDirection::ForwardRight => WalkDirection::ForwardRight,
577 SprintDirection::ForwardLeft => WalkDirection::ForwardLeft,
578 }
579 }
580}