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