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