Players never fall through unloaded chunks or end up inside solids on teleport
This commit is contained in:
parent
6b1856f226
commit
dd0ce3287f
@ -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.
|
// 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())
|
if (!(*itr)->IsTicking())
|
||||||
{
|
{
|
||||||
++itr;
|
++itr;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because the schedulded destruction is going to look for them in this chunk. See cEntity::destroy.
|
|
||||||
if ((((*itr)->GetChunkX() != m_PosX) ||
|
if ((((*itr)->GetChunkX() != m_PosX) ||
|
||||||
((*itr)->GetChunkZ() != m_PosZ))
|
((*itr)->GetChunkZ() != m_PosZ))
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include "ClientHandle.h"
|
#include "ClientHandle.h"
|
||||||
#include "Server.h"
|
#include "Server.h"
|
||||||
#include "World.h"
|
#include "World.h"
|
||||||
|
#include "Chunk.h"
|
||||||
#include "Entities/Pickup.h"
|
#include "Entities/Pickup.h"
|
||||||
#include "Bindings/PluginManager.h"
|
#include "Bindings/PluginManager.h"
|
||||||
#include "Entities/Player.h"
|
#include "Entities/Player.h"
|
||||||
@ -65,6 +66,7 @@ cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
|
|||||||
m_RequestedViewDistance(a_ViewDistance),
|
m_RequestedViewDistance(a_ViewDistance),
|
||||||
m_IPString(a_IPString),
|
m_IPString(a_IPString),
|
||||||
m_Player(nullptr),
|
m_Player(nullptr),
|
||||||
|
m_CachedSentChunk(0, 0),
|
||||||
m_HasSentDC(false),
|
m_HasSentDC(false),
|
||||||
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
|
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
|
||||||
m_LastStreamedChunkZ(0x7fffffff),
|
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)
|
// Spawn player (only serversided, so data is loaded)
|
||||||
m_Player = new cPlayer(m_Self, GetUsername());
|
m_Player = new cPlayer(m_Self, GetUsername());
|
||||||
|
InvalidateCachedSentChunk();
|
||||||
m_Self.reset();
|
m_Self.reset();
|
||||||
|
|
||||||
cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
|
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)
|
bool cClientHandle::CheckBlockInteractionsRate(void)
|
||||||
{
|
{
|
||||||
ASSERT(m_Player != nullptr);
|
ASSERT(m_Player != nullptr);
|
||||||
@ -1926,6 +1941,36 @@ void cClientHandle::Tick(float a_Dt)
|
|||||||
return;
|
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 the chunk the player's in was just sent, spawn the player:
|
||||||
if (m_HasSentPlayerChunk && (m_State == csDownloadingWorld))
|
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)
|
if ((m_Player->GetWorld()->GetWorldAge() % 100) == 0)
|
||||||
{
|
{
|
||||||
UnloadOutOfRangeChunks();
|
UnloadOutOfRangeChunks();
|
||||||
|
@ -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. */
|
/** 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
|
UInt32 GetProtocolVersion(void) const { return m_ProtocolVersion; } // tolua_export
|
||||||
|
|
||||||
|
void InvalidateCachedSentChunk();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
friend class cServer; // Needs access to SetSelf()
|
friend class cServer; // Needs access to SetSelf()
|
||||||
@ -408,6 +410,13 @@ private:
|
|||||||
|
|
||||||
cPlayer * m_Player;
|
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
|
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
|
// Chunk position when the last StreamChunks() was called; used to avoid re-streaming while in the same chunk
|
||||||
|
@ -251,36 +251,11 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
|||||||
// Handle a frozen player
|
// Handle a frozen player
|
||||||
if (m_IsFrozen)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASSERT(a_Chunk.IsValid());
|
||||||
|
|
||||||
super::Tick(a_Dt, a_Chunk);
|
super::Tick(a_Dt, a_Chunk);
|
||||||
|
|
||||||
// Handle charging the bow:
|
// 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)
|
int cPlayer::CalcLevelFromXp(int a_XpTotal)
|
||||||
{
|
{
|
||||||
// level 0 to 15
|
// 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)))
|
if (!cRoot::Get()->GetPluginManager()->CallHookEntityTeleport(*this, m_LastPosition, Vector3d(a_PosX, a_PosY, a_PosZ)))
|
||||||
{
|
{
|
||||||
SetPosition(a_PosX, a_PosY, a_PosZ);
|
SetPosition(a_PosX, a_PosY, a_PosZ);
|
||||||
|
FreezeInternal(GetPosition(), false);
|
||||||
m_LastGroundHeight = static_cast<float>(a_PosY);
|
m_LastGroundHeight = static_cast<float>(a_PosY);
|
||||||
m_bIsTeleporting = true;
|
m_bIsTeleporting = true;
|
||||||
|
|
||||||
@ -1746,6 +1795,9 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The clienthandle caches the coords of the chunk we're standing at. Invalidate this.
|
||||||
|
GetClientHandle()->InvalidateCachedSentChunk();
|
||||||
|
|
||||||
// Prevent further ticking in this world
|
// Prevent further ticking in this world
|
||||||
SetIsTicking(false);
|
SetIsTicking(false);
|
||||||
|
|
||||||
@ -1757,6 +1809,7 @@ bool cPlayer::DoMoveToWorld(cWorld * a_World, bool a_ShouldSendRespawn, Vector3d
|
|||||||
|
|
||||||
// Set position to the new position
|
// Set position to the new position
|
||||||
SetPosition(a_NewPosition);
|
SetPosition(a_NewPosition);
|
||||||
|
FreezeInternal(a_NewPosition, false);
|
||||||
|
|
||||||
// Stop all mobs from targeting this player
|
// Stop all mobs from targeting this player
|
||||||
StopEveryoneFromTargetingMe();
|
StopEveryoneFromTargetingMe();
|
||||||
|
@ -50,6 +50,8 @@ public:
|
|||||||
|
|
||||||
virtual void Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) override;
|
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); }
|
virtual void HandlePhysics(std::chrono::milliseconds a_Dt, cChunk &) override { UNUSED(a_Dt); }
|
||||||
|
|
||||||
/** Returns the currently equipped weapon; empty item if none */
|
/** Returns the currently equipped weapon; empty item if none */
|
||||||
|
Loading…
Reference in New Issue
Block a user