summaryrefslogtreecommitdiffstats
path: root/source/cClientHandle.cpp
diff options
context:
space:
mode:
authormadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2012-02-13 22:47:03 +0100
committermadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2012-02-13 22:47:03 +0100
commit4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c (patch)
treefebea3ecd89c0d4aa83924e430bf11366d754733 /source/cClientHandle.cpp
parentNew makefile with automatic *.cpp sourcefile import, automatic header file dependencies and switchable debug / release configuration. gnumake-specific :( (diff)
downloadcuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.tar
cuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.tar.gz
cuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.tar.bz2
cuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.tar.lz
cuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.tar.xz
cuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.tar.zst
cuberite-4f17362aeb80e5339c58a5d3b0fbaeb88d9e701c.zip
Diffstat (limited to '')
-rw-r--r--source/cClientHandle.cpp700
1 files changed, 406 insertions, 294 deletions
diff --git a/source/cClientHandle.cpp b/source/cClientHandle.cpp
index 1019f26c6..92dd93d73 100644
--- a/source/cClientHandle.cpp
+++ b/source/cClientHandle.cpp
@@ -4,7 +4,6 @@
#include "cClientHandle.h"
#include "cServer.h"
#include "cWorld.h"
-#include "cChunk.h"
#include "cPickup.h"
#include "cPluginManager.h"
#include "cPlayer.h"
@@ -24,7 +23,6 @@
#include "cBlockToPickup.h"
#include "cMonster.h"
#include "cChatColor.h"
-#include "cThread.h"
#include "cSocket.h"
#include "cTimer.h"
@@ -67,6 +65,10 @@
#include "packets/cPacket_UpdateSign.h"
#include "packets/cPacket_Ping.h"
#include "packets/cPacket_PlayerListItem.h"
+#include "packets/cPacket_NamedEntitySpawn.h"
+
+
+
#define AddPistonDir(x, y, z, dir, amount) switch (dir) { case 0: (y)-=(amount); break; case 1: (y)+=(amount); break;\
@@ -79,13 +81,6 @@
-// fwd: cServer.cpp:
-extern std::string GetWSAError();
-
-
-
-
-
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cClientHandle:
@@ -98,11 +93,11 @@ cClientHandle::cClientHandle(const cSocket & a_Socket)
, m_Player(NULL)
, m_bKicking(false)
, m_TimeLastPacket(cWorld::GetTime())
- , m_bLoggedIn(false)
, m_bKeepThreadGoing(true)
- , m_bSendLoginResponse(false)
, m_Ping(1000)
- , m_bPositionConfirmed(false)
+ , m_State(csConnected)
+ , m_LastStreamedChunkX(0x7fffffff) // bogus chunk coords to force streaming upon login
+ , m_LastStreamedChunkZ(0x7fffffff)
{
cTimer t1;
m_LastPingTime = t1.GetNowTime();
@@ -137,8 +132,6 @@ cClientHandle::cClientHandle(const cSocket & a_Socket)
m_PacketMap[E_RESPAWN] = new cPacket_Respawn;
m_PacketMap[E_PING] = new cPacket_Ping;
- memset(m_LoadedChunks, 0x00, sizeof(m_LoadedChunks));
-
//////////////////////////////////////////////////////////////////////////
m_pSendThread = new cThread(SendThread, this, "cClientHandle::SendThread");
m_pSendThread->Start (true);
@@ -155,27 +148,27 @@ cClientHandle::~cClientHandle()
{
LOG("Deleting client \"%s\"", GetUsername().c_str());
- for(unsigned int i = 0; i < VIEWDISTANCE*VIEWDISTANCE; i++)
- {
- if (m_LoadedChunks[i]) m_LoadedChunks[i]->RemoveClient(this);
- }
+ // Remove from cSocketThreads, just in case
+ cRoot::Get()->GetServer()->ClientDestroying(this);
+
+ m_LoadedChunks.clear();
+ m_ChunksToSend.clear();
- cWorld::PlayerList PlayerList = cRoot::Get()->GetWorld()->GetAllPlayers();
- for(cWorld::PlayerList::iterator itr = PlayerList.begin(); itr != PlayerList.end(); ++itr)
+ if (m_Player != NULL)
{
- if ((*itr) && (*itr)->GetClientHandle() && !GetUsername().empty())
+ cWorld * World = m_Player->GetWorld();
+ if (!m_Username.empty() && (World != NULL))
{
- std::string NameColor = (m_Player ? m_Player->GetColor() : "");
+ // Send the Offline PlayerList packet:
+ AString NameColor = (m_Player ? m_Player->GetColor() : "");
cPacket_PlayerListItem PlayerList(NameColor + GetUsername(), false, (short)9999);
- (*itr)->GetClientHandle()->Send(PlayerList);
+ World->Broadcast(PlayerList, this);
+
+ // Send the Chat packet:
+ cPacket_Chat Left(m_Username + " left the game!");
+ World->Broadcast(Left, this);
}
}
-
- if (m_Username.size() > 0)
- {
- cPacket_Chat Left(m_Username + " left the game!");
- cRoot::Get()->GetServer()->Broadcast(Left, this);
- }
// First stop sending thread
m_bKeepThreadGoing = false;
@@ -191,16 +184,6 @@ cClientHandle::~cClientHandle()
m_Semaphore.Signal();
delete m_pSendThread;
- while (!m_PendingNrmSendPackets.empty())
- {
- delete *m_PendingNrmSendPackets.begin();
- m_PendingNrmSendPackets.erase(m_PendingNrmSendPackets.begin());
- }
- while (!m_PendingLowSendPackets.empty())
- {
- delete *m_PendingLowSendPackets.begin();
- m_PendingLowSendPackets.erase(m_PendingLowSendPackets.begin());
- }
if (m_Player != NULL)
{
m_Player->SetClientHandle(NULL);
@@ -211,6 +194,18 @@ cClientHandle::~cClientHandle()
{
delete m_PacketMap[i];
}
+
+ {
+ cCSLock Lock(m_SendCriticalSection);
+ for (PacketList::iterator itr = m_PendingNrmSendPackets.begin(); itr != m_PendingNrmSendPackets.end(); ++itr)
+ {
+ delete *itr;
+ }
+ for (PacketList::iterator itr = m_PendingLowSendPackets.begin(); itr != m_PendingLowSendPackets.end(); ++itr)
+ {
+ delete *itr;
+ }
+ }
LOG("ClientHandle at %p destroyed", this);
}
@@ -222,6 +217,12 @@ cClientHandle::~cClientHandle()
void cClientHandle::Destroy()
{
m_bDestroyed = true;
+
+ if ((m_Player != NULL) && (m_Player->GetWorld() != NULL))
+ {
+ RemoveFromAllChunks();
+ }
+
if (m_Socket.IsValid())
{
m_Socket.CloseSocket();
@@ -237,7 +238,10 @@ void cClientHandle::Destroy()
void cClientHandle::Kick(const AString & a_Reason)
{
- LOG("Kicking user \"%s\" for \"%s\"", m_Username.c_str(), a_Reason.c_str());
+ if (m_State >= csAuthenticating) // Don't log pings
+ {
+ LOG("Kicking user \"%s\" for \"%s\"", m_Username.c_str(), a_Reason.c_str());
+ }
Send(cPacket_Disconnect(a_Reason));
m_bKicking = true;
}
@@ -248,7 +252,56 @@ void cClientHandle::Kick(const AString & a_Reason)
void cClientHandle::Authenticate(void)
{
- m_bSendLoginResponse = true;
+ // Spawn player (only serversided, so data is loaded)
+ m_Player = new cPlayer(this, GetUsername());
+
+ cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
+ if (World == NULL)
+ {
+ World = cRoot::Get()->GetDefaultWorld();
+ }
+
+ m_Player->LoginSetGameMode (World->GetGameMode()); //set player's gamemode to server's gamemode at login. TODO: set to last player's gamemode at logout
+
+ m_Player->SetIP (m_Socket.GetIPString());
+
+ cRoot::Get()->GetPluginManager()->CallHook(cPluginManager::E_PLUGIN_PLAYER_SPAWN, 1, m_Player);
+
+ // Return a server login packet
+ cPacket_Login LoginResponse;
+ LoginResponse.m_ProtocolVersion = m_Player->GetUniqueID();
+ //LoginResponse.m_Username = "";
+ LoginResponse.m_ServerMode = m_Player->GetGameMode(); // set gamemode from player.
+ LoginResponse.m_MapSeed = cRoot::Get()->GetWorld()->GetWorldSeed();
+ LoginResponse.m_Dimension = 0;
+ LoginResponse.m_MaxPlayers = (unsigned char)cRoot::Get()->GetWorld()->GetMaxPlayers();
+ LoginResponse.m_Difficulty = 2;
+ Send(LoginResponse);
+
+ // Send Weather if raining:
+ if ((World->GetWeather() == 1) || (World->GetWeather() == 2))
+ {
+ cPacket_NewInvalidState RainPacket;
+ RainPacket.m_Reason = 1; //begin rain
+ Send(RainPacket);
+ }
+
+ // Send time
+ Send(cPacket_TimeUpdate(World->GetWorldTime()));
+
+ // Send inventory
+ m_Player->GetInventory().SendWholeInventory(this);
+
+ // Send health
+ cPacket_UpdateHealth Health;
+ Health.m_Health = (short)m_Player->GetHealth();
+ Health.m_Food = m_Player->GetFood();
+ Health.m_Saturation = m_Player->GetFoodSaturation();
+ Send(Health);
+
+ m_Player->Initialize(World);
+ m_State = csDownloadingWorld;
+ StreamChunks();
}
@@ -257,92 +310,110 @@ void cClientHandle::Authenticate(void)
void cClientHandle::StreamChunks(void)
{
- if (!m_bLoggedIn)
+ if (m_State < csDownloadingWorld)
{
return;
}
assert(m_Player != NULL);
- int ChunkPosX = (int)floor(m_Player->GetPosX() / 16);
- int ChunkPosZ = (int)floor(m_Player->GetPosZ() / 16);
-
+ int ChunkPosX = FAST_FLOOR_DIV(m_Player->GetPosX(), 16);
+ int ChunkPosZ = FAST_FLOOR_DIV(m_Player->GetPosZ(), 16);
+ if ((ChunkPosX == m_LastStreamedChunkX) && (ChunkPosZ == m_LastStreamedChunkZ))
+ {
+ // Already streamed for this position
+ return;
+ }
+ m_LastStreamedChunkX = ChunkPosX;
+ m_LastStreamedChunkZ = ChunkPosZ;
+
+ // DEBUG:
+ LOGINFO("Streaming chunks centered on [%d, %d]", ChunkPosX, ChunkPosZ);
+
cWorld * World = m_Player->GetWorld();
+ assert(World != NULL);
- cChunk * NeededChunks[VIEWDISTANCE * VIEWDISTANCE] = { 0 };
- const int MaxDist = VIEWDISTANCE + GENERATEDISTANCE * 2;
- for (int x = 0; x < MaxDist; x++)
+ // Remove all loaded chunks that are no longer in range:
{
- for (int z = 0; z < MaxDist; z++)
+ cCSLock Lock(m_CSChunkLists);
+ for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end();)
{
- int RelX = x - (MaxDist - 1) / 2;
- int RelZ = z - (MaxDist - 1) / 2;
- cChunk * Chunk = World->GetChunk(ChunkPosX + RelX, 0, ChunkPosZ + RelZ); // Touch all chunks in wide range, so they get generated
- if (
- (x >= GENERATEDISTANCE) &&
- (x < VIEWDISTANCE + GENERATEDISTANCE) &&
- (z >= GENERATEDISTANCE) &&
- (z < VIEWDISTANCE + GENERATEDISTANCE)
- ) // but player only needs chunks in view distance
+ int RelX = (*itr).m_ChunkX - ChunkPosX;
+ int RelZ = (*itr).m_ChunkZ - ChunkPosZ;
+ if ((RelX > VIEWDISTANCE) || (RelX < -VIEWDISTANCE) || (RelZ > VIEWDISTANCE) || (RelZ < -VIEWDISTANCE))
{
- NeededChunks[(x - GENERATEDISTANCE) + (z - GENERATEDISTANCE) * VIEWDISTANCE] = Chunk;
+ World->GetChunk((*itr).m_ChunkX, 0, (*itr).m_ChunkZ)->RemoveClient(this);
+ itr = m_LoadedChunks.erase(itr);
}
- }
- }
-
- cChunk * MissingChunks[VIEWDISTANCE * VIEWDISTANCE];
- memset(MissingChunks, 0, sizeof(MissingChunks));
- unsigned int MissIndex = 0;
- for(int i = 0; i < VIEWDISTANCE * VIEWDISTANCE; i++) // Handshake loop - touch each chunk once
- {
- if (NeededChunks[i] == 0) continue; // Chunk is not yet loaded, so ignore
- // This can cause MissIndex to be 0 and thus chunks will not be unloaded while they are actually out of range,
- // which might actually be a good thing, otherwise it would keep trying to unload chunks until the new chunks are fully loaded
- bool bChunkMissing = true;
- for(int j = 0; j < VIEWDISTANCE*VIEWDISTANCE; j++)
- {
- if (m_LoadedChunks[j] == NeededChunks[i])
+ else
{
- bChunkMissing = false;
- break;
+ ++itr;
}
- }
- if (bChunkMissing)
+ } // for itr - m_LoadedChunks[]
+ for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();)
{
- MissingChunks[MissIndex] = NeededChunks[i];
- MissIndex++;
+ int RelX = (*itr).m_ChunkX - ChunkPosX;
+ int RelZ = (*itr).m_ChunkZ - ChunkPosZ;
+ if ((RelX > VIEWDISTANCE) || (RelX < -VIEWDISTANCE) || (RelZ > VIEWDISTANCE) || (RelZ < -VIEWDISTANCE))
+ {
+ itr = m_ChunksToSend.erase(itr);
+ }
+ else
+ {
+ ++itr;
+ }
}
}
-
- if (MissIndex > 0)
+
+ // Add all chunks that are in range and not yet in m_LoadedChunks:
+ // Queue these smartly - from the center out to the edge
+ for (int d = 0; d <= VIEWDISTANCE; ++d) // cycle through (square) distance, from nearest to furthest
{
- // Chunks are gonna be streamed in, so chunks probably also need to be streamed out <- optimization
- for(int x = 0; x < VIEWDISTANCE; x++)
+ // For each distance add chunks in a hollow square centered around current position:
+ for (int i = -d; i <= d; ++i)
{
- for(int z = 0; z < VIEWDISTANCE; z++)
- {
- cChunk* Chunk = m_LoadedChunks[x + z*VIEWDISTANCE];
- if (Chunk != NULL)
- {
- if ((Chunk->GetPosX() < ChunkPosX - (VIEWDISTANCE - 1) / 2)
- || (Chunk->GetPosX() > ChunkPosX + (VIEWDISTANCE - 1) / 2)
- || (Chunk->GetPosZ() < ChunkPosZ - (VIEWDISTANCE - 1) / 2)
- || (Chunk->GetPosZ() > ChunkPosZ + (VIEWDISTANCE - 1) / 2)
- )
- {
- Chunk->RemoveClient(this);
- Chunk->AsyncUnload(this); // TODO - I think it's possible to unload the chunk immediately instead of next tick
- // I forgot why I made it happen next tick
+ StreamChunk(ChunkPosX + d, ChunkPosZ + i);
+ StreamChunk(ChunkPosX - d, ChunkPosZ + i);
+ } // for i
+ for (int i = -d + 1; i < d; ++i)
+ {
+ StreamChunk(ChunkPosX + i, ChunkPosZ + d);
+ StreamChunk(ChunkPosX + i, ChunkPosZ - d);
+ } // for i
+ } // for d
+
+ // Touch chunks GENERATEDISTANCE ahead to let them generate:
+ for (int d = VIEWDISTANCE + 1; d <= VIEWDISTANCE + GENERATEDISTANCE; ++d) // cycle through (square) distance, from nearest to furthest
+ {
+ // For each distance touch chunks in a hollow square centered around current position:
+ for (int i = -d; i <= d; ++i)
+ {
+ World->GetChunk(ChunkPosX + d, 0, ChunkPosZ + i);
+ World->GetChunk(ChunkPosX - d, 0, ChunkPosZ + i);
+ } // for i
+ for (int i = -d + 1; i < d; ++i)
+ {
+ World->GetChunk(ChunkPosX + i, 0, ChunkPosZ + d);
+ World->GetChunk(ChunkPosX + i, 0, ChunkPosZ - d);
+ } // for i
+ } // for d
+}
- m_LoadedChunks[x + z * VIEWDISTANCE] = NULL;
- }
- }
- }
- }
- StreamChunksSmart(MissingChunks, MissIndex);
- memcpy(m_LoadedChunks, NeededChunks, sizeof(NeededChunks));
+
+void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ)
+{
+ cWorld * World = m_Player->GetWorld();
+ assert(World != NULL);
+
+ cChunkPtr Chunk = World->GetChunk(a_ChunkX, 0, a_ChunkZ);
+ if (!Chunk->HasClient(this))
+ {
+ Chunk->AddClient(this);
+ cCSLock Lock(m_CSChunkLists);
+ m_LoadedChunks.push_back(cChunkCoords(a_ChunkX, a_ChunkZ));
+ m_ChunksToSend.push_back(cChunkCoords(a_ChunkX, a_ChunkZ));
}
}
@@ -350,61 +421,41 @@ void cClientHandle::StreamChunks(void)
-// Sends chunks to the player from the player position outward
-void cClientHandle::StreamChunksSmart(cChunk** a_Chunks, unsigned int a_NumChunks)
+// Removes the client from all chunks. Used when switching worlds or destroying the player
+void cClientHandle::RemoveFromAllChunks()
{
- int X = (int)floor(m_Player->GetPosX() / 16);
- int Y = (int)floor(m_Player->GetPosY() / 128);
- int Z = (int)floor(m_Player->GetPosZ() / 16);
-
- bool bAllDone = false;
- while(!bAllDone)
+ cCSLock Lock(m_CSChunkLists);
+ cWorld * World = m_Player->GetWorld();
+ if (World != NULL)
{
- bAllDone = true;
- int ClosestIdx = -1;
- unsigned int ClosestSqrDist = (unsigned int)-1; // wraps around, becomes biggest number possible
- for(unsigned int i = 0; i < a_NumChunks; ++i)
+ for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end(); ++itr)
{
- if (a_Chunks[i])
- {
- bAllDone = false;
- int DistX = a_Chunks[i]->GetPosX()-X;
- int DistY = a_Chunks[i]->GetPosY()-Y;
- int DistZ = a_Chunks[i]->GetPosZ()-Z;
- unsigned int SqrDist = (DistX*DistX)+(DistY*DistY)+(DistZ*DistZ);
- if (SqrDist < ClosestSqrDist)
- {
- ClosestSqrDist = SqrDist;
- ClosestIdx = i;
- }
- }
- }
- if (ClosestIdx > -1)
- {
- a_Chunks[ClosestIdx]->Send(this);
- a_Chunks[ClosestIdx]->AddClient(this);
- //LOGINFO("CCC: Sending chunk %i %i", a_Chunks[ClosestIdx]->GetPosX(), a_Chunks[ClosestIdx]->GetPosZ());
- a_Chunks[ClosestIdx] = 0;
+ World->GetChunk(itr->m_ChunkX, 0, itr->m_ChunkZ)->RemoveClient(this);
}
}
+ m_LoadedChunks.clear();
+ m_ChunksToSend.clear();
}
-// This removes the client from all chunks. Used when switching worlds
-void cClientHandle::RemoveFromAllChunks()
+void cClientHandle::ChunkJustSent(cChunk * a_ChunkCompleted)
{
- for(int i = 0; i < VIEWDISTANCE*VIEWDISTANCE; i++)
+ cCSLock Lock(m_CSChunkLists);
+ for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end(); ++itr)
{
- if (m_LoadedChunks[i])
+ if (((*itr).m_ChunkX == a_ChunkCompleted->GetPosX()) && ((*itr).m_ChunkZ == a_ChunkCompleted->GetPosZ()))
{
- m_LoadedChunks[i]->RemoveClient(this);
- m_LoadedChunks[i]->AsyncUnload(this);
- m_LoadedChunks[i] = 0;
+ m_ChunksToSend.erase(itr);
+ if ((m_State == csDownloadingWorld) && (m_ChunksToSend.empty()))
+ {
+ CheckIfWorldDownloaded();
+ }
+ return;
}
- }
+ } // for itr - m_ChunksToSend[]
}
@@ -418,63 +469,115 @@ void cClientHandle::HandlePacket(cPacket * a_Packet)
// cPacket* CopiedPacket = a_Packet->Clone();
// a_Packet = CopiedPacket;
- //LOG("Packet: 0x%02x", a_Packet->m_PacketID);
+ // LOG("Recv packet 0x%02x from client \"%s\" (\"%s\")", a_Packet->m_PacketID, m_Socket.GetIPString().c_str(), m_Username.c_str());
if (m_bKicking)
{
return;
}
- if (!m_bLoggedIn)
+ switch (m_State)
{
- switch (a_Packet->m_PacketID)
+ case csConnected:
{
- case E_NEW_INVALID_STATE: // New/Invalid State packet received. I'm guessing the client only sends it when there's a problem with the bed?
+ switch (a_Packet->m_PacketID)
{
- LOGINFO("Got New Invalid State packet");
- break;
+ case E_NEW_INVALID_STATE: // New/Invalid State packet received. I'm guessing the client only sends it when there's a problem with the bed?
+ {
+ LOGINFO("Got New Invalid State packet");
+ break;
+ }
+ case E_PING: HandlePing (); break;
+ case E_HANDSHAKE: HandleHandshake(reinterpret_cast<cPacket_Handshake *>(a_Packet)); break;
+ case E_LOGIN: HandleLogin (reinterpret_cast<cPacket_Login *> (a_Packet)); break;
+
+ // Ignored packets:
+ case E_PLAYERLOOK:
+ case E_PLAYERMOVELOOK:
+ case E_PLAYERPOS:
+ case E_KEEP_ALIVE: break;
+ default: HandleUnexpectedPacket(a_Packet); break;
+ } // switch (PacketType)
+ break;
+ } // case csConnected
+
+ case csAuthenticating:
+ {
+ // Waiting for external authentication, no packets are handled
+ switch (a_Packet->m_PacketID)
+ {
+ // Ignored packets:
+ case E_KEEP_ALIVE:
+ case E_PLAYERLOOK:
+ case E_PLAYERMOVELOOK:
+ case E_PLAYERPOS: break;
+
+ default: HandleUnexpectedPacket(a_Packet); break;
}
+ break;
+ }
- case E_PING: HandlePing(); break;
- case E_HANDSHAKE: HandleHandshake (reinterpret_cast<cPacket_Handshake *> (a_Packet)); break;
- case E_LOGIN: HandleLogin (reinterpret_cast<cPacket_Login *> (a_Packet)); break;
- case E_PLAYERMOVELOOK: HandleMoveLookLogin(reinterpret_cast<cPacket_PlayerMoveLook *>(a_Packet)); break;
- case E_KEEP_ALIVE: break;
- default: HandleDefaultLogin (a_Packet); break;
- } // switch (Packet type)
- }
- else if (!m_bPositionConfirmed) // m_bLoggedIn is true
- {
- switch (a_Packet->m_PacketID)
+ case csDownloadingWorld:
{
- case E_PLAYERMOVELOOK: HandleMoveLookConfirm(reinterpret_cast<cPacket_PlayerMoveLook *>(a_Packet)); break;
- // No default handling, ignore everything else
- } // switch (Packet type)
- } // if (! position confirmed)
+ // Waiting for chunks to stream to client, no packets are handled
+ switch (a_Packet->m_PacketID)
+ {
+ // Ignored packets:
+ case E_KEEP_ALIVE:
+ case E_PLAYERLOOK:
+ case E_PLAYERMOVELOOK:
+ case E_PLAYERPOS: break;
+
+ default: HandleUnexpectedPacket(a_Packet); break;
+ }
+ break;
+ }
+
+ case csConfirmingPos:
+ {
+ switch (a_Packet->m_PacketID)
+ {
+ // Ignored packets:
+ case E_KEEP_ALIVE:
+ case E_PLAYERLOOK:
+ case E_PLAYERPOS: break;
- if (m_bPositionConfirmed)
- {
- switch (a_Packet->m_PacketID)
+ case E_PLAYERMOVELOOK: HandleMoveLookConfirm(reinterpret_cast<cPacket_PlayerMoveLook *>(a_Packet)); break;
+
+ default:
+ {
+ HandleUnexpectedPacket(a_Packet);
+ break;
+ }
+ } // switch (PacketType)
+ break;
+ } // case csConfirmingPos
+
+ case csPlaying:
{
- case E_CREATIVE_INVENTORY_ACTION: HandleCreativeInventory(reinterpret_cast<cPacket_CreativeInventoryAction *>(a_Packet)); break;
- case E_PLAYERPOS: HandlePlayerPos (reinterpret_cast<cPacket_PlayerPosition *> (a_Packet)); break;
- case E_BLOCK_DIG: HandleBlockDig (reinterpret_cast<cPacket_BlockDig *> (a_Packet)); break;
- case E_BLOCK_PLACE: HandleBlockPlace (reinterpret_cast<cPacket_BlockPlace *> (a_Packet)); break;
- case E_PICKUP_SPAWN: HandlePickupSpawn (reinterpret_cast<cPacket_PickupSpawn *> (a_Packet)); break;
- case E_CHAT: HandleChat (reinterpret_cast<cPacket_Chat *> (a_Packet)); break;
- case E_PLAYERLOOK: HandlePlayerLook (reinterpret_cast<cPacket_PlayerLook *> (a_Packet)); break;
- case E_PLAYERMOVELOOK: HandlePlayerMoveLook (reinterpret_cast<cPacket_PlayerMoveLook *> (a_Packet)); break;
- case E_ANIMATION: HandleAnimation (reinterpret_cast<cPacket_ArmAnim *> (a_Packet)); break;
- case E_ITEM_SWITCH: HandleItemSwitch (reinterpret_cast<cPacket_ItemSwitch *> (a_Packet)); break;
- case E_WINDOW_CLOSE: HandleWindowClose (reinterpret_cast<cPacket_WindowClose *> (a_Packet)); break;
- case E_WINDOW_CLICK: HandleWindowClick (reinterpret_cast<cPacket_WindowClick *> (a_Packet)); break;
- case E_UPDATE_SIGN: HandleUpdateSign (reinterpret_cast<cPacket_UpdateSign *> (a_Packet)); break;
- case E_USE_ENTITY: HandleUseEntity (reinterpret_cast<cPacket_UseEntity *> (a_Packet)); break;
- case E_RESPAWN: HandleRespawn();
- case E_DISCONNECT: HandleDisconnect (reinterpret_cast<cPacket_Disconnect *> (a_Packet)); break;
- case E_KEEP_ALIVE: HandleKeepAlive (reinterpret_cast<cPacket_KeepAlive *> (a_Packet)); break;
- } // switch (Packet type)
- } // if (normal game)
+ switch (a_Packet->m_PacketID)
+ {
+ case E_CREATIVE_INVENTORY_ACTION: HandleCreativeInventory(reinterpret_cast<cPacket_CreativeInventoryAction *>(a_Packet)); break;
+ case E_PLAYERPOS: HandlePlayerPos (reinterpret_cast<cPacket_PlayerPosition *> (a_Packet)); break;
+ case E_BLOCK_DIG: HandleBlockDig (reinterpret_cast<cPacket_BlockDig *> (a_Packet)); break;
+ case E_BLOCK_PLACE: HandleBlockPlace (reinterpret_cast<cPacket_BlockPlace *> (a_Packet)); break;
+ case E_PICKUP_SPAWN: HandlePickupSpawn (reinterpret_cast<cPacket_PickupSpawn *> (a_Packet)); break;
+ case E_CHAT: HandleChat (reinterpret_cast<cPacket_Chat *> (a_Packet)); break;
+ case E_PLAYERLOOK: HandlePlayerLook (reinterpret_cast<cPacket_PlayerLook *> (a_Packet)); break;
+ case E_PLAYERMOVELOOK: HandlePlayerMoveLook (reinterpret_cast<cPacket_PlayerMoveLook *> (a_Packet)); break;
+ case E_ANIMATION: HandleAnimation (reinterpret_cast<cPacket_ArmAnim *> (a_Packet)); break;
+ case E_ITEM_SWITCH: HandleItemSwitch (reinterpret_cast<cPacket_ItemSwitch *> (a_Packet)); break;
+ case E_WINDOW_CLOSE: HandleWindowClose (reinterpret_cast<cPacket_WindowClose *> (a_Packet)); break;
+ case E_WINDOW_CLICK: HandleWindowClick (reinterpret_cast<cPacket_WindowClick *> (a_Packet)); break;
+ case E_UPDATE_SIGN: HandleUpdateSign (reinterpret_cast<cPacket_UpdateSign *> (a_Packet)); break;
+ case E_USE_ENTITY: HandleUseEntity (reinterpret_cast<cPacket_UseEntity *> (a_Packet)); break;
+ case E_RESPAWN: HandleRespawn(); break;
+ case E_DISCONNECT: HandleDisconnect (reinterpret_cast<cPacket_Disconnect *> (a_Packet)); break;
+ case E_KEEP_ALIVE: HandleKeepAlive (reinterpret_cast<cPacket_KeepAlive *> (a_Packet)); break;
+ } // switch (Packet type)
+ break;
+ } // case csPlaying
+ } // switch (m_State)
}
@@ -484,7 +587,6 @@ void cClientHandle::HandlePacket(cPacket * a_Packet)
void cClientHandle::HandlePing(void)
{
// Somebody tries to retrieve information about the server
- LOGINFO("Got ping from \"%s\"", m_Socket.GetIPString().c_str());
AString Reply;
Printf(Reply, "%s%s%i%s%i",
cRoot::Get()->GetWorld()->GetDescription().c_str(),
@@ -554,6 +656,7 @@ void cClientHandle::HandleLogin(cPacket_Login * a_Packet)
}
// Schedule for authentication; until then, let them wait (but do not block)
+ m_State = csAuthenticating;
cRoot::Get()->GetAuthenticator().Authenticate(m_Username, cRoot::Get()->GetServer()->GetServerID());
}
@@ -561,48 +664,11 @@ void cClientHandle::HandleLogin(cPacket_Login * a_Packet)
-void cClientHandle::HandleMoveLookLogin(cPacket_PlayerMoveLook * a_Packet)
-{
- // After this is received we're safe to send anything
- if (m_Player == NULL)
- {
- LOGWARNING("Received PlayerMoveLook packet for NULL player from client \"%s\", kicking.", m_Socket.GetIPString().c_str());
- Kick("Hacked client"); // Don't tell them why we don't want them
- return;
- }
- if (!cRoot::Get()->GetPluginManager()->CallHook(cPluginManager::E_PLUGIN_PLAYER_JOIN, 1, m_Player))
- {
- // Broadcast that this player has joined the game! Yay~
- cPacket_Chat Joined(m_Username + " joined the game!");
- cRoot::Get()->GetServer()->Broadcast(Joined, this);
- }
-
- // Now initialize player (adds to entity list etc.)
- cWorld* PlayerWorld = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
- if (!PlayerWorld)
- {
- PlayerWorld = cRoot::Get()->GetDefaultWorld();
- }
- m_Player->Initialize(PlayerWorld);
-
- // Then we can start doing more stuffs! :D
- m_bLoggedIn = true;
- LOG("Player \"%s\" completely logged in", m_Username.c_str());
- StreamChunks();
-
- // Send position
- m_ConfirmPosition = m_Player->GetPosition();
- Send(cPacket_PlayerMoveLook(m_Player));
-}
-
-
-
-
-
-void cClientHandle::HandleDefaultLogin(cPacket * a_Packet)
+void cClientHandle::HandleUnexpectedPacket(cPacket * a_Packet)
{
LOGWARNING(
- "Invalid packet in login state: 0x%02x from client \"%s\", username \"%s\"",
+ "Invalid packet in state %d: 0x%02x from client \"%s\", username \"%s\"",
+ m_State,
a_Packet->m_PacketID,
m_Socket.GetIPString().c_str(),
m_Username.c_str()
@@ -627,11 +693,13 @@ void cClientHandle::HandleMoveLookConfirm(cPacket_PlayerMoveLook * a_Packet)
{
LOGINFO("Exact position confirmed by client!");
}
- m_bPositionConfirmed = true;
+ m_State = csPlaying;
}
else
{
- LOGWARNING("Player \"%s\" sent a weird position confirmation %.2 blocks away, waiting for another confirmation", m_Username.c_str(), Dist);
+ LOGWARNING("Player \"%s\" sent a weird position confirmation %.2f blocks away, retrying", m_Username.c_str(), Dist);
+ m_ConfirmPosition = m_Player->GetPosition();
+ Send(cPacket_PlayerMoveLook(m_Player));
}
}
@@ -1448,14 +1516,12 @@ void cClientHandle::HandleWindowClick(cPacket_WindowClick * a_Packet)
void cClientHandle::HandleUpdateSign(cPacket_UpdateSign * a_Packet)
{
cWorld * World = m_Player->GetWorld();
- cChunk * Chunk = World->GetChunkOfBlock(a_Packet->m_PosX, a_Packet->m_PosY, a_Packet->m_PosZ);
- cBlockEntity * BlockEntity = Chunk->GetBlockEntity(a_Packet->m_PosX, a_Packet->m_PosY, a_Packet->m_PosZ);
- if ((BlockEntity != NULL) && ((BlockEntity->GetBlockType() == E_BLOCK_SIGN_POST) || (BlockEntity->GetBlockType() == E_BLOCK_WALLSIGN)))
+ cChunkPtr Chunk = World->GetChunkOfBlock(a_Packet->m_PosX, a_Packet->m_PosY, a_Packet->m_PosZ);
+ if ((Chunk == NULL) || !Chunk->IsValid())
{
- cSignEntity * Sign = reinterpret_cast<cSignEntity *>(BlockEntity);
- Sign->SetLines(a_Packet->m_Line1, a_Packet->m_Line2, a_Packet->m_Line3, a_Packet->m_Line4);
- Sign->SendTo(NULL); // Broadcast to all players in chunk
+ return;
}
+ Chunk->UpdateSign(a_Packet->m_PosX, a_Packet->m_PosY, a_Packet->m_PosZ, a_Packet->m_Line1, a_Packet->m_Line2, a_Packet->m_Line3, a_Packet->m_Line4);
}
@@ -1554,6 +1620,7 @@ void cClientHandle::Tick(float a_Dt)
cPacket_Disconnect DC("Nooooo!! You timed out! D: Come back!");
m_Socket.Send(&DC);
+ // TODO: Cannot sleep in the tick thread!
cSleep::MilliSleep(1000); // Give packet some time to be received
Destroy();
@@ -1563,63 +1630,37 @@ void cClientHandle::Tick(float a_Dt)
// Send ping packet
if (m_LastPingTime + cClientHandle::PING_TIME_MS <= t1.GetNowTime())
{
- // TODO: why a random ping ID, can't we just use normal ascending numbers?
- MTRand r1;
- m_PingID = r1.randInt();
+ m_PingID++;
cPacket_KeepAlive Ping(m_PingID);
m_PingStartTime = t1.GetNowTime();
Send(Ping);
m_LastPingTime = m_PingStartTime;
}
-
- if (m_bSendLoginResponse)
+
+ if (m_State >= csDownloadingWorld)
{
- m_bSendLoginResponse = false;
-
- // Spawn player (only serversided, so data is loaded)
- m_Player = new cPlayer(this, GetUsername()); // !!DO NOT INITIALIZE!! <- is done after receiving MoveLook Packet
-
- cWorld* World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
- if (!World) World = cRoot::Get()->GetDefaultWorld();
- World->LockEntities();
- m_Player->LoginSetGameMode (World->GetGameMode()); //set player's gamemode to server's gamemode at login. TODO: set to last player's gamemode at logout
-
- m_Player->SetIP (m_Socket.GetIPString());
-
- cRoot::Get()->GetPluginManager()->CallHook(cPluginManager::E_PLUGIN_PLAYER_SPAWN, 1, m_Player);
-
- // Return a server login packet
- cPacket_Login LoginResponse;
- LoginResponse.m_ProtocolVersion = m_Player->GetUniqueID();
- //LoginResponse.m_Username = "";
- LoginResponse.m_ServerMode = m_Player->GetGameMode(); //set gamemode from player.
- LoginResponse.m_MapSeed = cRoot::Get()->GetWorld()->GetWorldSeed();
- LoginResponse.m_Dimension = 0;
- LoginResponse.m_MaxPlayers = (unsigned char)cRoot::Get()->GetWorld()->GetMaxPlayers();
- LoginResponse.m_Difficulty = 2;
- Send(LoginResponse);
-
- // Send Weather if raining:
- if ((World->GetWeather() == 1) || (World->GetWeather() == 2)) {
- cPacket_NewInvalidState RainPacket;
- RainPacket.m_Reason = 1; //begin rain
- Send(RainPacket);
- }
-
- // Send time
- Send(cPacket_TimeUpdate(World->GetWorldTime()));
-
- // Send inventory
- m_Player->GetInventory().SendWholeInventory(this);
-
- // Send health
- cPacket_UpdateHealth Health;
- Health.m_Health = (short)m_Player->GetHealth();
- Health.m_Food = m_Player->GetFood();
- Health.m_Saturation = m_Player->GetFoodSaturation();
- Send(Health);
-
- World->UnlockEntities();
+ cWorld * World = m_Player->GetWorld();
+ cCSLock Lock(m_CSChunkLists);
+ int NumSent = 0;
+ for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();)
+ {
+ cChunkPtr Chunk = World->GetChunk(itr->m_ChunkX, 0, itr->m_ChunkZ);
+ if (!Chunk->IsValid())
+ {
+ ++itr;
+ continue;
+ }
+ // The chunk has become valid, send it and remove it from the list:
+ Chunk->Send(this);
+ itr = m_ChunksToSend.erase(itr);
+ NumSent++;
+ if (NumSent > 10)
+ {
+ // Only send up to 10 chunks per tick, otherwise we'd choke the tick thread
+ break;
+ }
+ CheckIfWorldDownloaded();
+ } // for itr - m_ChunksToSend[]
}
}
@@ -1627,15 +1668,46 @@ void cClientHandle::Tick(float a_Dt)
-void cClientHandle::Send(const cPacket & a_Packet, ENUM_PRIORITY a_Priority /* = E_PRIORITY_NORMAL */)
+void cClientHandle::Send(const cPacket * a_Packet, ENUM_PRIORITY a_Priority /* = E_PRIORITY_NORMAL */)
{
if (m_bKicking) return; // Don't add more packets if player is getting kicked anyway
+ // If it is the packet spawning myself for myself, drop it silently:
+ if (a_Packet->m_PacketID == E_NAMED_ENTITY_SPAWN)
+ {
+ if (((cPacket_NamedEntitySpawn *)a_Packet)->m_UniqueID == m_Player->GetUniqueID())
+ {
+ return;
+ }
+ }
+
+ // Filter out packets that don't belong to a csDownloadingWorld state:
+ if (m_State == csDownloadingWorld)
+ {
+ switch (a_Packet->m_PacketID)
+ {
+ case E_PLAYERMOVELOOK:
+ case E_KEEP_ALIVE:
+ case E_PRE_CHUNK:
+ {
+ // Allow
+ break;
+ }
+ case E_MAP_CHUNK:
+ {
+ CheckIfWorldDownloaded();
+ break;
+ }
+
+ default: return;
+ }
+ }
+
bool bSignalSemaphore = true;
cCSLock Lock(m_SendCriticalSection);
if (a_Priority == E_PRIORITY_NORMAL)
{
- if (a_Packet.m_PacketID == E_REL_ENT_MOVE_LOOK)
+ if (a_Packet->m_PacketID == E_REL_ENT_MOVE_LOOK)
{
PacketList & Packets = m_PendingNrmSendPackets;
for (PacketList::iterator itr = Packets.begin(); itr != Packets.end(); ++itr)
@@ -1645,11 +1717,10 @@ void cClientHandle::Send(const cPacket & a_Packet, ENUM_PRIORITY a_Priority /* =
{
case E_REL_ENT_MOVE_LOOK:
{
- const cPacket_RelativeEntityMoveLook* ThisPacketData = reinterpret_cast< const cPacket_RelativeEntityMoveLook* >(&a_Packet);
+ const cPacket_RelativeEntityMoveLook* ThisPacketData = reinterpret_cast< const cPacket_RelativeEntityMoveLook* >(a_Packet);
cPacket_RelativeEntityMoveLook* PacketData = reinterpret_cast< cPacket_RelativeEntityMoveLook* >(*itr);
if (ThisPacketData->m_UniqueID == PacketData->m_UniqueID)
{
- //LOGINFO("Optimized by removing double packet");
Packets.erase(itr);
bBreak = true;
bSignalSemaphore = false; // Because 1 packet is removed, semaphore count is the same
@@ -1665,11 +1736,11 @@ void cClientHandle::Send(const cPacket & a_Packet, ENUM_PRIORITY a_Priority /* =
}
} // for itr - Packets[]
} // if (E_REL_ENT_MOVE_LOOK
- m_PendingNrmSendPackets.push_back(a_Packet.Clone());
+ m_PendingNrmSendPackets.push_back(a_Packet->Clone());
}
else if (a_Priority == E_PRIORITY_LOW)
{
- m_PendingLowSendPackets.push_back(a_Packet.Clone());
+ m_PendingLowSendPackets.push_back(a_Packet->Clone());
}
Lock.Unlock();
if (bSignalSemaphore)
@@ -1682,6 +1753,44 @@ void cClientHandle::Send(const cPacket & a_Packet, ENUM_PRIORITY a_Priority /* =
+void cClientHandle::CheckIfWorldDownloaded(void)
+{
+ if (m_State != csDownloadingWorld)
+ {
+ return;
+ }
+ cCSLock Lock(m_CSChunkLists);
+ if (m_ChunksToSend.empty())
+ {
+ SendConfirmPosition();
+ }
+}
+
+
+
+
+
+void cClientHandle::SendConfirmPosition(void)
+{
+ LOG("Spawning player \"%s\"", m_Username.c_str());
+
+ m_State = csConfirmingPos;
+
+ if (!cRoot::Get()->GetPluginManager()->CallHook(cPluginManager::E_PLUGIN_PLAYER_JOIN, 1, m_Player))
+ {
+ // Broadcast that this player has joined the game! Yay~
+ cPacket_Chat Joined(m_Username + " joined the game!");
+ cRoot::Get()->GetServer()->Broadcast(Joined, this);
+ }
+
+ m_ConfirmPosition = m_Player->GetPosition();
+ Send(cPacket_PlayerMoveLook(m_Player));
+}
+
+
+
+
+
void cClientHandle::SendThread(void *lpParam)
{
cClientHandle* self = (cClientHandle*)lpParam;
@@ -1737,6 +1846,9 @@ void cClientHandle::SendThread(void *lpParam)
{
break;
}
+
+ // LOG("Sending packet 0x%02x to \"%s\" (\"%s\")", Packet->m_PacketID, self->m_Socket.GetIPString().c_str(), self->m_Username.c_str());
+
bool bSuccess = self->m_Socket.Send(Packet);
if (!bSuccess)