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