summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLogicParrot <LogicParrot@users.noreply.github.com>2016-04-04 13:53:46 +0200
committerLogicParrot <LogicParrot@users.noreply.github.com>2016-04-04 13:53:46 +0200
commitb4a6edc958fbb011e2d7edda514c394e012b4721 (patch)
treea5111ba75442f299bdbb892e4adc73e9522a1f7e
parentMerge pull request #3118 from LogicParrot/fixWorld (diff)
parentPlayers never fall through unloaded chunks or end up inside solids on teleport (diff)
downloadcuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.tar
cuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.tar.gz
cuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.tar.bz2
cuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.tar.lz
cuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.tar.xz
cuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.tar.zst
cuberite-b4a6edc958fbb011e2d7edda514c394e012b4721.zip
-rw-r--r--src/Chunk.cpp2
-rw-r--r--src/ClientHandle.cpp47
-rw-r--r--src/ClientHandle.h9
-rw-r--r--src/Entities/Player.cpp107
-rw-r--r--src/Entities/Player.h2
5 files changed, 138 insertions, 29 deletions
diff --git a/src/Chunk.cpp b/src/Chunk.cpp
index db8966a23..c1baae9b2 100644
--- a/src/Chunk.cpp
+++ b/src/Chunk.cpp
@@ -638,13 +638,13 @@ void cChunk::Tick(std::chrono::milliseconds a_Dt)
}
// Do not move mobs that are detached from the world to neighbors. They're either scheduled for teleportation or for removal.
+ // Because the schedulded destruction is going to look for them in this chunk. See cEntity::destroy.
if (!(*itr)->IsTicking())
{
++itr;
continue;
}
- // Because the schedulded destruction is going to look for them in this chunk. See cEntity::destroy.
if ((((*itr)->GetChunkX() != m_PosX) ||
((*itr)->GetChunkZ() != m_PosZ))
)
diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp
index 6fb2033f4..036b0250d 100644
--- a/src/ClientHandle.cpp
+++ b/src/ClientHandle.cpp
@@ -3,6 +3,7 @@
#include "ClientHandle.h"
#include "Server.h"
#include "World.h"
+#include "Chunk.h"
#include "Entities/Pickup.h"
#include "Bindings/PluginManager.h"
#include "Entities/Player.h"
@@ -65,6 +66,7 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
m_RequestedViewDistance(a_ViewDistance),
m_IPString(a_IPString),
m_Player(nullptr),
+ m_CachedSentChunk(0, 0),
m_HasSentDC(false),
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
m_LastStreamedChunkZ(0x7fffffff),
@@ -336,6 +338,7 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
// Spawn player (only serversided, so data is loaded)
m_Player = new cPlayer(m_Self, GetUsername());
+ InvalidateCachedSentChunk();
m_Self.reset();
cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
@@ -1869,6 +1872,18 @@ void cClientHandle::RemoveFromWorld(void)
+void cClientHandle::InvalidateCachedSentChunk()
+{
+ ASSERT(m_Player != nullptr);
+ // Sets this to a junk value different from the player's current chunk, which invalidates it and
+ // ensures its value will not be used.
+ m_CachedSentChunk = cChunkCoords(m_Player->GetChunkX() + 500, m_Player->GetChunkZ());
+}
+
+
+
+
+
bool cClientHandle::CheckBlockInteractionsRate(void)
{
ASSERT(m_Player != nullptr);
@@ -1926,6 +1941,36 @@ void cClientHandle::Tick(float a_Dt)
return;
}
+
+ // Freeze the player if it is standing on a chunk not yet sent to the client
+ {
+ 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())
+ {
+ // Before iterating m_SentChunks, see if the player's coords equal m_CachedSentChunk
+ // 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;
+ }
+ else
+ {
+ // This block is entered only when the player moves to a new chunk, invalidating the cached coords.
+ // Otherwise the cached coords are used.
+ cCSLock Lock(m_CSChunkLists);
+ auto itr = std::find(m_SentChunks.begin(), m_SentChunks.end(), cChunkCoords(m_Player->GetChunkX(), m_Player->GetChunkZ()));
+ if (itr != m_SentChunks.end())
+ {
+ m_CachedSentChunk = *itr;
+ PlayerIsStandingAtASentChunk = 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:
if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld))
{
@@ -1957,7 +2002,7 @@ void cClientHandle::Tick(float a_Dt)
}
}
- // Unload all chunks that are out of the view distance (all 5 seconds)
+ // Unload all chunks that are out of the view distance (every 5 seconds)
if ((m_Player->GetWorld()->GetWorldAge() % 100) == 0)
{
UnloadOutOfRangeChunks();
diff --git a/src/ClientHandle.h b/src/ClientHandle.h
index 899b0a5ab..123d1b057 100644
--- a/src/ClientHandle.h
+++ b/src/ClientHandle.h
@@ -363,6 +363,8 @@ public: // tolua_export
/** Returns the protocol version number of the protocol that the client is talking. Returns zero if the protocol version is not (yet) known. */
UInt32 GetProtocolVersion(void) const { return m_ProtocolVersion; } // tolua_export
+ void InvalidateCachedSentChunk();
+
private:
friend class cServer; // Needs access to SetSelf()
@@ -408,6 +410,13 @@ private:
cPlayer * m_Player;
+ /** This is an optimization which saves you an iteration of m_SentChunks if you just want to know
+ whether or not the player is standing at a sent chunk.
+ If this is equal to the coordinates of the chunk the player is currrently standing at, then this must be a sent chunk
+ and a member of m_SentChunks.
+ Otherwise, this contains an arbitrary value which should not be used. */
+ cChunkCoords m_CachedSentChunk;
+
bool m_HasSentDC; ///< True if a Disconnect packet has been sent in either direction
// Chunk position when the last StreamChunks() was called; used to avoid re-streaming while in the same chunk
diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp
index 804a92284..f4e7eee44 100644
--- a/src/Entities/Player.cpp
+++ b/src/Entities/Player.cpp
@@ -251,36 +251,11 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
// Handle a frozen player
if (m_IsFrozen)
{
- m_FreezeCounter += 1;
- if (!m_IsManuallyFrozen && a_Chunk.IsValid())
- {
- // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded
- Unfreeze();
- }
- else
- {
- // 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.
- SetPosition(m_FrozenPosition);
- SetSpeed(0, 0, 0);
- if (m_FreezeCounter % 60 == 0)
- {
- BroadcastMovementUpdate(m_ClientHandle.get());
- m_ClientHandle->SendPlayerPosition();
- }
- return;
- }
- }
-
- if (!a_Chunk.IsValid())
- {
- FreezeInternal(GetPosition(), false);
- // This may happen if the cPlayer is created before the chunks have the chance of being loaded / generated (#83)
return;
}
+ ASSERT(a_Chunk.IsValid());
+
super::Tick(a_Dt, a_Chunk);
// Handle charging the bow:
@@ -339,6 +314,79 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
+void cPlayer::TickFreezeCode(bool a_MyChunkIsSent)
+{
+ // 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))
+ {
+ cWorld::cLock Lock(*GetWorld());
+ // If the player was automatically frozen, unfreeze if the chunk the player is inside is loaded
+ Unfreeze();
+
+ // Pull the player out of any solids that might have loaded on them.
+ PREPARE_REL_AND_CHUNK(GetPosition(), *(GetParentChunk()));
+ if (RelSuccess)
+ {
+ int NewY = Rel.y;
+ if (NewY < 0)
+ {
+ NewY = 0;
+ }
+ while (NewY < cChunkDef::Height - 2)
+ {
+ // If we find a position with enough space for the player
+ if (
+ (Chunk->GetBlock(Rel.x, NewY, Rel.z) == E_BLOCK_AIR) &&
+ (Chunk->GetBlock(Rel.x, NewY + 1, Rel.z) == E_BLOCK_AIR)
+ )
+ {
+ // If the found position is not the same as the original
+ if (NewY != Rel.y)
+ {
+ SetPosition(GetPosition().x, NewY, GetPosition().z);
+ GetClientHandle()->SendPlayerPosition();
+ }
+ break;
+ }
+ ++NewY;
+ }
+ }
+ }
+ else
+ {
+ // 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;
+ }
+ }
+ else
+ {
+ if (!a_MyChunkIsSent)
+ {
+ FreezeInternal(GetPosition(), false);
+ }
+ }
+}
+
+
+
+
+
int cPlayer::CalcLevelFromXp(int a_XpTotal)
{
// level 0 to 15
@@ -1392,6 +1440,7 @@ void cPlayer::TeleportToCoords(double a_PosX, double a_PosY, double a_PosZ)
if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ)))
{
SetPosition(a_PosX, a_PosY, a_PosZ);
+ FreezeInternal(GetPosition(), false);
m_LastGroundHeight = static_cast<float>(a_PosY);
m_bIsTeleporting = true;
@@ -1746,6 +1795,9 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d
return false;
}
+ // The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
+ GetClientHandle()->InvalidateCachedSentChunk();
+
// Prevent further ticking in this world
SetIsTicking(false);
@@ -1757,6 +1809,7 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d
// Set position to the new position
SetPosition(a_NewPosition);
+ FreezeInternal(a_NewPosition, false);
// Stop all mobs from targeting this player
StopEveryoneFromTargetingMe();
diff --git a/src/Entities/Player.h b/src/Entities/Player.h
index fae0e6177..777a533c9 100644
--- a/src/Entities/Player.h
+++ b/src/Entities/Player.h
@@ -50,6 +50,8 @@ public:
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
+ void TickFreezeCode(bool a_MyChunkIsSent);
+
virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &) override { UNUSED(a_Dt); }
/** Returns the currently equipped weapon; empty item if none */