1
0

Rewritten most of the code for multithreading; still not 100%, but getting there. If this commit proves to be too problematic, we can always undo it.

git-svn-id: http://mc-server.googlecode.com/svn/trunk@251 0a769ca7-a7f5-676a-18bf-c427514a06d6
This commit is contained in:
madmaxoft@gmail.com
2012-02-13 21:47:03 +00:00
parent 0a46c065bf
commit 4f17362aeb
87 changed files with 6915 additions and 2803 deletions

View File

@@ -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,26 +148,26 @@ 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);
}
cWorld::PlayerList PlayerList = cRoot::Get()->GetWorld()->GetAllPlayers();
for(cWorld::PlayerList::iterator itr = PlayerList.begin(); itr != PlayerList.end(); ++itr)
{
if ((*itr) && (*itr)->GetClientHandle() && !GetUsername().empty())
{
std::string NameColor = (m_Player ? m_Player->GetColor() : "");
cPacket_PlayerListItem PlayerList(NameColor + GetUsername(), false, (short)9999);
(*itr)->GetClientHandle()->Send(PlayerList);
}
}
// Remove from cSocketThreads, just in case
cRoot::Get()->GetServer()->ClientDestroying(this);
if (m_Username.size() > 0)
m_LoadedChunks.clear();
m_ChunksToSend.clear();
if (m_Player != NULL)
{
cPacket_Chat Left(m_Username + " left the game!");
cRoot::Get()->GetServer()->Broadcast(Left, this);
cWorld * World = m_Player->GetWorld();
if (!m_Username.empty() && (World != NULL))
{
// Send the Offline PlayerList packet:
AString NameColor = (m_Player ? m_Player->GetColor() : "");
cPacket_PlayerListItem PlayerList(NameColor + GetUsername(), false, (short)9999);
World->Broadcast(PlayerList, this);
// Send the Chat packet:
cPacket_Chat Left(m_Username + " left the game!");
World->Broadcast(Left, this);
}
}
// First stop sending thread
@@ -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,135 +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);
}
else
{
++itr;
}
} // for itr - m_LoadedChunks[]
for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end();)
{
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;
}
}
}
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
// 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
{
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++)
// For each distance add chunks in a hollow square centered around current position:
for (int i = -d; i <= d; ++i)
{
if (m_LoadedChunks[j] == NeededChunks[i])
{
bChunkMissing = false;
break;
}
}
if (bChunkMissing)
StreamChunk(ChunkPosX + d, ChunkPosZ + i);
StreamChunk(ChunkPosX - d, ChunkPosZ + i);
} // for i
for (int i = -d + 1; i < d; ++i)
{
MissingChunks[MissIndex] = NeededChunks[i];
MissIndex++;
}
}
if (MissIndex > 0)
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
{
// 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 touch 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
m_LoadedChunks[x + z * VIEWDISTANCE] = NULL;
}
}
}
}
StreamChunksSmart(MissingChunks, MissIndex);
memcpy(m_LoadedChunks, NeededChunks, sizeof(NeededChunks));
}
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
}
// Sends chunks to the player from the player position outward
void cClientHandle::StreamChunksSmart(cChunk** a_Chunks, unsigned int a_NumChunks)
void cClientHandle::StreamChunk(int a_ChunkX, int a_ChunkZ)
{
int X = (int)floor(m_Player->GetPosX() / 16);
int Y = (int)floor(m_Player->GetPosY() / 128);
int Z = (int)floor(m_Player->GetPosZ() / 16);
cWorld * World = m_Player->GetWorld();
assert(World != NULL);
bool bAllDone = false;
while(!bAllDone)
cChunkPtr Chunk = World->GetChunk(a_ChunkX, 0, a_ChunkZ);
if (!Chunk->HasClient(this))
{
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)
{
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;
}
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));
}
}
@@ -393,18 +421,41 @@ void cClientHandle::StreamChunksSmart(cChunk** a_Chunks, unsigned int a_NumChunk
// This removes the client from all chunks. Used when switching worlds
// Removes the client from all chunks. Used when switching worlds or destroying the player
void cClientHandle::RemoveFromAllChunks()
{
for(int i = 0; i < VIEWDISTANCE*VIEWDISTANCE; i++)
cCSLock Lock(m_CSChunkLists);
cWorld * World = m_Player->GetWorld();
if (World != NULL)
{
if (m_LoadedChunks[i])
for (cChunkCoordsList::iterator itr = m_LoadedChunks.begin(); itr != m_LoadedChunks.end(); ++itr)
{
m_LoadedChunks[i]->RemoveClient(this);
m_LoadedChunks[i]->AsyncUnload(this);
m_LoadedChunks[i] = 0;
World->GetChunk(itr->m_ChunkX, 0, itr->m_ChunkZ)->RemoveClient(this);
}
}
m_LoadedChunks.clear();
m_ChunksToSend.clear();
}
void cClientHandle::ChunkJustSent(cChunk * a_ChunkCompleted)
{
cCSLock Lock(m_CSChunkLists);
for (cChunkCoordsList::iterator itr = m_ChunksToSend.begin(); itr != m_ChunksToSend.end(); ++itr)
{
if (((*itr).m_ChunkX == a_ChunkCompleted->GetPosX()) && ((*itr).m_ChunkZ == a_ChunkCompleted->GetPosZ()))
{
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 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 csAuthenticating:
{
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 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 csDownloadingWorld:
{
// 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)