From a431b45641d119a91a74cd88768389eebeb54a68 Mon Sep 17 00:00:00 2001 From: LogicParrot Date: Tue, 5 Apr 2016 11:45:09 +0300 Subject: Improved player freeze code --- Server/Plugins/APIDump/APIDesc.lua | 1 - src/Chunk.cpp | 15 +++++- src/Chunk.h | 1 - src/ChunkMap.cpp | 5 +- src/ClientHandle.cpp | 35 +++++++------ src/ClientHandle.h | 2 + src/Entities/Player.cpp | 105 +++++++++++++++++++++---------------- src/Entities/Player.h | 11 +--- 8 files changed, 99 insertions(+), 76 deletions(-) diff --git a/Server/Plugins/APIDump/APIDesc.lua b/Server/Plugins/APIDump/APIDesc.lua index 5217cbc28..800396b4e 100644 --- a/Server/Plugins/APIDump/APIDesc.lua +++ b/Server/Plugins/APIDump/APIDesc.lua @@ -1946,7 +1946,6 @@ a_Player:OpenWindow(Window); GetFoodPoisonedTicksRemaining = { Params = "", Return = "", Notes = "Returns the number of ticks left for the food posoning effect" }, GetFoodSaturationLevel = { Params = "", Return = "number", Notes = "Returns the food saturation (overcharge of the food level, is depleted before food level)" }, GetFoodTickTimer = { Params = "", Return = "", Notes = "Returns the number of ticks past the last food-based heal or damage action; when this timer reaches 80, a new heal / damage is applied." }, - GetFrozenDuration = { Params = "", Return = "number", Notes = "Returns the number of ticks since the player was frozen" }, GetGameMode = { Return = "{{Globals#GameMode|GameMode}}", Notes = "Returns the player's gamemode. The player may have their gamemode unassigned, in which case they inherit the gamemode from the current {{cWorld|world}}.
NOTE: Instead of comparing the value returned by this function to the gmXXX constants, use the IsGameModeXXX() functions. These functions handle the gamemode inheritance automatically."}, GetIP = { Return = "string", Notes = "Returns the IP address of the player, if available. Returns an empty string if there's no IP to report."}, GetInventory = { Return = "{{cInventory|Inventory}}", Notes = "Returns the player's inventory"}, diff --git a/src/Chunk.cpp b/src/Chunk.cpp index c1baae9b2..bc0de0516 100644 --- a/src/Chunk.cpp +++ b/src/Chunk.cpp @@ -604,7 +604,19 @@ void cChunk::SpawnMobs(cMobSpawner & a_MobSpawner) void cChunk::Tick(std::chrono::milliseconds a_Dt) { - m_IsInTick = true; + // If we are not valid, tick players and bailout + if (!IsValid()) + { + for (auto Entity : m_Entities) + { + if (Entity->IsPlayer()) + { + Entity->Tick(a_Dt, *this); + } + } + return; + } + BroadcastPendingBlockChanges(); CheckBlocks(); @@ -668,7 +680,6 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt) } // for itr - m_Entitites[] ApplyWeatherToTop(); - m_IsInTick = false; } diff --git a/src/Chunk.h b/src/Chunk.h index e1f44b197..557fe332a 100644 --- a/src/Chunk.h +++ b/src/Chunk.h @@ -502,7 +502,6 @@ private: /** If the chunk fails to load, should it be queued in the generator or reset back to invalid? */ bool m_ShouldGenerateIfLoadFailed; - bool m_IsInTick; // True if the chunk is executing the tick() method. bool m_IsLightValid; // True if the blocklight and skylight are calculated bool m_IsDirty; // True if the chunk has changed since it was last saved bool m_IsSaving; // True if the chunk is being saved diff --git a/src/ChunkMap.cpp b/src/ChunkMap.cpp index 629acb341..156d93e2f 100644 --- a/src/ChunkMap.cpp +++ b/src/ChunkMap.cpp @@ -2920,8 +2920,9 @@ void cChunkMap::cChunkLayer::Tick(std::chrono::milliseconds a_Dt) { for (size_t i = 0; i < ARRAYCOUNT(m_Chunks); i++) { - // Only tick chunks that are valid and should be ticked: - if ((m_Chunks[i] != nullptr) && m_Chunks[i]->IsValid() && m_Chunks[i]->ShouldBeTicked()) + // Only tick chunks that should be ticked: + // Note that chunks that are not IsValid() will only tick players and then bailout + if ((m_Chunks[i] != nullptr) && m_Chunks[i]->ShouldBeTicked()) { m_Chunks[i]->Tick(a_Dt); } diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 036b0250d..f14ec193e 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -764,6 +764,12 @@ void cClientHandle::HandlePlayerPos(double a_PosX, double a_PosY, double a_PosZ, return; } + if (m_Player->IsFrozen()) + { + // Ignore client-side updates if the player is frozen + return; + } + Vector3d NewPosition(a_PosX, a_PosY, a_PosZ); Vector3d OldPosition = GetPlayer()->GetPosition(); auto PreviousIsOnGround = GetPlayer()->IsOnGround(); @@ -1864,8 +1870,6 @@ void cClientHandle::RemoveFromWorld(void) // Here, we set last streamed values to bogus ones so everything is resent m_LastStreamedChunkX = 0x7fffffff; m_LastStreamedChunkZ = 0x7fffffff; - - m_HasSentPlayerChunk = false; } @@ -1884,6 +1888,15 @@ void cClientHandle::InvalidateCachedSentChunk() +bool cClientHandle::IsPlayerChunkSent() +{ + return m_HasSentPlayerChunk; +} + + + + + bool cClientHandle::CheckBlockInteractionsRate(void) { ASSERT(m_Player != nullptr); @@ -1943,8 +1956,9 @@ void cClientHandle::Tick(float a_Dt) // Freeze the player if it is standing on a chunk not yet sent to the client + m_HasSentPlayerChunk = false; + if (m_Player->GetParentChunk() != nullptr) { - bool PlayerIsStandingAtASentChunk = false; // If the chunk is invalid, do not bother checking if it's sent to the client, it is definitely not if (m_Player->GetParentChunk()->IsValid()) { @@ -1952,7 +1966,7 @@ void cClientHandle::Tick(float a_Dt) // If so, the chunk has been sent to the client. This is an optimization that saves an iteration of m_SentChunks. if (cChunkCoords(m_Player->GetChunkX(), m_Player->GetChunkZ()) == m_CachedSentChunk) { - PlayerIsStandingAtASentChunk = true; + m_HasSentPlayerChunk = true; } else { @@ -1963,12 +1977,10 @@ void cClientHandle::Tick(float a_Dt) if (itr != m_SentChunks.end()) { m_CachedSentChunk = *itr; - PlayerIsStandingAtASentChunk = true; + m_HasSentPlayerChunk = true; } } } - // The player will freeze itself if it is standing on a chunk not yet sent to the client - m_Player->TickFreezeCode(PlayerIsStandingAtASentChunk); } // If the chunk the player's in was just sent, spawn the player: @@ -2281,15 +2293,6 @@ void cClientHandle::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializ cCSLock Lock(m_CSChunkLists); m_SentChunks.push_back(cChunkCoords(a_ChunkX, a_ChunkZ)); } - - // If it is the chunk the player's in, make them spawn (in the tick thread): - if ((m_State == csAuthenticated) || (m_State == csDownloadingWorld)) - { - if ((a_ChunkX == m_Player->GetChunkX()) && (a_ChunkZ == m_Player->GetChunkZ())) - { - m_HasSentPlayerChunk = true; - } - } } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 123d1b057..0bcf0b9fe 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -365,6 +365,8 @@ public: // tolua_export void InvalidateCachedSentChunk(); + bool IsPlayerChunkSent(); + private: friend class cServer; // Needs access to SetSelf() diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index f4e7eee44..83f134bf5 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -65,7 +65,6 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : m_GameMode(eGameMode_NotSet), m_IP(""), m_ClientHandle(a_Client), - m_FreezeCounter(-1), m_NormalMaxSpeed(1.0), m_SprintingMaxSpeed(1.3), m_FlyingMaxSpeed(1.0), @@ -119,7 +118,7 @@ cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) : m_LastGroundHeight = static_cast(GetPosY()); m_Stance = GetPosY() + 1.62; - FreezeInternal(GetPosition(), false); // Freeze. Will be unfrozen once the chunk is loaded + if (m_GameMode == gmNotSet) { @@ -246,13 +245,16 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) ASSERT(!"Player ticked whilst in the process of destruction!"); } + m_Stats.AddValue(statMinutesPlayed, 1); // Handle a frozen player + TickFreezeCode(); if (m_IsFrozen) { return; } + ASSERT((GetParentChunk() != nullptr) && (GetParentChunk()->IsValid())); ASSERT(a_Chunk.IsValid()); @@ -314,18 +316,13 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) -void cPlayer::TickFreezeCode(bool a_MyChunkIsSent) +void cPlayer::TickFreezeCode() { - // This function is ticked by the player's client handle. This ensures it always ticks, even if the player - // is standing in an unloaded chunk, unlike cPlayer::Tick. We need this because the freeze handling code must - // also tick in unloaded chunks. if (m_IsFrozen) { - m_FreezeCounter += 1; - if ((!m_IsManuallyFrozen) && (a_MyChunkIsSent)) + if ((!m_IsManuallyFrozen) && (GetClientHandle()->IsPlayerChunkSent())) { - cWorld::cLock Lock(*GetWorld()); - // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded + // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded and sent Unfreeze(); // Pull the player out of any solids that might have loaded on them. @@ -357,26 +354,16 @@ void cPlayer::TickFreezeCode(bool a_MyChunkIsSent) } } } - else + else if (GetWorld()->GetWorldAge() % 4096 == 0) { - // If the player was externally / manually frozen (plugin, etc.) or if the chunk isn't loaded yet: - // 1. Set the location to m_FrozenPosition every tick. - // 2. Zero out the speed every tick. - // 3. Send location updates every 60 ticks. - - if ((m_FreezeCounter % 60 == 0) || ((m_FrozenPosition - GetPosition()).SqrLength() > 2 * 2)) - { - SetPosition(m_FrozenPosition); - SetSpeed(0, 0, 0); - BroadcastMovementUpdate(m_ClientHandle.get()); - m_ClientHandle->SendPlayerPosition(); - } - return; + // Despite the client side freeze, the player may be able to move a little by + // Jumping or canceling flight. Re-freeze every now and then + FreezeInternal(GetPosition(), m_IsManuallyFrozen); } } else { - if (!a_MyChunkIsSent) + if (!GetClientHandle()->IsPlayerChunkSent()) { FreezeInternal(GetPosition(), false); } @@ -772,8 +759,9 @@ double cPlayer::GetMaxSpeed(void) const void cPlayer::SetNormalMaxSpeed(double a_Speed) { m_NormalMaxSpeed = a_Speed; - if (!m_IsSprinting && !m_IsFlying) + if (!m_IsSprinting && !m_IsFlying && !m_IsFrozen) { + // If we are frozen, we do not send this yet. We send when unfreeze() is called m_ClientHandle->SendPlayerMaxSpeed(); } } @@ -785,8 +773,9 @@ void cPlayer::SetNormalMaxSpeed(double a_Speed) void cPlayer::SetSprintingMaxSpeed(double a_Speed) { m_SprintingMaxSpeed = a_Speed; - if (m_IsSprinting && !m_IsFlying) + if (m_IsSprinting && !m_IsFlying && !m_IsFrozen) { + // If we are frozen, we do not send this yet. We send when unfreeze() is called m_ClientHandle->SendPlayerMaxSpeed(); } } @@ -800,7 +789,11 @@ void cPlayer::SetFlyingMaxSpeed(double a_Speed) m_FlyingMaxSpeed = a_Speed; // Update the flying speed, always: - m_ClientHandle->SendPlayerAbilities(); + if (!m_IsFrozen) + { + // If we are frozen, we do not send this yet. We send when unfreeze() is called + m_ClientHandle->SendPlayerAbilities(); + } } @@ -907,7 +900,11 @@ void cPlayer::SetFlying(bool a_IsFlying) } m_IsFlying = a_IsFlying; - m_ClientHandle->SendPlayerAbilities(); + if (!m_IsFrozen) + { + // If we are frozen, we do not send this yet. We send when unfreeze() is called + m_ClientHandle->SendPlayerAbilities(); + } } @@ -1471,22 +1468,14 @@ bool cPlayer::IsFrozen() -int cPlayer::GetFrozenDuration() -{ - return m_FreezeCounter; -} - - - - - void cPlayer::Unfreeze() { - m_FreezeCounter = -1; + GetClientHandle()->SendPlayerAbilities(); + GetClientHandle()->SendPlayerMaxSpeed(); + m_IsFrozen = false; - SetPosition(m_FrozenPosition); - BroadcastMovementUpdate(m_ClientHandle.get()); - m_ClientHandle->SendPlayerPosition(); + BroadcastMovementUpdate(GetClientHandle()); + GetClientHandle()->SendPlayerPosition(); } @@ -1545,8 +1534,12 @@ void cPlayer::ForceSetSpeed(const Vector3d & a_Speed) void cPlayer::DoSetSpeed(double a_SpeedX, double a_SpeedY, double a_SpeedZ) { + if (m_IsFrozen) + { + // Do not set speed to a frozen client + return; + } super::DoSetSpeed(a_SpeedX, a_SpeedY, a_SpeedZ); - // Send the speed to the client so he actualy moves m_ClientHandle->SendEntityVelocity(*this); } @@ -1768,9 +1761,33 @@ void cPlayer::TossItems(const cItems & a_Items) void cPlayer::FreezeInternal(const Vector3d & a_Location, bool a_ManuallyFrozen) { + SetSpeed(0, 0, 0); + SetPosition(a_Location); m_IsFrozen = true; - m_FrozenPosition = a_Location; m_IsManuallyFrozen = a_ManuallyFrozen; + + double NormalMaxSpeed = GetNormalMaxSpeed(); + double SprintMaxSpeed = GetSprintingMaxSpeed(); + double FlyingMaxpeed = GetFlyingMaxSpeed(); + bool IsFlying = m_IsFlying; + + // Set the client-side speed to 0 + m_NormalMaxSpeed = 0; + m_SprintingMaxSpeed = 0; + m_FlyingMaxSpeed = 0; + m_IsFlying = true; + + // Send the client its fake speed and max speed of 0 + GetClientHandle()->SendPlayerMoveLook(); + GetClientHandle()->SendPlayerAbilities(); + GetClientHandle()->SendPlayerMaxSpeed(); + GetClientHandle()->SendEntityVelocity(*this); + + // Keep the server side speed variables as they were in the first place + m_NormalMaxSpeed = NormalMaxSpeed; + m_SprintingMaxSpeed = SprintMaxSpeed; + m_FlyingMaxSpeed = FlyingMaxpeed; + m_IsFlying = IsFlying; } diff --git a/src/Entities/Player.h b/src/Entities/Player.h index 777a533c9..f04bcdd39 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -50,7 +50,7 @@ public: virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override; - void TickFreezeCode(bool a_MyChunkIsSent); + void TickFreezeCode(); virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &) override { UNUSED(a_Dt); } @@ -148,9 +148,6 @@ public: /** Is the player frozen? */ bool IsFrozen(); - /** How long has the player been frozen? */ - int GetFrozenDuration(); - /** Cancels Freeze(...) and allows the player to move naturally. */ void Unfreeze(); @@ -605,15 +602,9 @@ protected: cSlotNums m_InventoryPaintSlots; - /** if m_IsFrozen is true, we lock m_Location to this position. */ - Vector3d m_FrozenPosition; - /** If true, we are locking m_Position to m_FrozenPosition. */ bool m_IsFrozen; - /** */ - int m_FreezeCounter; - /** Was the player frozen manually by a plugin or automatically by the server? */ bool m_IsManuallyFrozen; -- cgit v1.2.3