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