From 028a5735c5f98aa10718c94de07d2f4b4c1fa6b3 Mon Sep 17 00:00:00 2001 From: Tiger Wang Date: Wed, 29 Sep 2021 23:17:03 +0100 Subject: Spectation: add dedicated pathway for spectator mode (#5303) * Spectation: add dedicated pathway for spectator mode + Sync player rotation with spectated entity. + Add dedicated infrastructure to cPlayer for handling spectation, instead of misusing entity riding. * Avoid infinite recursion when exiting spectation, fixes #5296 * AttachTo: Change parameter to reference --- src/Entities/Player.cpp | 179 ++++++++++++++++++++++-------------------------- 1 file changed, 81 insertions(+), 98 deletions(-) (limited to 'src/Entities/Player.cpp') diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 731ecd3e9..1ca37c105 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -140,6 +140,7 @@ cPlayer::cPlayer(const std::shared_ptr & a_Client) : m_BowCharge(0), m_FloaterID(cEntity::INVALID_ID), m_Team(nullptr), + m_Spectating(nullptr), m_TicksUntilNextSave(PLAYER_INVENTORY_SAVE_INTERVAL), m_SkinParts(0) { @@ -658,6 +659,13 @@ void cPlayer::SetCrouch(const bool a_ShouldCrouch) if (a_ShouldCrouch && IsStanding()) { m_BodyStance = BodyStanceCrouching(*this); + + // Handle spectator mode detach: + if (IsGameModeSpectator()) + { + SpectateEntity(nullptr); + } + cRoot::Get()->GetPluginManager()->CallHookPlayerCrouched(*this); } else if (!a_ShouldCrouch && IsCrouched()) @@ -1414,17 +1422,30 @@ void cPlayer::SendRotation(double a_YawDegrees, double a_PitchDegrees) -void cPlayer::SpectateEntity(cEntity * a_Target) +void cPlayer::SpectateEntity(const cEntity * a_Target) { - if ((a_Target == nullptr) || (static_cast(this) == a_Target)) + if (a_Target == this) { - GetClientHandle()->SendCameraSetTo(*this); - m_AttachedTo = nullptr; + // Canonicalise self-pointers: + a_Target = nullptr; + } + + if (m_Spectating == a_Target) + { + // Already spectating requested target: return; } - m_AttachedTo = a_Target; - GetClientHandle()->SendCameraSetTo(*m_AttachedTo); + if (a_Target == nullptr) + { + m_ClientHandle->SendCameraSetTo(*this); + m_ClientHandle->SendPlayerMoveLook(); + m_Spectating = nullptr; + return; + } + + m_Spectating = a_Target; + m_ClientHandle->SendCameraSetTo(*a_Target); } @@ -2522,78 +2543,6 @@ void cPlayer::SetSkinParts(int a_Parts) -void cPlayer::AttachTo(cEntity * a_AttachTo) -{ - // Different attach, if this is a spectator - if (IsGameModeSpectator()) - { - SpectateEntity(a_AttachTo); - return; - } - - Super::AttachTo(a_AttachTo); -} - - - - - -void cPlayer::Detach() -{ - if (m_AttachedTo == nullptr) - { - // The player is not attached to anything. Bail out. - return; - } - - // Different detach, if this is a spectator: - if (IsGameModeSpectator()) - { - GetClientHandle()->SendCameraSetTo(*this); - TeleportToEntity(*m_AttachedTo); - m_AttachedTo = nullptr; - return; - } - - Super::Detach(); - - // If they are teleporting, no need to figure out position: - if (m_IsTeleporting) - { - return; - } - - int PosX = POSX_TOINT; - int PosY = POSY_TOINT; - int PosZ = POSZ_TOINT; - - // Search for a position within an area to teleport player after detachment - // Position must be solid land with two air blocks above. - // If nothing found, player remains where they are - for (int x = PosX - 1; x <= (PosX + 1); ++x) - { - for (int y = PosY; y <= (PosY + 3); ++y) - { - for (int z = PosZ - 1; z <= (PosZ + 1); ++z) - { - if ( - (m_World->GetBlock({ x, y, z }) == E_BLOCK_AIR) && - (m_World->GetBlock({ x, y + 1, z }) == E_BLOCK_AIR) && - cBlockInfo::IsSolid(m_World->GetBlock({ x, y - 1, z })) - ) - { - TeleportToCoords(x + 0.5, y, z + 0.5); - return; - } - } - } - } -} - - - - - AString cPlayer::GetUUIDFileName(const cUUID & a_UUID) { AString UUID = a_UUID.ToLongString(); @@ -2980,18 +2929,18 @@ float cPlayer::GetEnchantmentBlastKnockbackReduction() -bool cPlayer::IsInvisible() const +bool cPlayer::IsCrouched(void) const { - return !m_IsVisible || Super::IsInvisible(); + return std::holds_alternative(m_BodyStance); } -bool cPlayer::IsCrouched(void) const +bool cPlayer::IsSprinting(void) const { - return std::holds_alternative(m_BodyStance); + return std::holds_alternative(m_BodyStance); } @@ -3007,9 +2956,9 @@ bool cPlayer::IsElytraFlying(void) const -bool cPlayer::IsSprinting(void) const +bool cPlayer::IsInvisible() const { - return std::holds_alternative(m_BodyStance); + return !m_IsVisible || Super::IsInvisible(); } @@ -3067,6 +3016,45 @@ void cPlayer::OnAddToWorld(cWorld & a_World) +void cPlayer::OnDetach() +{ + if (m_IsTeleporting) + { + // If they are teleporting, no need to figure out position: + return; + } + + int PosX = POSX_TOINT; + int PosY = POSY_TOINT; + int PosZ = POSZ_TOINT; + + // Search for a position within an area to teleport player after detachment + // Position must be solid land with two air blocks above. + // If nothing found, player remains where they are. + for (int x = PosX - 1; x <= (PosX + 1); ++x) + { + for (int y = PosY; y <= (PosY + 3); ++y) + { + for (int z = PosZ - 1; z <= (PosZ + 1); ++z) + { + if ( + (m_World->GetBlock({ x, y, z }) == E_BLOCK_AIR) && + (m_World->GetBlock({ x, y + 1, z }) == E_BLOCK_AIR) && + cBlockInfo::IsSolid(m_World->GetBlock({ x, y - 1, z })) + ) + { + TeleportToCoords(x + 0.5, y, z + 0.5); + return; + } + } + } + } +} + + + + + void cPlayer::OnRemoveFromWorld(cWorld & a_World) { Super::OnRemoveFromWorld(a_World); @@ -3174,20 +3162,6 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) } } - // Handle the player detach, when the player is in spectator mode - if ( - (IsGameModeSpectator()) && - (m_AttachedTo != nullptr) && - ( - (m_AttachedTo->IsDestroyed()) || // Watching entity destruction - (m_AttachedTo->GetHealth() <= 0) || // Watching entity dead - (IsCrouched()) // Or the player wants to be detached - ) - ) - { - Detach(); - } - if (!a_Chunk.IsValid()) { // Players are ticked even if the parent chunk is invalid. @@ -3217,6 +3191,15 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) m_BowCharge += 1; } + // Handle syncing our position with the entity being spectated: + if (IsGameModeSpectator() && (m_Spectating != nullptr)) + { + SetYaw(m_Spectating->GetYaw()); + SetPitch(m_Spectating->GetPitch()); + SetRoll(m_Spectating->GetRoll()); + SetPosition(m_Spectating->GetPosition()); + } + if (IsElytraFlying()) { // Damage elytra, once per second: -- cgit v1.2.3