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