From dd0ce3287ffddf3fd0dd5d7af1ebda60bd871c85 Mon Sep 17 00:00:00 2001
From: LogicParrot <LogicParrot@users.noreply.github.com>
Date: Sun, 27 Mar 2016 20:43:30 +0300
Subject: Players never fall through unloaded chunks or end up inside solids on
 teleport

---
 src/Entities/Player.cpp | 107 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 80 insertions(+), 27 deletions(-)

(limited to 'src/Entities/Player.cpp')

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();
-- 
cgit v1.2.3