1
0

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
This commit is contained in:
Tiger Wang
2021-09-29 23:17:03 +01:00
committed by GitHub
parent 6bbbc52d02
commit 028a5735c5
11 changed files with 152 additions and 142 deletions

View File

@@ -140,6 +140,7 @@ cPlayer::cPlayer(const std::shared_ptr<cClientHandle> & 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<cEntity *>(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<BodyStanceCrouching>(m_BodyStance);
}
bool cPlayer::IsCrouched(void) const
bool cPlayer::IsSprinting(void) const
{
return std::holds_alternative<BodyStanceCrouching>(m_BodyStance);
return std::holds_alternative<BodyStanceSprinting>(m_BodyStance);
}
@@ -3007,9 +2956,9 @@ bool cPlayer::IsElytraFlying(void) const
bool cPlayer::IsSprinting(void) const
bool cPlayer::IsInvisible() const
{
return std::holds_alternative<BodyStanceSprinting>(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: