Botcraft 1.21.7
Loading...
Searching...
No Matches
PhysicsManager.cpp
Go to the documentation of this file.
14#if USE_GUI
16#endif
17
18using namespace ProtocolCraft;
19
20namespace Botcraft
21{
23#if USE_GUI
24 const std::shared_ptr<Renderer::RenderingManager>& rendering_manager_,
25#endif
26 const std::shared_ptr<InventoryManager>& inventory_manager_,
27 const std::shared_ptr<EntityManager>& entity_manager_,
28 const std::shared_ptr<NetworkManager>& network_manager_,
29 const std::shared_ptr<World>& world_)
30 {
31#if USE_GUI
32 rendering_manager = rendering_manager_;
33#endif
34 inventory_manager = inventory_manager_;
35 entity_manager = entity_manager_;
36 player = nullptr;
37 network_manager = network_manager_;
38 world = world_;
39
40 should_run = false;
41 teleport_id = std::nullopt;
44
45 elytra_item = AssetsManager::getInstance().GetItem("minecraft:elytra");
46 if (elytra_item == nullptr)
47 {
48 throw std::runtime_error("Unknown item minecraft:elytra");
49 }
50 }
51
56
58 {
59 should_run = true;
60
61 // Launch the physics thread (continuously sending the position to the server)
62 thread_physics = std::thread(&PhysicsManager::Physics, this);
63 }
64
66 {
67 should_run = false;
68 if (thread_physics.joinable())
69 {
70 thread_physics.join();
71 }
72 }
73
75 {
77 // Reset sprint_double_tap_trigger_time to 0 if b is false
78 if (player != nullptr)
79 {
80 std::scoped_lock<std::shared_mutex> lock(player->entity_mutex);
81 player->sprint_double_tap_trigger_time *= b;
82 }
83 }
84
86 {
87 return ms_per_tick;
88 }
89
90
92 {
93 // Reset the player because *some* non vanilla servers
94 // sends new login packets
95 if (player != nullptr)
96 {
97 // If we already have a player, we need to make sure we're not in the middle
98 // of a physics update while swapping the player with the new one
99 std::scoped_lock<std::shared_mutex> lock(player->entity_mutex);
100 player = entity_manager->GetLocalPlayer();
101 }
102 else
103 {
104 player = entity_manager->GetLocalPlayer();
105 }
106 }
107
109 {
110 if (player == nullptr)
111 {
112 LOG_WARNING("Received a PlayerPosition packet without a player");
113 return;
114 }
115
116 std::scoped_lock<std::shared_mutex> lock(player->entity_mutex);
117#if PROTOCOL_VERSION < 768 /* < 1.21.2 */
118 if (packet.GetRelativeArguments() & 0x01)
119 {
120 player->position.x = player->position.x + packet.GetX();
121 }
122 else
123 {
124 player->position.x = packet.GetX();
125 player->speed.x = 0.0;
126 }
127 if (packet.GetRelativeArguments() & 0x02)
128 {
129 player->position.y = player->position.y + packet.GetY();
130 }
131 else
132 {
133 player->position.y = packet.GetY();
134 player->speed.y = 0.0;
135 }
136 if (packet.GetRelativeArguments() & 0x04)
137 {
138 player->position.z = player->position.z + packet.GetZ();
139 }
140 else
141 {
142 player->position.z = packet.GetZ();
143 player->speed.z = 0.0;
144 }
145 player->yaw = packet.GetRelativeArguments() & 0x08 ? player->yaw + packet.GetYRot() : packet.GetYRot();
146 player->pitch = packet.GetRelativeArguments() & 0x10 ? player->pitch + packet.GetXRot() : packet.GetXRot();
147
148 player->previous_position = player->position;
149 player->previous_yaw = player->yaw;
150 player->previous_pitch = player->pitch;
151#else
152 for (int i = 0; i < 3; ++i)
153 {
154 player->position[i] = packet.GetRelatives() & (1 << i) ? player->position[i] + packet.GetChange().GetPosition()[i] : packet.GetChange().GetPosition()[i];
155 }
156 const float new_yaw = packet.GetRelatives() & (1 << 3) ? player->yaw + packet.GetChange().GetYRot() : packet.GetChange().GetYRot();
157 const float new_pitch = packet.GetRelatives() & (1 << 4) ? player->pitch + packet.GetChange().GetXRot() : packet.GetChange().GetXRot();
158 Vector3<double> speed = player->speed;
159 if (packet.GetRelatives() & (1 << 8)) // Rotate delta, not sure what it's for...
160 {
161 const float delta_yaw = player->yaw - new_yaw;
162 const float delta_pitch = player->pitch - new_pitch;
163 // xRot
164 speed = Vector3<double>(
165 speed.x,
166 speed.y * static_cast<double>(std::cos(delta_pitch * 0.017453292f /* PI/180 */)) + speed.z * static_cast<double>(std::sin(delta_pitch * 0.017453292f /* PI/180 */)),
167 speed.z * static_cast<double>(std::cos(delta_pitch * 0.017453292f /* PI/180 */)) - speed.y * static_cast<double>(std::sin(delta_pitch * 0.017453292f /* PI/180 */))
168 );
169 // yRot
170 speed = Vector3<double>(
171 speed.x * static_cast<double>(std::cos(delta_yaw * 0.017453292f /* PI/180 */)) + speed.z * static_cast<double>(std::sin(delta_yaw * 0.017453292f /* PI/180 */)),
172 speed.y,
173 speed.z * static_cast<double>(std::cos(delta_yaw * 0.017453292f /* PI/180 */)) - speed.x * static_cast<double>(std::sin(delta_yaw * 0.017453292f /* PI/180 */))
174 );
175 }
176 player->yaw = new_yaw;
177 player->pitch = new_pitch;
178 for (int i = 0; i < 3; ++i)
179 {
180 player->speed[i] = packet.GetRelatives() & (1 << (5 + i)) ? speed[i] + packet.GetChange().GetDeltaMovement()[i] : packet.GetChange().GetDeltaMovement()[i];
181 }
182 for (int i = 0; i < 3; ++i)
183 {
184 player->previous_position[i] = packet.GetRelatives() & (1 << i) ? player->previous_position[i] + packet.GetChange().GetPosition()[i] : packet.GetChange().GetPosition()[i];
185 }
186 player->previous_yaw = packet.GetRelatives() & (1 << 3) ? player->yaw + packet.GetChange().GetYRot() : packet.GetChange().GetYRot();
187 player->previous_pitch = packet.GetRelatives() & (1 << 4) ? player->pitch + packet.GetChange().GetXRot() : packet.GetChange().GetXRot();
188#endif
189 player->UpdateVectors();
190
191 // Defer sending the position update to the next physics tick
192 // Sending it now causes huge slow down of the tests (lag when
193 // botcraft_0 is teleported to place structure block/signs).
194 // I'm not sure what causes this behaviour (server side?)
195 teleport_id = packet.GetId_();
196 }
197
198#if PROTOCOL_VERSION > 764 /* > 1.20.2 */
200 {
201 // If the game is frozen, physics run normally for players
202 if (packet.GetIsFrozen())
203 {
204 ms_per_tick = 50.0;
205 return;
206 }
207
208 // Vanilla behaviour: slowed down but never speed up, even if tick rate is > 20/s
209 // TODO: add a parameter to allow non-vanilla client speed up for higher tick rates?
210 ms_per_tick = std::max(50.0, 1000.0 / static_cast<double>(packet.GetTickRate()));
211 }
212#endif
213
214#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
216 {
217 if (player == nullptr)
218 {
219 LOG_WARNING("Received a PlayerRotation packet without a player");
220 return;
221 }
222
223 std::scoped_lock<std::shared_mutex> lock(player->entity_mutex);
224 player->yaw = packet.GetYRot();
225 player->pitch = packet.GetXRot();
226 player->previous_yaw = player->yaw;
227 player->previous_pitch = player->pitch;
228 }
229#endif
230
232 {
233 Logger::GetInstance().RegisterThread("Physics - " + network_manager->GetMyName());
234
235 while (should_run)
236 {
237 auto end = std::chrono::steady_clock::now();
238 // End of the current tick
239 end += std::chrono::microseconds(static_cast<long long int>(1000.0 * ms_per_tick));
240
241 if (network_manager->GetConnectionState() == ConnectionState::Play)
242 {
243 if (player != nullptr && !std::isnan(player->GetY()))
244 {
245 // As PhysicsManager is a friend of LocalPlayer, we can lock the whole entity
246 // while physics is processed. This also means we can't use public interface
247 // as it's thread-safe by design and would deadlock because of this global lock
248 std::scoped_lock<std::shared_mutex> lock(player->entity_mutex);
249
250 // Send player updated position with onground set to false to mimic vanilla client behaviour
251 if (teleport_id.has_value())
252 {
253 std::shared_ptr<ServerboundMovePlayerPacketPosRot> updated_position_packet = std::make_shared<ServerboundMovePlayerPacketPosRot>();
254 updated_position_packet->SetX(player->position.x);
255 updated_position_packet->SetY(player->position.y);
256 updated_position_packet->SetZ(player->position.z);
257 updated_position_packet->SetYRot(player->yaw);
258 updated_position_packet->SetXRot(player->pitch);
259 updated_position_packet->SetOnGround(false);
260#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
261 updated_position_packet->SetHorizontalCollision(false);
262#endif
263
264 std::shared_ptr<ServerboundAcceptTeleportationPacket> accept_tp_packet = std::make_shared<ServerboundAcceptTeleportationPacket>();
265 accept_tp_packet->SetId_(teleport_id.value());
266
267 // Before 1.21.2 -> Accept TP then move player, 1.21.2+ -> move player then accept TP
268#if PROTOCOL_VERSION < 768 /* < 1.21.2 */
269 network_manager->Send(accept_tp_packet);
270 network_manager->Send(updated_position_packet);
271#else
272 network_manager->Send(updated_position_packet);
273 network_manager->Send(accept_tp_packet);
274#endif
275 teleport_id = std::nullopt;
276 }
277 PhysicsTick();
278 }
279#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
280 std::shared_ptr<ServerboundClientTickEndPacket> tick_end_packet = std::make_shared<ServerboundClientTickEndPacket>();
281 network_manager->Send(tick_end_packet);
282#endif
283 }
284 // Wait for end of tick
286 }
287 }
288
290 {
291 // Check for rocket boosting if currently in elytra flying mode
292 if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying))
293 {
294 for (const auto& e : *entity_manager->GetEntities())
295 {
296 if (e.second->GetType() != EntityType::FireworkRocketEntity)
297 {
298 continue;
299 }
300
301#if PROTOCOL_VERSION > 404 /* > 1.13.2 */
302 const int attached_id = reinterpret_cast<const FireworkRocketEntity*>(e.second.get())->GetDataAttachedToTarget().value_or(0);
303#else
304 const int attached_id = reinterpret_cast<const FireworkRocketEntity*>(e.second.get())->GetDataAttachedToTarget();
305#endif
306 if (attached_id == player->entity_id)
307 {
308 player->speed += player->front_vector * 0.1 + (player->front_vector * 1.5 - player->speed) * 0.5;
309 }
310 }
311 }
312
313 { // LocalPlayer::tick
314 // The idea here is to follow the tick flow from LocalPlayer::tick
315 // in Minecraft code
316 // This is neither the most efficient way nor the simplest one.
317 // But hopefully it will be easier to compare to original code
318 // and update when changed on Minecraft side.
319 if (world->IsLoaded(Position(
320 static_cast<int>(std::floor(player->position.x)),
321 static_cast<int>(std::floor(player->position.y)),
322 static_cast<int>(std::floor(player->position.z))
323 )))
324 { // Player::tick
325 if (player->game_mode == GameType::Spectator)
326 {
327 player->on_ground = false;
328 }
329
330 { // Entity::baseTick
331 FluidPhysics(true);
332 FluidPhysics(false);
333
334 UpdateSwimming(); // Player::updateSwimming
335 } // Entity::baseTick
336
337 { // LocalPlayer::aiStep
338 player->sprint_double_tap_trigger_time = std::max(0, player->sprint_double_tap_trigger_time - 1);
339
341
342 // TODO: if game_mode != GameType::Spectator, move towards closest free space if inside block
343
345
346 InputsToFly();
347
348 // If sneaking in water, add downward speed
349 if (player->in_water && player->inputs.sneak && !player->flying)
350 {
351 player->speed.y -= 0.03999999910593033;
352 }
353
354 // If flying in spectator/creative, go up/down if sneak/jump
355 if (player->flying)
356 {
357 player->speed.y += (-1 * player->inputs.sneak + player->inputs.jump) * player->flying_speed * 3.0f;
358 }
359
360 { // Player::aiStep
361 player->fly_jump_trigger_time = std::max(0, player->fly_jump_trigger_time - 1);
362 }
363
364 // Update previous values
365 player->previous_forward = player->inputs.forward_axis;
366 player->previous_jump = player->inputs.jump;
367 player->previous_sneak = player->inputs.sneak;
368
369 { // LivingEntity::aiStep
370 // Decrease jump delay if > 0
371 player->jump_delay = std::max(0, player->jump_delay - 1);
372
373 if (std::abs(player->speed.x) < 0.003)
374 {
375 player->speed.x = 0.0;
376 }
377 if (std::abs(player->speed.y) < 0.003)
378 {
379 player->speed.y = 0.0;
380 }
381 if (std::abs(player->speed.z) < 0.003)
382 {
383 player->speed.z = 0.0;
384 }
385
386 InputsToJump();
387
388 player->inputs.forward_axis *= 0.98f;
389 player->inputs.left_axis *= 0.98f;
390
391 // Compensate water downward speed depending on looking direction (?)
393 {
394 const double m_sin_pitch = player->front_vector.y;
395 bool condition = m_sin_pitch <= 0.0 || player->inputs.jump;
396 if (!condition)
397 {
398 const Blockstate* above_block = world->GetBlock(Position(
399 static_cast<int>(std::floor(player->position.x)),
400 static_cast<int>(std::floor(player->position.y + 1.0 - 0.1)),
401 static_cast<int>(std::floor(player->position.z))
402 ));
403 condition = above_block != nullptr && above_block->IsFluid();
404 }
405 if (condition)
406 {
407 player->speed.y += (m_sin_pitch - player->speed.y) * (m_sin_pitch < -0.2 ? 0.085 : 0.06);
408 }
409 }
410
411 const double speed_y = player->speed.y;
412 MovePlayer();
413 if (player->flying)
414 {
415 player->speed.y = 0.6 * speed_y;
416 player->SetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying, false);
417 }
418
419 // Minecraft code doesn't store on_climbable and check everytime it's needed instead,
420 // but it's more convenient for pathfinding to have it stored
421 player->on_climbable = IsInClimbable();
422
423 // TODO: pushed by other entities
424
425 } // LivingEntity::aiStep
426
427 // Stop flying in creative when touching ground
428 if (player->on_ground && player->flying && player->game_mode != GameType::Spectator)
429 {
430 player->flying = false;
432 }
433 } // LocalPlayer::aiStep
434
435 player->position.x = std::clamp(player->position.x, -2.9999999E7, 2.9999999E7);
436 player->position.z = std::clamp(player->position.z, -2.9999999E7, 2.9999999E7);
437
438#if PROTOCOL_VERSION > 404 /* > 1.13.2 */
439 if (world->IsFree(player->GetColliderImpl(Pose::Swimming).Inflate(-1e-7), false))
440 { // Player::UpdatePlayerPose
441 Pose current_pose;
442 if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying))
443 {
444 current_pose = Pose::FallFlying;
445 }
446 else if (player->GetSleepingPosIdImpl())
447 {
448 current_pose = Pose::Sleeping;
449 }
450 else if (IsSwimmingAndNotFlying())
451 {
452 current_pose = Pose::Swimming;
453 }
454 else if (player->GetDataLivingEntityFlagsImpl() & 0x04)
455 {
456 current_pose = Pose::SpinAttack;
457 }
458 else if (player->inputs.sneak && !player->flying)
459 {
460 current_pose = Pose::Crouching;
461 }
462 else
463 {
464 current_pose = Pose::Standing;
465 }
466
467 if (player->game_mode == GameType::Spectator || world->IsFree(player->GetColliderImpl(current_pose).Inflate(-1e-7), false))
468 {
469 player->SetDataPoseImpl(current_pose);
470 }
471 else if (world->IsFree(player->GetColliderImpl(Pose::Crouching).Inflate(-1e-7), false))
472 {
473 player->SetDataPoseImpl(Pose::Crouching);
474 }
475 else
476 {
477 player->SetDataPoseImpl(Pose::Swimming);
478 }
479 }
480#endif
481 } // Player::tick
482
483 SendPosition();
484 } // LocalPlayer::tick
485
486 player->ResetInputs();
487 }
488
490 {
491 if (player->flying)
492 {
493 player->SetDataSharedFlagsIdImpl(EntitySharedFlagsId::Swimming, false);
494 }
495 else if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Swimming))
496 {
497 player->SetDataSharedFlagsIdImpl(
499 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting) && player->in_water
500 );
501 }
502 else
503 {
504 const Blockstate* block = world->GetBlock(Position(
505 static_cast<int>(std::floor(player->position.x)),
506 static_cast<int>(std::floor(player->position.y)),
507 static_cast<int>(std::floor(player->position.z))
508 ));
509 player->SetDataSharedFlagsIdImpl(
511 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting) &&
512 player->under_water &&
513 block != nullptr &&
514 block->IsWater()
515 );
516 }
517 }
518
519 void PhysicsManager::FluidPhysics(const bool water)
520 { // Entity::updateFluidHeightAndDoFluidPushing
521 const AABB aabb = player->GetColliderImpl().Inflate(-0.001);
522
523 if (water)
524 {
525 player->in_water = false;
526 player->under_water = false;
527 }
528 else
529 {
530 player->in_lava = false;
531 }
532
533 const Vector3<double> min_aabb = aabb.GetMin();
534 const Vector3<double> max_aabb = aabb.GetMax();
535 const double eye_height = player->position.y + player->GetEyeHeightImpl();
536
537 Vector3<double> push(0.0, 0.0, 0.0);
538 Position block_pos;
539 double fluid_relative_height = 0.0;
540 int num_push = 0;
541
542 for (int x = static_cast<int>(std::floor(min_aabb.x)); x <= static_cast<int>(std::floor(max_aabb.x)); ++x)
543 {
544 block_pos.x = x;
545 for (int y = static_cast<int>(std::floor(min_aabb.y)); y <= static_cast<int>(std::floor(max_aabb.y)); ++y)
546 {
547 block_pos.y = y;
548 for (int z = static_cast<int>(std::floor(min_aabb.z)); z <= static_cast<int>(std::floor(max_aabb.z)); ++z)
549 {
550 block_pos.z = z;
551 const Blockstate* block = world->GetBlock(block_pos);
552 if (block == nullptr || !block->IsFluid() ||
553 (block->IsLava() && water) || (block->IsWater() && !water))
554 {
555 continue;
556 }
557
558 double fluid_height = 0.0;
559 if (const Blockstate* block_above = world->GetBlock(block_pos + Position(0, 1, 0)); block_above != nullptr &&
560 ((block_above->IsLava() && block->IsLava()) || (block_above->IsWater() && block->IsWater())))
561 {
562 fluid_height = 1.0;
563 }
564 else
565 {
566 fluid_height = block->GetFluidHeight();
567 }
568
569 if (fluid_height + y < min_aabb.y)
570 {
571 continue;
572 }
573
574 if (water)
575 {
576 player->in_water = true;
577 if (fluid_height + y >= eye_height)
578 {
579 player->under_water = true;
580 }
581 }
582 else
583 {
584 player->in_lava = true;
585 }
586
587 fluid_relative_height = std::max(fluid_height - min_aabb.y, fluid_relative_height);
588
589 if (player->flying)
590 {
591 continue;
592 }
593
594 Vector3<double> current_push = world->GetFlow(block_pos);
595 if (fluid_relative_height < 0.4)
596 {
597 current_push *= fluid_relative_height;
598 }
599 push += current_push;
600 num_push += 1;
601 }
602 }
603 }
604
605 if (push.SqrNorm() > 0.0)
606 {
607 if (num_push > 0) // this should always be true but just in case
608 {
609 push /= static_cast<double>(num_push);
610 }
611 if (water)
612 {
613 push *= 0.014;
614 }
615 else
616 {
617 push *= world->IsInUltraWarmDimension() ? 0.007 : 0.0023333333333333335;
618 }
619 const double push_norm = std::sqrt(push.SqrNorm());
620 if (std::abs(player->speed.x) < 0.003 && std::abs(player->speed.z) < 0.003 && push_norm < 0.0045000000000000005)
621 {
622 // Normalize and scale
623 push /= push_norm;
624 push *= 0.0045000000000000005;
625 }
626 player->speed += push;
627 }
628 }
629
631 {
632#if PROTOCOL_VERSION > 404 /* > 1.13.2 */
633 player->crouching =
635 world->IsFree(player->GetColliderImpl(Pose::Crouching).Inflate(-1e-7), false) &&
636 (player->previous_sneak || !world->IsFree(player->GetColliderImpl(Pose::Standing).Inflate(-1e-7), false));
637#else
638 player->crouching = !IsSwimmingAndNotFlying() && player->previous_sneak;
639#endif
640
641#if PROTOCOL_VERSION > 404 /* > 1.13.2 */
642 const bool is_moving_slowly = player->crouching || (player->GetDataPoseImpl() == Pose::Swimming && !player->in_water);
643#else
644 const bool is_moving_slowly = player->crouching;
645#endif
646
647#if PROTOCOL_VERSION > 768 /* > 1.21.3 */
648 bool has_blindness = false;
649 for (const auto& effect : player->effects)
650 {
651 if (effect.type == EntityEffectType::Blindness && effect.end > std::chrono::steady_clock::now())
652 {
653 has_blindness = true;
654 break;
655 }
656 }
657
658 // Stop sprinting when crouching fix in 1.21.4+
659 if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying) ||
660 has_blindness ||
661 is_moving_slowly)
662 {
663 SetSprinting(false);
664 }
665#endif
666
667 // If crouch, slow down player inputs
668 if (is_moving_slowly)
669 {
670#if PROTOCOL_VERSION < 759 /* < 1.19 */
671 constexpr float sneak_coefficient = 0.3f;
672#elif PROTOCOL_VERSION < 767 /* < 1.21 */
673 float sneak_coefficient = 0.3f;
674 // Get SneakSpeed bonus from pants
675 const Slot leggings_armor = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_LEGS_ARMOR);
676 sneak_coefficient += Utilities::GetEnchantmentLvl(leggings_armor, Enchantment::SwiftSneak) * 0.15f;
677 sneak_coefficient = std::min(std::max(0.0f, sneak_coefficient), 1.0f);
678#else
679 const float sneak_coefficient = static_cast<float>(player->GetAttributePlayerSneakingSpeedValueImpl());
680#endif
681 player->inputs.forward_axis *= sneak_coefficient;
682 player->inputs.left_axis *= sneak_coefficient;
683 }
684 }
685
687 {
688 const bool was_sneaking = player->previous_sneak;
689 const bool had_enough_impulse_to_start_sprinting = player->previous_forward >= (player->under_water ? 1e-5f : 0.8f);
690 const bool has_enough_impulse_to_start_sprinting = player->inputs.forward_axis >= (player->under_water ? 1e-5f : 0.8f);
691
692#if PROTOCOL_VERSION > 404 /* > 1.13.2 */
693 const bool is_moving_slowly = player->crouching || (player->GetDataPoseImpl() == Pose::Swimming && !player->in_water);
694#else
695 const bool is_moving_slowly = player->crouching;
696#endif
697
698 if (was_sneaking)
699 {
700 player->sprint_double_tap_trigger_time = 0;
701 }
702
703 bool has_blindness = false;
704 for (const auto& effect : player->effects)
705 {
706 if (effect.type == EntityEffectType::Blindness && effect.end > std::chrono::steady_clock::now())
707 {
708 has_blindness = true;
709 break;
710 }
711 }
712
713 const bool can_start_sprinting = !(
714 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting) ||
715 !has_enough_impulse_to_start_sprinting ||
716 !(player->food > 6 || player->may_fly) ||
717 has_blindness ||
718 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying) ||
719 (is_moving_slowly && !player->under_water)
720 );
721
722 if ((player->on_ground || player->under_water) && !was_sneaking && !had_enough_impulse_to_start_sprinting && can_start_sprinting)
723 {
724 if (player->sprint_double_tap_trigger_time > 0 || player->inputs.sprint)
725 {
726 SetSprinting(true);
727 }
728 else
729 {
730 player->sprint_double_tap_trigger_time = 7 * double_tap_cause_sprint;
731 }
732 }
733
734 if ((!player->in_water || player->under_water) && can_start_sprinting && player->inputs.sprint)
735 {
736 SetSprinting(true);
737 }
738
739 // Stop sprinting if necessary
740 if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting))
741 {
742 const bool stop_sprint_condition = player->inputs.forward_axis <= 1e-5 || (player->food <= 6 && !player->may_fly);
744 {
745 if ((!player->on_ground && !player->inputs.sneak && stop_sprint_condition) || !player->in_water)
746 {
747 SetSprinting(false);
748 }
749 }
750 else if (stop_sprint_condition ||
751 player->horizontal_collision || // TODO: add minor horizontal collision
752 (player->in_water && !player->under_water))
753 {
754 SetSprinting(false);
755 }
756 }
757 }
758
759 void PhysicsManager::SetSprinting(const bool b) const
760 {
761 player->SetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting, b);
763 if (b)
764 {
767 0.3, // amount
769 }
770 );
771 }
772 }
773
775 {
776 // Start flying in Creative/Spectator
777 bool fly_changed = false;
778 if (player->may_fly)
779 {
780 // Auto trigger flying if in spectator mode
781 if (player->game_mode == GameType::Spectator && !player->flying)
782 {
783 player->flying = true;
784 fly_changed = true;
786 }
787 // If double jump in creative, swap flying mode
788 else if (!player->previous_jump && player->inputs.jump)
789 {
790 if (player->fly_jump_trigger_time == 0)
791 {
792 player->fly_jump_trigger_time = 7;
793 }
794 else if (!IsSwimmingAndNotFlying())
795 {
796 player->flying = !player->flying;
797 fly_changed = true;
799 player->fly_jump_trigger_time = 0;
800 }
801 }
802 }
803
804 bool has_levitation_effect = false;
805 for (const auto& effect : player->effects)
806 {
807 if (effect.type == EntityEffectType::Levitation && effect.end > std::chrono::steady_clock::now())
808 {
809 has_levitation_effect = true;
810 break;
811 }
812 }
813
814 // Start elytra flying
815 if (player->inputs.jump &&
816 !fly_changed &&
817 !player->previous_jump &&
818 !player->flying &&
819 !player->on_climbable &&
820 !player->on_ground &&
821 !player->in_water &&
822 !player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying) &&
823 !has_levitation_effect)
824 {
825 const Slot chest_slot = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_CHEST_ARMOR);
826 if (!chest_slot.IsEmptySlot() &&
827 chest_slot.GetItemId() == elytra_item->GetId() &&
829 {
830 player->SetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying, true);
831 std::shared_ptr<ServerboundPlayerCommandPacket> player_command_packet = std::make_shared<ServerboundPlayerCommandPacket>();
832 player_command_packet->SetAction(static_cast<int>(PlayerCommandAction::StartFallFlying));
833 player_command_packet->SetId_(player->entity_id);
834 network_manager->Send(player_command_packet);
835 }
836 }
837 }
838
840 {
841 // Perform jump
842 if (player->inputs.jump && !player->flying)
843 {
844 // Jump from fluid
845 if (player->in_lava || player->in_water)
846 {
847 player->speed.y += 0.03999999910593033;
848 }
849 // Jump from ground
850 else if (player->on_ground && player->jump_delay == 0)
851 {
852 // Get jump boost
853 float jump_boost = 0.0f;
854 for (const auto& effect : player->effects)
855 {
856 if (effect.type == EntityEffectType::JumpBoost && effect.end > std::chrono::steady_clock::now())
857 {
858 jump_boost = 0.1f * (effect.amplifier + 1); // amplifier is 0 for "Effect I"
859 break;
860 }
861 }
862
863 // Get block underneath
864 float block_jump_factor = 1.0f;
865 const Blockstate* feet_block = world->GetBlock(Position(
866 static_cast<int>(std::floor(player->position.x)),
867 static_cast<int>(std::floor(player->position.y)),
868 static_cast<int>(std::floor(player->position.z))
869 ));
870 if (feet_block == nullptr || !feet_block->IsHoney())
871 {
872 const Blockstate* below_block = world->GetBlock(GetBlockBelowAffectingMovement());
873 if (below_block != nullptr && below_block->IsHoney())
874 {
875 block_jump_factor = 0.4f;
876 }
877 }
878 else
879 {
880 block_jump_factor = 0.4f;
881 }
882
883#if PROTOCOL_VERSION < 766 /* < 1.20.5 */
884 player->speed.y = 0.42f * block_jump_factor + jump_boost;
885 if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting))
886 {
887 const float yaw_rad = player->yaw * 0.017453292f /* PI/180 */;
888 player->speed.x -= std::sin(yaw_rad) * 0.2f;
889 player->speed.z += std::cos(yaw_rad) * 0.2f;
890 }
891#else
892 const float jump_power = static_cast<float>(player->GetAttributeJumpStrengthValueImpl()) * block_jump_factor + jump_boost;
893 if (jump_power > 1e-5f)
894 {
895 player->speed.y = jump_power;
896 if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting))
897 {
898 const float yaw_rad = player->yaw * 0.017453292f /* PI/180 */;
899 player->speed.x -= static_cast<double>(std::sin(yaw_rad)) * 0.2;
900 player->speed.z += static_cast<double>(std::cos(yaw_rad)) * 0.2;
901 }
902 }
903#endif
904 player->jump_delay = 10;
905 }
906 }
907 else
908 {
909 player->jump_delay = 0;
910 }
911 }
912
913 void PhysicsManager::ApplyInputs(const float strength) const
914 {
915 Vector3<double> input_vector(player->inputs.left_axis, 0.0, player->inputs.forward_axis);
916 const double sqr_norm = input_vector.SqrNorm();
917 if (input_vector.SqrNorm() < 1e-7)
918 {
919 return;
920 }
921 if (sqr_norm > 1.0)
922 {
923 input_vector.Normalize();
924 }
925 input_vector *= strength;
926 const double sin_yaw = std::sin(player->yaw * 0.017453292f /* PI/180 */);
927 const double cos_yaw = std::cos(player->yaw * 0.017453292f /* PI/180 */);
928
929 player->speed.x += input_vector.x * cos_yaw - input_vector.z * sin_yaw;
930 player->speed.y += input_vector.y;
931 player->speed.z += input_vector.x * sin_yaw + input_vector.z * cos_yaw;
932 }
933
935 {
937
938#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
939 // Before 1.21.2, shift was sent after jump
940 // After 1.21.5 the ServerboundPlayerCommandPacket is not sent anymore
941#if PROTOCOL_VERSION < 771 /* < 1.21.6 */
942 const bool shift_key_down = player->inputs.sneak;
943 if (shift_key_down != player->previous_shift_key_down)
944 {
945 std::shared_ptr<ServerboundPlayerCommandPacket> player_command_packet = std::make_shared<ServerboundPlayerCommandPacket>();
946 player_command_packet->SetAction(static_cast<int>(shift_key_down ? PlayerCommandAction::PressShiftKey : PlayerCommandAction::ReleaseShifKey));
947 player_command_packet->SetId_(player->entity_id);
948 network_manager->Send(player_command_packet);
949 player->previous_shift_key_down = shift_key_down;
950 }
951#endif
952
953 if (player->last_sent_inputs.sneak != player->inputs.sneak ||
954 player->last_sent_inputs.jump != player->inputs.jump ||
955 player->last_sent_inputs.sprint != player->inputs.sprint ||
956 player->last_sent_inputs.forward_axis != player->inputs.forward_axis ||
957 player->last_sent_inputs.left_axis != player->inputs.left_axis)
958 {
959 std::shared_ptr<ServerboundPlayerInputPacket> player_input_packet = std::make_shared<ServerboundPlayerInputPacket>();
960 player_input_packet->SetForward(player->inputs.forward_axis > 0.0f);
961 player_input_packet->SetBackward(player->inputs.forward_axis < 0.0f);
962 player_input_packet->SetLeft(player->inputs.left_axis > 0.0f);
963 player_input_packet->SetRight(player->inputs.left_axis < 0.0f);
964 player_input_packet->SetJump(player->inputs.jump);
965 player_input_packet->SetShift(player->inputs.sneak);
966 player_input_packet->SetSprint(player->inputs.sprint);
967 network_manager->Send(player_input_packet);
968 player->last_sent_inputs = player->inputs;
969 }
970#endif
971
972 const bool sprinting = player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting);
973 if (sprinting != player->previous_sprinting)
974 {
975 std::shared_ptr<ServerboundPlayerCommandPacket> player_command_packet = std::make_shared<ServerboundPlayerCommandPacket>();
976 player_command_packet->SetAction(static_cast<int>(sprinting ? PlayerCommandAction::StartSprinting : PlayerCommandAction::StopSprinting));
977 player_command_packet->SetId_(player->entity_id);
978 network_manager->Send(player_command_packet);
979 player->previous_sprinting = sprinting;
980 }
981
982#if PROTOCOL_VERSION < 768 /* < 1.21.2 */
983 // Before 1.21.2, shift was sent after jump
984 const bool shift_key_down = player->inputs.sneak;
985 if (shift_key_down != player->previous_shift_key_down)
986 {
987 std::shared_ptr<ServerboundPlayerCommandPacket> player_command_packet = std::make_shared<ServerboundPlayerCommandPacket>();
988 player_command_packet->SetAction(static_cast<int>(shift_key_down ? PlayerCommandAction::PressShiftKey : PlayerCommandAction::ReleaseShifKey));
989 player_command_packet->SetId_(player->entity_id);
990 network_manager->Send(player_command_packet);
991 player->previous_shift_key_down = shift_key_down;
992 }
993#endif
994
995 const bool has_moved = (player->position - player->previous_position).SqrNorm() > 4e-8 || ticks_since_last_position_sent >= 20;
996 const bool has_rotated = player->yaw != player->previous_yaw || player->pitch != player->previous_pitch;
997 if (has_moved && has_rotated)
998 {
999 std::shared_ptr<ServerboundMovePlayerPacketPosRot> move_player_packet = std::make_shared<ServerboundMovePlayerPacketPosRot>();
1000 move_player_packet->SetX(player->position.x);
1001 move_player_packet->SetY(player->position.y);
1002 move_player_packet->SetZ(player->position.z);
1003 move_player_packet->SetXRot(player->pitch);
1004 move_player_packet->SetYRot(player->yaw);
1005 move_player_packet->SetOnGround(player->on_ground);
1006#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
1007 move_player_packet->SetHorizontalCollision(player->horizontal_collision);
1008#endif
1009 network_manager->Send(move_player_packet);
1010 }
1011 else if (has_moved)
1012 {
1013 std::shared_ptr<ServerboundMovePlayerPacketPos> move_player_packet = std::make_shared<ServerboundMovePlayerPacketPos>();
1014 move_player_packet->SetX(player->position.x);
1015 move_player_packet->SetY(player->position.y);
1016 move_player_packet->SetZ(player->position.z);
1017 move_player_packet->SetOnGround(player->on_ground);
1018#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
1019 move_player_packet->SetHorizontalCollision(player->horizontal_collision);
1020#endif
1021 network_manager->Send(move_player_packet);
1022 }
1023 else if (has_rotated)
1024 {
1025 std::shared_ptr<ServerboundMovePlayerPacketRot> move_player_packet = std::make_shared<ServerboundMovePlayerPacketRot>();
1026 move_player_packet->SetXRot(player->pitch);
1027 move_player_packet->SetYRot(player->yaw);
1028 move_player_packet->SetOnGround(player->on_ground);
1029#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
1030 move_player_packet->SetHorizontalCollision(player->horizontal_collision);
1031#endif
1032 network_manager->Send(move_player_packet);
1033 }
1034 else if (player->on_ground != player->previous_on_ground
1035#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
1036 || player->horizontal_collision != player->previous_horizontal_collision
1037#endif
1038 )
1039 {
1040#if PROTOCOL_VERSION > 754 /* > 1.16.5 */
1041 std::shared_ptr<ServerboundMovePlayerPacketStatusOnly> move_player_packet = std::make_shared<ServerboundMovePlayerPacketStatusOnly>();
1042#else
1043 std::shared_ptr<ServerboundMovePlayerPacket> move_player_packet = std::make_shared<ServerboundMovePlayerPacket>();
1044#endif
1045 move_player_packet->SetOnGround(player->on_ground);
1046#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
1047 move_player_packet->SetHorizontalCollision(player->horizontal_collision);
1048#endif
1049 network_manager->Send(move_player_packet);
1050 }
1051
1052 if (has_moved)
1053 {
1054 player->previous_position = player->position;
1056 }
1057 if (has_rotated)
1058 {
1059 player->previous_yaw = player->yaw;
1060 player->previous_pitch = player->pitch;
1061 }
1062 player->previous_on_ground = player->on_ground;
1063#if PROTOCOL_VERSION > 767 /* > 1.21.1 */
1064 player->previous_horizontal_collision = player->horizontal_collision;
1065#endif
1066
1067#if USE_GUI
1068 if (rendering_manager != nullptr && (has_moved || has_rotated))
1069 {
1070 rendering_manager->SetPosOrientation(
1071 player->position.x,
1072 player->position.y + player->GetEyeHeightImpl(),
1073 player->position.z,
1074 player->yaw,
1075 player->pitch
1076 );
1077 }
1078#endif
1079 }
1080
1082 {
1083 const std::vector<AABB> colliders = world->GetColliders(aabb, movement);
1084 // TODO: add world borders to colliders?
1085 if (colliders.size() == 0)
1086 {
1087 return movement;
1088 }
1089
1090 Vector3<double> collided_movement = movement;
1091
1092 AABB moved_aabb = aabb;
1093 // Collision on Y axis
1094 CollideOneAxis(moved_aabb, collided_movement, 1, colliders);
1095
1096 // Collision on X before Z
1097 if (std::abs(collided_movement.x) > std::abs(collided_movement.z))
1098 {
1099 CollideOneAxis(moved_aabb, collided_movement, 0, colliders);
1100 CollideOneAxis(moved_aabb, collided_movement, 2, colliders);
1101 }
1102 // Collision on X after Z
1103 else
1104 {
1105 CollideOneAxis(moved_aabb, collided_movement, 2, colliders);
1106 CollideOneAxis(moved_aabb, collided_movement, 0, colliders);
1107 }
1108
1109 return collided_movement;
1110 }
1111
1112 void PhysicsManager::CollideOneAxis(AABB& aabb, Vector3<double>& movement, const unsigned int axis, const std::vector<AABB>& colliders) const
1113 {
1114 const Vector3<double> min_aabb = aabb.GetMin();
1115 const Vector3<double> max_aabb = aabb.GetMax();
1116 const int this_axis = axis % 3;
1117 const int axis_1 = (axis + 1) % 3;
1118 const int axis_2 = (axis + 2) % 3;
1119
1120 for (const AABB& collider : colliders)
1121 {
1122 if (std::abs(movement[this_axis]) < 1.0e-7)
1123 {
1124 movement[this_axis] = 0.0;
1125 break;
1126 }
1127 const Vector3<double> min_collider = collider.GetMin();
1128 const Vector3<double> max_collider = collider.GetMax();
1129 if (max_aabb[axis_1] - 1e-7 > min_collider[axis_1] && min_aabb[axis_1] + 1e-7 < max_collider[axis_1] &&
1130 max_aabb[axis_2] - 1e-7 > min_collider[axis_2] && min_aabb[axis_2] + 1e-7 < max_collider[axis_2])
1131 {
1132 if (movement[this_axis] > 0.0 && max_aabb[this_axis] - 1e-7 <= min_collider[this_axis])
1133 {
1134 movement[this_axis] = std::min(min_collider[this_axis] - max_aabb[this_axis], movement[this_axis]);
1135 }
1136 else if (movement[this_axis] < 0.0 && min_aabb[this_axis] + 1e-7 >= max_collider[this_axis])
1137 {
1138 movement[this_axis] = std::max(max_collider[this_axis] - min_aabb[this_axis], movement[this_axis]);
1139 }
1140 }
1141 }
1142 Vector3<double> translation(0.0, 0.0, 0.0);
1143 translation[this_axis] = movement[this_axis];
1144 aabb.Translate(translation);
1145 }
1146
1148 {
1149 return !player->flying &&
1150 player->game_mode != GameType::Spectator &&
1151 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Swimming);
1152 }
1153
1155 {
1156 if (player->game_mode == GameType::Spectator)
1157 {
1158 return false;
1159 }
1160
1161 const Blockstate* feet_block = world->GetBlock(Position(
1162 static_cast<int>(std::floor(player->position.x)),
1163 static_cast<int>(std::floor(player->position.y)),
1164 static_cast<int>(std::floor(player->position.z))
1165 ));
1166
1167 // TODO: if trapdoor AND below block is a ladder with the same facing property
1168 // as the trapdoor then the trapdoor is a climbable block too
1169 return feet_block != nullptr && feet_block->IsClimbable();
1170 }
1171
1173 { // LivingEntity::travel
1174 const bool going_down = player->speed.y <= 0.0;
1175 bool has_slow_falling = false;
1176#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
1177 for (const auto& effect : player->effects)
1178 {
1179 if (effect.type == EntityEffectType::SlowFalling && effect.end > std::chrono::steady_clock::now())
1180 {
1181 has_slow_falling = true;
1182 break;
1183 }
1184 }
1185#endif
1186
1187#if PROTOCOL_VERSION < 766 /* < 1.20.5 */
1188 const double drag = (going_down && has_slow_falling) ? 0.01 : 0.08;
1189#else
1190 const double drag = (going_down && has_slow_falling) ? std::min(0.01, player->GetAttributeGravityValueImpl()) : player->GetAttributeGravityValueImpl();
1191#endif
1192
1193 // Move in water
1194 if (player->in_water && !player->flying)
1195 {
1196 const double init_y = player->position.y;
1197 float water_slow_down = player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting) ? 0.9f : 0.8f;
1198 float inputs_strength = 0.02f;
1199
1200#if PROTOCOL_VERSION < 767 /* < 1.21 */
1201 const Slot boots_armor = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_FEET_ARMOR);
1202 float depth_strider_mult = std::min(static_cast<float>(Utilities::GetEnchantmentLvl(boots_armor, Enchantment::DepthStrider)), 3.0f) / 3.0f;
1203#else
1204 float depth_strider_mult = static_cast<float>(player->GetAttributeWaterMovementEfficiencyValueImpl());
1205#endif
1206 if (!player->on_ground)
1207 {
1208 depth_strider_mult *= 0.5f;
1209 }
1210 if (depth_strider_mult > 0.0)
1211 {
1212 water_slow_down += (0.54600006f - water_slow_down) * depth_strider_mult;
1213 inputs_strength += (static_cast<float>(player->GetAttributeMovementSpeedValueImpl()) - inputs_strength) * depth_strider_mult;
1214 }
1215
1216#if PROTOCOL_VERSION > 340 /* > 1.12.2 */
1217 for (const auto& effect : player->effects)
1218 {
1219 if (effect.type == EntityEffectType::DolphinsGrace && effect.end > std::chrono::steady_clock::now())
1220 {
1221 water_slow_down = 0.96f;
1222 break;
1223 }
1224 }
1225#endif
1226 ApplyInputs(inputs_strength);
1227 ApplyMovement();
1228
1229 if (player->horizontal_collision && player->on_climbable)
1230 {
1231 player->speed.y = 0.2;
1232 }
1233 player->speed.x *= water_slow_down;
1234 player->speed.y *= 0.800000011920929;
1235 player->speed.z *= water_slow_down;
1236
1237 if (!player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting))
1238 {
1239 if (going_down &&
1240 std::abs(player->speed.y - 0.005) >= 0.003 &&
1241 std::abs(player->speed.y - drag / 16.0) < 0.003)
1242 {
1243 player->speed.y = -0.003;
1244 }
1245 else
1246 {
1247 player->speed.y -= drag / 16.0;
1248 }
1249 }
1250
1251 // Jump out of water
1252 if (player->horizontal_collision &&
1253 world->IsFree(player->GetColliderImpl().Inflate(-1e-7) + player->speed + Vector3<double>(0.0, 0.6000000238418579 - player->position.y + init_y, 0.0), true))
1254 {
1255 player->speed.y = 0.30000001192092896;
1256 }
1257 }
1258 // Move in lava
1259 else if (player->in_lava && !player->flying)
1260 {
1261 const double init_y = player->position.y;
1262 ApplyInputs(0.02f);
1263 ApplyMovement();
1264 player->speed *= 0.5;
1265 player->speed.y -= drag / 4.0;
1266 // Jump out of lava
1267 if (player->horizontal_collision &&
1268 world->IsFree(player->GetColliderImpl().Inflate(-1e-7) + player->speed + Vector3<double>(0.0, 0.6000000238418579 - player->position.y + init_y, 0.0), true))
1269 {
1270 player->speed.y = 0.30000001192092896;
1271 }
1272 }
1273 // Move with elytra
1274 else if (player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying))
1275 {
1276 // sqrt(front_vector.x² + front_vector.z²) to follow vanilla code
1277 // it's equal to cos(pitch) (as -90°<=pitch<=90°, cos(pitch) >= 0.0)
1278 const double cos_pitch_from_length = std::sqrt(player->front_vector.x * player->front_vector.x + player->front_vector.z * player->front_vector.z);
1279 const double cos_pitch = std::cos(static_cast<double>(player->pitch * 0.017453292f /* PI/180 */));
1280 const double cos_pitch_sqr = cos_pitch * cos_pitch;
1281 const double horizontal_speed = std::sqrt(player->speed.x * player->speed.x + player->speed.z * player->speed.z);
1282
1283 player->speed.y += drag * (-1.0 + 0.75 * cos_pitch_sqr);
1284
1285 if (player->speed.y < 0.0 && cos_pitch_from_length > 0.0) // cos condition to prevent dividing by 0
1286 {
1287 const double delta_speed = -player->speed.y * 0.1 * cos_pitch_sqr;
1288 player->speed.x += player->front_vector.x * delta_speed / cos_pitch_from_length;
1289 player->speed.y += delta_speed;
1290 player->speed.z += player->front_vector.z * delta_speed / cos_pitch_from_length;
1291 }
1292 if (player->pitch < 0.0 && cos_pitch_from_length > 0.0) // cos condition to prevent dividing by 0
1293 {
1294 // player->front_vector.y == -sin(pitch)
1295 const double delta_speed = horizontal_speed * player->front_vector.y * 0.04;
1296 player->speed.x -= player->front_vector.x * delta_speed / cos_pitch_from_length;
1297 player->speed.y += delta_speed * 3.2;
1298 player->speed.z -= player->front_vector.z * delta_speed / cos_pitch_from_length;
1299 }
1300 if (cos_pitch_from_length > 0.0) // cos condition to prevent dividing by 0
1301 {
1302 player->speed.x += (player->front_vector.x / cos_pitch_from_length * horizontal_speed - player->speed.x) * 0.1;
1303 player->speed.z += (player->front_vector.z / cos_pitch_from_length * horizontal_speed - player->speed.z) * 0.1;
1304 }
1305 player->speed *= Vector3<double>(0.9900000095367432, 0.9800000190734863, 0.9900000095367432);
1306 ApplyMovement();
1307 // Not sure this is required as the server should send an update
1308 // anyway in case it should be set to false
1309 if (player->on_ground)
1310 {
1311 player->SetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying, false);
1312 }
1313 }
1314 // Move generic case
1315 else
1316 {
1317 const Blockstate* below_block = world->GetBlock(GetBlockBelowAffectingMovement());
1318 const float friction = below_block == nullptr ? 0.6f : below_block->GetFriction();
1319 const float inertia = player->on_ground ? friction * 0.91f : 0.91f;
1320
1321 ApplyInputs(player->on_ground ?
1322 (static_cast<float>(player->GetAttributeMovementSpeedValueImpl()) * (0.21600002f / (friction * friction * friction))) :
1323#if PROTOCOL_VERSION < 762 /* < 1.19.4 */
1324 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting) ? 0.02f + 0.006f : 0.02f // flying_speed updated during Player::aiStep call
1325#else
1326 player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Sprinting) ? 0.025999999f : 0.02f // Player::getFlyingSpeed overload
1327#endif
1328 );
1329 if (player->on_climbable)
1330 { // LivingEntity::handleOnClimbable
1331 player->speed.x = std::clamp(player->speed.x, -0.15000000596046448, 0.15000000596046448);
1332 player->speed.y = std::max(player->speed.y, -0.15000000596046448);
1333 // Remove negative Y speed if feet are inside a scaffolding block and not pressing
1334 // sneak, or if not a scaffolding block and pressing sneak
1335 const Blockstate* feet_block = world->GetBlock(Position(
1336 static_cast<int>(std::floor(player->position.x)),
1337 static_cast<int>(std::floor(player->position.y)),
1338 static_cast<int>(std::floor(player->position.z))
1339 ));
1340 if (feet_block != nullptr && feet_block->IsScaffolding() != player->inputs.sneak)
1341 {
1342 player->speed.y = 0.0;
1343 }
1344 player->speed.z = std::clamp(player->speed.z, -0.15000000596046448, 0.15000000596046448);
1345 }
1346 ApplyMovement();
1347 // If colliding and in climbable, go up
1348 if ((player->horizontal_collision || player->inputs.jump) &&
1349 (player->on_climbable) // TODO: or in powder snow with leather boots
1350 )
1351 {
1352 player->speed.y = 0.2;
1353 }
1354
1355 unsigned char levitation = 0;
1356 for (const auto& effect : player->effects)
1357 {
1358 if (effect.type == EntityEffectType::Levitation && effect.end > std::chrono::steady_clock::now())
1359 {
1360 levitation = effect.amplifier + 1; // amplifier is 0 for "Effect I"
1361 break;
1362 }
1363 }
1364 if (levitation > 0)
1365 {
1366 player->speed.y += (0.05 * levitation - player->speed.y) * 0.2;
1367 }
1368 else
1369 {
1370 player->speed.y -= drag;
1371 }
1372 player->speed.x *= inertia;
1373 player->speed.y *= 0.9800000190734863;
1374 player->speed.z *= inertia;
1375 }
1376 }
1377
1379 { // Entity::move
1380 // If no physics, just add speed to position
1381 if (player->game_mode == GameType::Spectator)
1382 {
1383 player->position += player->speed;
1384 return;
1385 }
1386
1387 Vector3<double> movement = player->speed;
1388 // If player is stuck, reset stuck multiplier and set speed to 0
1389 if (player->stuck_speed_multiplier.SqrNorm() > 1e-7)
1390 {
1391 movement *= player->stuck_speed_multiplier;
1392 player->stuck_speed_multiplier *= 0.0;
1393 player->speed *= 0.0;
1394 }
1395
1396#if PROTOCOL_VERSION < 766 /* < 1.20.5 */
1397 constexpr double max_up_step = 0.6;
1398#else
1399 const double max_up_step = player->GetAttributeStepHeightValueImpl();
1400#endif
1401 const AABB player_aabb = player->GetColliderImpl();
1402 if (!player->flying
1403 && movement.y <= 0.0
1404 && player->inputs.sneak
1405 && player->on_ground
1406 )
1407 { // Player::maybeBackOffFromEdge
1408 const double step = 0.05;
1409
1410 while (movement.x != 0.0 && world->IsFree((player_aabb + Vector3<double>(movement.x, -max_up_step, 0.0)).Inflate(-1e-7), false))
1411 {
1412 movement.x = (movement.x < step && movement.x >= -step) ? 0.0 : (movement.x > 0.0 ? (movement.x - step) : (movement.x + step));
1413 }
1414
1415 while (movement.z != 0.0 && world->IsFree((player_aabb + Vector3<double>(0.0, -max_up_step, movement.z)).Inflate(-1e-7), false))
1416 {
1417 movement.z = (movement.z < step && movement.z >= -step) ? 0.0 : (movement.z > 0.0 ? (movement.z - step) : (movement.z + step));
1418 }
1419
1420 while (movement.x != 0.0 && movement.z != 0.0 && world->IsFree((player_aabb + Vector3<double>(movement.x, -max_up_step, movement.z)).Inflate(-1e-7), false))
1421 {
1422 movement.x = (movement.x < step && movement.x >= -step) ? 0.0 : (movement.x > 0.0 ? (movement.x - step) : (movement.x + step));
1423 movement.z = (movement.z < step && movement.z >= -step) ? 0.0 : (movement.z > 0.0 ? (movement.z - step) : (movement.z + step));
1424 }
1425 }
1426
1427 const Vector3<double> movement_before_collisions = movement;
1428 { // Entity::collide
1429 if (movement.SqrNorm() != 0.0)
1430 {
1431 movement = CollideBoundingBox(player_aabb, movement);
1432 }
1433
1434 // Step up if block height delta is < max_up_step
1435 // If already on ground (or collided with the ground while moving down) and horizontal collision
1436 // TODO: changed in 1.21, check if this is still accurate
1437 if ((player->on_ground || (movement.y != movement_before_collisions.y && movement_before_collisions.y < 0.0)) &&
1438 (movement.x != movement_before_collisions.x || movement.z != movement_before_collisions.z))
1439 {
1440 Vector3<double> movement_with_step_up = CollideBoundingBox(player_aabb, Vector3<double>(movement_before_collisions.x, max_up_step, movement_before_collisions.z));
1441 const Vector3<double> horizontal_movement(
1442 movement_before_collisions.x,
1443 0.0,
1444 movement_before_collisions.z
1445 );
1446 const Vector3<double> movement_step_up_only = CollideBoundingBox(AABB(player_aabb.GetCenter() + horizontal_movement * 0.5, player_aabb.GetHalfSize() + horizontal_movement.Abs() * 0.5), Vector3<double>(0.0, max_up_step, 0.0));
1447 if (movement_step_up_only.y < max_up_step)
1448 {
1449 const Vector3<double> check = CollideBoundingBox(player_aabb + movement_step_up_only, horizontal_movement) + movement_step_up_only;
1450 if (check.x * check.x + check.z * check.z > movement_with_step_up.x * movement_with_step_up.x + movement_with_step_up.z * movement_with_step_up.z)
1451 {
1452 movement_with_step_up = check;
1453 }
1454 }
1455 if (movement_with_step_up.x * movement_with_step_up.x + movement_with_step_up.z * movement_with_step_up.z > movement.x * movement.x + movement.z * movement.z)
1456 {
1457 movement = movement_with_step_up + CollideBoundingBox(player_aabb + movement_with_step_up, Vector3<double>(0.0, -movement_with_step_up.y + movement_before_collisions.y, 0.0));
1458 }
1459 }
1460 }
1461
1462 if (movement.SqrNorm() > 1.0e-7)
1463 {
1464 player->position += movement;
1465 }
1466 const bool collision_x = movement_before_collisions.x != movement.x;
1467 const bool collision_y = movement_before_collisions.y != movement.y;
1468 const bool collision_z = movement_before_collisions.z != movement.z;
1469 player->horizontal_collision = collision_x || collision_z;
1470 // TODO: add minor horizontal collision check
1471 { // Entity::setOngroundWithKnownMovement
1472 player->on_ground = movement_before_collisions.y < 0.0 && collision_y;
1473
1474 if (player->on_ground)
1475 {
1476 const double half_width = 0.5 * player->GetWidthImpl();
1477 const AABB feet_slice_aabb(
1479 player->position.x,
1480 player->position.y - 0.5e-6,
1481 player->position.z),
1482 Vector3<double>(half_width, 0.5e-6, half_width));
1483 std::optional<Position> supporting_block_pos = world->GetSupportingBlockPos(feet_slice_aabb);
1484 if (supporting_block_pos.has_value() || player->on_ground_without_supporting_block)
1485 {
1486 player->supporting_block_pos = supporting_block_pos;
1487 }
1488 else
1489 {
1490 player->supporting_block_pos = world->GetSupportingBlockPos(feet_slice_aabb + Vector3<double>(-movement.x, 0.0, -movement.z));
1491 }
1492 player->on_ground_without_supporting_block = !player->supporting_block_pos.has_value();
1493 }
1494 else
1495 {
1496 player->on_ground_without_supporting_block = false;
1497 player->supporting_block_pos = std::optional<Position>();
1498 }
1499 }
1500
1501 // Update speeds
1502 if (collision_x)
1503 {
1504 player->speed.x = 0.0;
1505 }
1506 if (collision_z)
1507 {
1508 player->speed.z = 0.0;
1509 }
1510 if (collision_y)
1511 {
1512 if (player->inputs.sneak)
1513 {
1514 player->speed.y = 0.0;
1515 }
1516 else
1517 {
1518 const Blockstate* block_below = world->GetBlock(Position(
1519 static_cast<int>(std::floor(player->position.x)),
1520 static_cast<int>(std::floor(player->position.y - 0.2)),
1521 static_cast<int>(std::floor(player->position.z))
1522 ));
1523 double new_speed = 0.0;
1524 if (block_below != nullptr)
1525 {
1526 if (block_below->IsSlime())
1527 {
1528 new_speed = -player->speed.y;
1529 }
1530 else if (block_below->IsBed())
1531 {
1532 new_speed = player->speed.y * -0.66f;
1533 }
1534 }
1535 player->speed.y = new_speed;
1536 }
1537 }
1538
1540
1541#if PROTOCOL_VERSION > 578 /* > 1.15.2 */ && PROTOCOL_VERSION < 767 /* < 1.21 */
1542 short soul_speed_lvl = 0;
1543 // Get SoulSpeed bonus from boots
1544 const Slot boots_armor = inventory_manager->GetPlayerInventory()->GetSlot(Window::INVENTORY_FEET_ARMOR);
1545 soul_speed_lvl = Utilities::GetEnchantmentLvl(boots_armor, Enchantment::SoulSpeed);
1546#else
1547 constexpr short soul_speed_lvl = 0;
1548#endif
1549 float block_speed_factor = 1.0f;
1550 const Blockstate* feet_block = world->GetBlock(Position(
1551 static_cast<int>(std::floor(player->position.x)),
1552 static_cast<int>(std::floor(player->position.y)),
1553 static_cast<int>(std::floor(player->position.z))
1554 ));
1555 if (feet_block != nullptr && (feet_block->IsHoney() || (feet_block->IsSoulSand() && soul_speed_lvl == 0)))
1556 {
1557 block_speed_factor = 0.4f;
1558 }
1559 if (block_speed_factor == 1.0f)
1560 {
1561 const Blockstate* below_block = world->GetBlock(GetBlockBelowAffectingMovement());
1562 if (below_block != nullptr && (below_block->IsHoney() || (below_block->IsSoulSand() && soul_speed_lvl == 0)))
1563 {
1564 block_speed_factor = 0.4f;
1565 }
1566 }
1567
1568#if PROTOCOL_VERSION > 766 /* > 1.20.6 */
1569 block_speed_factor = block_speed_factor + static_cast<float>(player->GetAttributeMovementEfficiencyValueImpl()) * (1.0f - block_speed_factor);
1570#endif
1571
1572 player->speed.x *= block_speed_factor;
1573 player->speed.z *= block_speed_factor;
1574 }
1575
1577 {
1578 player->UpdateAbilitiesFlagsImpl();
1579 std::shared_ptr<ServerboundPlayerAbilitiesPacket> abilities_packet = std::make_shared<ServerboundPlayerAbilitiesPacket>();
1580 abilities_packet->SetFlags(player->abilities_flags);
1581#if PROTOCOL_VERSION < 727 /* < 1.16 */
1582 abilities_packet->SetFlyingSpeed(player->flying_speed);
1583 abilities_packet->SetWalkingSpeed(player->walking_speed);
1584#endif
1585 network_manager->Send(abilities_packet);
1586 }
1587
1589 {
1590 const AABB aabb = player->GetColliderImpl().Inflate(-1.0e-7);
1591 const Vector3<double> min_aabb = aabb.GetMin();
1592 const Vector3<double> max_aabb = aabb.GetMax();
1593 Position block_pos;
1594 for (int y = static_cast<int>(std::floor(min_aabb.y)); y <= static_cast<int>(std::floor(max_aabb.y)); ++y)
1595 {
1596 block_pos.y = y;
1597 for (int z = static_cast<int>(std::floor(min_aabb.z)); z <= static_cast<int>(std::floor(max_aabb.z)); ++z)
1598 {
1599 block_pos.z = z;
1600 for (int x = static_cast<int>(std::floor(min_aabb.x)); x <= static_cast<int>(std::floor(max_aabb.x)); ++x)
1601 {
1602 block_pos.x = x;
1603 const Blockstate* block = world->GetBlock(block_pos);
1604 if (block == nullptr)
1605 {
1606 continue;
1607 }
1608 else if (block->IsCobweb())
1609 { // WebBlock::entityInside
1610 player->stuck_speed_multiplier = Vector3<double>(0.25, 0.05000000074505806, 0.25);
1611 }
1612 else if (block->IsBubbleColumn())
1613 {
1614 const Blockstate* above_block = world->GetBlock(block_pos + Position(0, 1, 0));
1615 if (above_block == nullptr || above_block->IsAir())
1616 { // Entity::onAboveBubbleCol
1617 player->speed.y = block->IsDownBubbleColumn() ? std::max(-0.9, player->speed.y - 0.03) : std::min(1.8, player->speed.y + 0.1);
1618 }
1619 else
1620 { // Entity::onInsideBubbleColumn
1621 player->speed.y = block->IsDownBubbleColumn() ? std::max(-0.3, player->speed.y - 0.03) : std::min(0.7, player->speed.y + 0.06);
1622 }
1623 }
1624 else if (block->IsHoney())
1625 {
1626 // Check if sliding down on the side of the block
1627 if (!player->on_ground &&
1628 player->position.y <= y + 0.9375 - 1.0e-7 &&
1629 player->speed.y < -0.08 && (
1630 std::abs(x + 0.5 - player->position.x) + 1.0e-7 > 0.4375 + player->GetWidthImpl() / 2.0 ||
1631 std::abs(z + 0.5 - player->position.z) + 1.0e-7 > 0.4375 + player->GetWidthImpl() / 2.0)
1632 )
1633 {
1634 if (player->speed.y < -0.13)
1635 {
1636 const double factor = -0.05 / player->speed.y;
1637 player->speed.x *= factor;
1638 player->speed.y = -0.05;
1639 player->speed.z *= factor;
1640 }
1641 else
1642 {
1643 player->speed.y = -0.05;
1644 }
1645 }
1646 }
1647 else if (block->IsBerryBush())
1648 { // SweetBerryBushBlock::entityInside
1649 player->stuck_speed_multiplier = Vector3<double>(0.800000011920929, 0.75, 0.800000011920929);
1650 }
1651 else if (block->IsPowderSnow())
1652 { // PowderSnowBlock::entityInside
1653 const Blockstate* feet_block = world->GetBlock(Position(
1654 static_cast<int>(std::floor(player->position.x)),
1655 static_cast<int>(std::floor(player->position.y)),
1656 static_cast<int>(std::floor(player->position.z))
1657 ));
1658 if (feet_block != nullptr && feet_block->IsPowderSnow())
1659 {
1660 player->stuck_speed_multiplier = Vector3<double>(0.8999999761581421, 1.5, 0.8999999761581421);
1661 }
1662 }
1663 }
1664 }
1665 }
1666 }
1667
1669 {
1670 if (player->supporting_block_pos.has_value())
1671 {
1672 Position output = player->supporting_block_pos.value();
1673 output.y = static_cast<int>(std::floor(player->position.y) - 0.500001);
1674 return output;
1675 }
1676
1677 return Position(
1678 static_cast<int>(std::floor(player->position.x)),
1679 static_cast<int>(std::floor(player->position.y - 0.500001)),
1680 static_cast<int>(std::floor(player->position.z))
1681 );
1682 }
1683
1684} //Botcraft
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
const Vector3< double > & GetHalfSize() const
Definition AABB.cpp:33
Vector3< double > GetMin() const
Definition AABB.cpp:18
AABB & Translate(const Vector3< double > &t)
Definition AABB.cpp:98
const Vector3< double > & GetCenter() const
Definition AABB.cpp:28
AABB & Inflate(const double d)
Definition AABB.cpp:92
Vector3< double > GetMax() const
Definition AABB.cpp:23
const Item * GetItem(const ItemId id) const
static AssetsManager & getInstance()
bool IsBerryBush() const
bool IsCobweb() const
bool IsDownBubbleColumn() const
bool IsClimbable() const
bool IsBubbleColumn() const
bool IsPowderSnow() const
float GetFriction() const
float GetFluidHeight() const
Get fluid height for this block.
bool IsScaffolding() const
bool IsSoulSand() const
Vector3< double > speed
Definition Entity.hpp:268
ItemId GetId() const
Definition Item.cpp:16
int GetMaxDurability() const
Get the max durability of this item.
Definition Item.cpp:41
static const std::string speed_modifier_sprinting_key
void RegisterThread(const std::string &name)
Register the current thread in the map.
Definition Logger.cpp:104
static Logger & GetInstance()
Definition Logger.cpp:36
virtual void Handle(ProtocolCraft::ClientboundLoginPacket &packet) override
Vector3< double > CollideBoundingBox(const AABB &aabb, const Vector3< double > &movement) const
Check collisions of an AABB with a given movement.
std::shared_ptr< InventoryManager > inventory_manager
std::shared_ptr< Renderer::RenderingManager > rendering_manager
void PhysicsTick()
Follow minecraft physics related flow in LocalPlayer tick function.
std::shared_ptr< NetworkManager > network_manager
std::shared_ptr< EntityManager > entity_manager
std::optional< int > teleport_id
void FluidPhysics(const bool water)
Perform fluid physics on the player, and set in_water/lava boolean accordingly.
void CollideOneAxis(AABB &aabb, Vector3< double > &movement, const unsigned int axis, const std::vector< AABB > &colliders) const
std::shared_ptr< World > world
void SetDoubleTapCauseSprint(const bool b)
Enable/disable auto triggering of sprint when press/unpress/press forward key in less than 7 ticks (e...
void SendPosition()
Send position/rotation/on_ground to server.
std::atomic< bool > double_tap_cause_sprint
std::atomic< double > ms_per_tick
std::atomic< bool > should_run
Position GetBlockBelowAffectingMovement() const
std::shared_ptr< LocalPlayer > player
void SetSprinting(const bool b) const
void ApplyInputs(const float strength) const
static constexpr short INVENTORY_LEGS_ARMOR
Definition Window.hpp:23
static constexpr short INVENTORY_CHEST_ARMOR
Definition Window.hpp:22
static constexpr short INVENTORY_FEET_ARMOR
Definition Window.hpp:24
bool IsEmptySlot() const
Definition Slot.hpp:92
void SleepUntil(const std::chrono::steady_clock::time_point &end)
int GetDamageCount(const ProtocolCraft::Slot &item)
short GetEnchantmentLvl(const ProtocolCraft::Slot &item, const Botcraft::Enchantment enchantment)
Vector3< int > Position
Definition Vector3.hpp:282
double SqrNorm() const
Definition Vector3.hpp:256
Vector3 Abs() const
Definition Vector3.hpp:199