commit
1a52e89177
|
@ -1,24 +1,23 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
function OnPlayerJoined(Player)
|
||||
--if( BannedPlayersIni:GetValueB("Banned", Player:GetName(), false) == true ) then
|
||||
-- LOGINFO( Player:GetName() .. " tried to join, but is banned!" )
|
||||
-- KickPlayer(Player:GetName(), cChatColor.Red .. "You are banned!" )
|
||||
-- return true
|
||||
--elseif( WhiteListIni:GetValueB("WhiteListSettings", "WhiteListOn", false ) == true ) then
|
||||
-- if( WhiteListIni:GetValueB("WhiteList", Player:GetName(), false ) == false ) then
|
||||
-- LOGINFO( Player:GetName() .. " tried to join, but is not whitelisted!" )
|
||||
-- KickPlayer(Player:GetName(), cChatColor.Red .. "You are not whitelisted!" )
|
||||
-- end
|
||||
--else
|
||||
ShowMOTDTo( Player )
|
||||
local Server = cRoot:Get():GetServer()
|
||||
Server:SendMessage(cChatColor.Yellow .. "[JOIN] " .. cChatColor.White .. Player:GetName() .. " has joined the game" )
|
||||
return false
|
||||
--end
|
||||
ShowMOTDTo(Player)
|
||||
Player:GetWorld():BroadcastChat(cChatColor.Yellow .. "[JOIN] " .. cChatColor.White .. Player:GetName() .. " has joined the game")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function OnDisconnect(Player, Reason)
|
||||
local Server = cRoot:Get():GetServer()
|
||||
Server:SendMessage(cChatColor.Yellow .. "[LEAVE] " .. cChatColor.White .. Player:GetName() .. " has left the game" )
|
||||
LOG("Player " .. Player:GetName() .. " has left the game.")
|
||||
Player:GetWorld():BroadcastChat(cChatColor.Yellow .. "[LEAVE] " .. cChatColor.White .. Player:GetName() .. " has left the game")
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -390,6 +390,14 @@
|
|||
RelativePath="..\source\Cuboid.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\DeadlockDetect.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\DeadlockDetect.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\source\Defines.h"
|
||||
>
|
||||
|
@ -2449,7 +2457,15 @@
|
|||
Name="Core"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\ban.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\back.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\ban-unban.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\clear.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
@ -2457,15 +2473,19 @@
|
|||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\coords.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\do.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\gamemode.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\functions.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\gotoworld.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\give.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\gm.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
@ -2476,32 +2496,44 @@
|
|||
RelativePath="..\MCServer\Plugins\Core\item.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\itemrepair.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\kick.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\kill.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\locate.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\main.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\me.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\motd.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\onblockdig.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\onbreakplaceblock.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\onblockplace.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\ondeath.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\oncraftingnorecipe.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\onkilled.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\onjoinleave.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
@ -2509,23 +2541,23 @@
|
|||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\onplayerjoin.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\plugins.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\playerlist.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\portal-worlds.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\pluginlist.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\rank-groups.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\regeneratechunk.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\regen.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\reload.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\save-reload-stop.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
@ -2533,11 +2565,11 @@
|
|||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\stop.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\teleport.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\teleport.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\tell.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
@ -2549,11 +2581,11 @@
|
|||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\unban.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\viewdistance.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\viewdistance.lua"
|
||||
RelativePath="..\MCServer\Plugins\Core\weather.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
|
@ -2564,6 +2596,10 @@
|
|||
RelativePath="..\MCServer\Plugins\Core\web_manageplugins.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\web_manageserver.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\web_permissions.lua"
|
||||
>
|
||||
|
@ -2580,6 +2616,10 @@
|
|||
RelativePath="..\MCServer\Plugins\Core\web_whitelist.lua"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\MCServer\Plugins\Core\worldlimiter.lua"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="ChatLog"
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
|
||||
:: If there was a Git conflict, resolve it by resetting to HEAD; we're regenerating the files from scratch anyway
|
||||
git checkout -- Bindings.cpp
|
||||
git checkout -- Bindings.h
|
||||
git checkout --ours Bindings.cpp
|
||||
git add -u Bindings.cpp
|
||||
git checkout --ours Bindings.h
|
||||
git add -u Bindings.h
|
||||
|
||||
|
||||
|
||||
|
|
61106
source/Bindings.cpp
61106
source/Bindings.cpp
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
** Lua binding: AllToLua
|
||||
** Generated automatically by tolua++-1.0.92 on 08/12/13 11:15:34.
|
||||
*/
|
||||
|
||||
/* Exported function */
|
||||
TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S);
|
||||
|
||||
/*
|
||||
** Lua binding: AllToLua
|
||||
** Generated automatically by tolua++-1.0.92 on 08/14/13 19:48:00.
|
||||
*/
|
||||
|
||||
/* Exported function */
|
||||
TOLUA_API int tolua_AllToLua_open (lua_State* tolua_S);
|
||||
|
||||
|
|
|
@ -478,16 +478,16 @@ enum
|
|||
E_META_TORCH_ZP = 4, // Torch attached to the ZP side of its block
|
||||
|
||||
// E_BLOCK_WOODEN_DOUBLE_STEP metas:
|
||||
E_BLOCK_WOODEN_DOUBLE_STEP_APPLE = 0,
|
||||
E_BLOCK_WOODEN_DOUBLE_STEP_CONIFER = 1,
|
||||
E_BLOCK_WOODEN_DOUBLE_STEP_BIRCH = 2,
|
||||
E_BLOCK_WOODEN_DOUBLE_STEP_JUNGLE = 3,
|
||||
E_META_WOODEN_DOUBLE_STEP_APPLE = 0,
|
||||
E_META_WOODEN_DOUBLE_STEP_CONIFER = 1,
|
||||
E_META_WOODEN_DOUBLE_STEP_BIRCH = 2,
|
||||
E_META_WOODEN_DOUBLE_STEP_JUNGLE = 3,
|
||||
|
||||
// E_BLOCK_WOODEN_STEP metas:
|
||||
E_BLOCK_WOODEN_STEP_APPLE = 0,
|
||||
E_BLOCK_WOODEN_STEP_CONIFER = 1,
|
||||
E_BLOCK_WOODEN_STEP_BIRCH = 2,
|
||||
E_BLOCK_WOODEN_STEP_JUNGLE = 3,
|
||||
E_META_WOODEN_STEP_APPLE = 0,
|
||||
E_META_WOODEN_STEP_CONIFER = 1,
|
||||
E_META_WOODEN_STEP_BIRCH = 2,
|
||||
E_META_WOODEN_STEP_JUNGLE = 3,
|
||||
|
||||
// E_BLOCK_WOOL metas:
|
||||
E_META_WOOL_WHITE = 0,
|
||||
|
|
|
@ -170,13 +170,21 @@ cClientHandle::~cClientHandle()
|
|||
|
||||
|
||||
|
||||
void cClientHandle::Destroy()
|
||||
void cClientHandle::Destroy(void)
|
||||
{
|
||||
// Setting m_bDestroyed was moved to the bottom of Destroy(),
|
||||
// otherwise the destructor may be called within another thread before the client is removed from chunks
|
||||
// http://forum.mc-server.org/showthread.php?tid=366
|
||||
{
|
||||
cCSLock Lock(m_CSDestroyingState);
|
||||
if (m_State >= csDestroying)
|
||||
{
|
||||
// Already called
|
||||
return;
|
||||
}
|
||||
m_State = csDestroying;
|
||||
}
|
||||
|
||||
// DEBUG:
|
||||
LOGD("%s: client %p, \"%s\"", __FUNCTION__, this, m_Username.c_str());
|
||||
|
||||
m_State = csDestroying;
|
||||
if ((m_Player != NULL) && (m_Player->GetWorld() != NULL))
|
||||
{
|
||||
RemoveFromAllChunks();
|
||||
|
@ -253,9 +261,8 @@ void cClientHandle::Authenticate(void)
|
|||
SendGameMode(m_Player->GetGameMode());
|
||||
|
||||
m_Player->Initialize(World);
|
||||
StreamChunks();
|
||||
m_State = csDownloadingWorld;
|
||||
|
||||
m_State = csAuthenticated;
|
||||
|
||||
// Broadcast this player's spawning to all other players in the same chunk
|
||||
m_Player->GetWorld()->BroadcastSpawnEntity(*m_Player, this);
|
||||
|
||||
|
@ -268,7 +275,7 @@ void cClientHandle::Authenticate(void)
|
|||
|
||||
void cClientHandle::StreamChunks(void)
|
||||
{
|
||||
if ((m_State < csAuthenticating) || (m_State >= csDestroying))
|
||||
if ((m_State < csAuthenticated) || (m_State >= csDestroying))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -421,11 +428,11 @@ void cClientHandle::HandlePing(void)
|
|||
// Somebody tries to retrieve information about the server
|
||||
AString Reply;
|
||||
Printf(Reply, "%s%s%i%s%i",
|
||||
cRoot::Get()->GetDefaultWorld()->GetDescription().c_str(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetDefaultWorld()->GetNumPlayers(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetDefaultWorld()->GetMaxPlayers()
|
||||
cRoot::Get()->GetServer()->GetDescription().c_str(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetServer()->GetNumPlayers(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetServer()->GetMaxPlayers()
|
||||
);
|
||||
Kick(Reply.c_str());
|
||||
}
|
||||
|
@ -914,11 +921,24 @@ void cClientHandle::HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, c
|
|||
|
||||
void cClientHandle::HandleChat(const AString & a_Message)
|
||||
{
|
||||
// We need to process messages in the Tick thread, to avoid deadlocks resulting from player-commands being processed
|
||||
// in the SocketThread and waiting for acquiring the ChunkMap CS with Plugin CS locked
|
||||
// We no longer need to postpone message processing, because the messages already arrive in the Tick thread
|
||||
|
||||
cCSLock Lock(m_CSMessages);
|
||||
m_PendingMessages.push_back(a_Message);
|
||||
// If a command, perform it:
|
||||
AString Message(a_Message);
|
||||
if (cRoot::Get()->GetServer()->Command(*this, Message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a command, broadcast as a simple message:
|
||||
AString Msg;
|
||||
Printf(Msg, "<%s%s%s> %s",
|
||||
m_Player->GetColor().c_str(),
|
||||
m_Player->GetName().c_str(),
|
||||
cChatColor::White.c_str(),
|
||||
Message.c_str()
|
||||
);
|
||||
m_Player->GetWorld()->BroadcastChat(Msg);
|
||||
}
|
||||
|
||||
|
||||
|
@ -1176,7 +1196,7 @@ bool cClientHandle::HandleHandshake(const AString & a_Username)
|
|||
{
|
||||
if (!cRoot::Get()->GetPluginManager()->CallHookHandshake(this, a_Username))
|
||||
{
|
||||
if (cRoot::Get()->GetDefaultWorld()->GetNumPlayers() >= cRoot::Get()->GetDefaultWorld()->GetMaxPlayers())
|
||||
if (cRoot::Get()->GetServer()->GetNumPlayers() >= cRoot::Get()->GetServer()->GetMaxPlayers())
|
||||
{
|
||||
Kick("The server is currently full :(-- Try again later");
|
||||
return false;
|
||||
|
@ -1191,7 +1211,7 @@ bool cClientHandle::HandleHandshake(const AString & a_Username)
|
|||
|
||||
void cClientHandle::HandleEntityAction(int a_EntityID, char a_ActionID)
|
||||
{
|
||||
if( a_EntityID != m_Player->GetUniqueID() )
|
||||
if (a_EntityID != m_Player->GetUniqueID())
|
||||
{
|
||||
// We should only receive entity actions from the entity that is performing the action
|
||||
return;
|
||||
|
@ -1307,6 +1327,42 @@ void cClientHandle::SendData(const char * a_Data, int a_Size)
|
|||
|
||||
|
||||
|
||||
void cClientHandle::MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket)
|
||||
{
|
||||
ASSERT(m_Player != NULL);
|
||||
|
||||
if (a_SendRespawnPacket)
|
||||
{
|
||||
SendRespawn();
|
||||
}
|
||||
|
||||
cWorld * World = m_Player->GetWorld();
|
||||
|
||||
// Remove all associated chunks:
|
||||
cChunkCoordsList Chunks;
|
||||
{
|
||||
cCSLock Lock(m_CSChunkLists);
|
||||
std::swap(Chunks, m_LoadedChunks);
|
||||
m_ChunksToSend.clear();
|
||||
}
|
||||
for (cChunkCoordsList::iterator itr = Chunks.begin(), end = Chunks.end(); itr != end; ++itr)
|
||||
{
|
||||
World->RemoveChunkClient(itr->m_ChunkX, itr->m_ChunkZ, this);
|
||||
m_Protocol->SendUnloadChunk(itr->m_ChunkX, itr->m_ChunkZ);
|
||||
} // for itr - Chunks[]
|
||||
|
||||
// Do NOT stream new chunks, the new world runs its own tick thread and may deadlock
|
||||
// Instead, the chunks will be streamed when the client is moved to the new world's Tick list,
|
||||
// by setting state to csAuthenticated
|
||||
m_State = csAuthenticated;
|
||||
m_LastStreamedChunkX = 0x7fffffff;
|
||||
m_LastStreamedChunkZ = 0x7fffffff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cClientHandle::CheckBlockInteractionsRate(void)
|
||||
{
|
||||
ASSERT(m_Player != NULL);
|
||||
|
@ -1342,6 +1398,20 @@ bool cClientHandle::CheckBlockInteractionsRate(void)
|
|||
|
||||
void cClientHandle::Tick(float a_Dt)
|
||||
{
|
||||
// Process received network data:
|
||||
AString IncomingData;
|
||||
{
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
std::swap(IncomingData, m_IncomingData);
|
||||
}
|
||||
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
|
||||
|
||||
if (m_State == csAuthenticated)
|
||||
{
|
||||
StreamChunks();
|
||||
m_State = csDownloadingWorld;
|
||||
}
|
||||
|
||||
m_TimeSinceLastPacket += a_Dt;
|
||||
if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
|
||||
{
|
||||
|
@ -1355,12 +1425,14 @@ void cClientHandle::Tick(float a_Dt)
|
|||
m_ShouldCheckDownloaded = false;
|
||||
}
|
||||
|
||||
if (m_Player == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a ping packet:
|
||||
cTimer t1;
|
||||
if (
|
||||
(m_Player != NULL) && // Is logged in?
|
||||
(m_LastPingTime + cClientHandle::PING_TIME_MS <= t1.GetNowTime())
|
||||
)
|
||||
if ((m_LastPingTime + cClientHandle::PING_TIME_MS <= t1.GetNowTime()))
|
||||
{
|
||||
m_PingID++;
|
||||
m_PingStartTime = t1.GetNowTime();
|
||||
|
@ -1369,7 +1441,7 @@ void cClientHandle::Tick(float a_Dt)
|
|||
}
|
||||
|
||||
// Handle block break animation:
|
||||
if ((m_Player != NULL) && (m_BlockDigAnimStage > -1))
|
||||
if (m_BlockDigAnimStage > -1)
|
||||
{
|
||||
int lastAnimVal = m_BlockDigAnimStage;
|
||||
m_BlockDigAnimStage += (int)(m_BlockDigAnimSpeed * a_Dt);
|
||||
|
@ -1387,9 +1459,6 @@ void cClientHandle::Tick(float a_Dt)
|
|||
m_CurrentExplosionTick = (m_CurrentExplosionTick + 1) % ARRAYCOUNT(m_NumExplosionsPerTick);
|
||||
m_RunningSumExplosions -= m_NumExplosionsPerTick[m_CurrentExplosionTick];
|
||||
m_NumExplosionsPerTick[m_CurrentExplosionTick] = 0;
|
||||
|
||||
// Process the queued messages:
|
||||
ProcessPendingMessages();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1955,7 +2024,7 @@ void cClientHandle::SendConfirmPosition(void)
|
|||
if (!cRoot::Get()->GetPluginManager()->CallHookPlayerJoined(*m_Player))
|
||||
{
|
||||
// Broadcast that this player has joined the game! Yay~
|
||||
cRoot::Get()->GetServer()->BroadcastChat(m_Username + " joined the game!", this);
|
||||
m_Player->GetWorld()->BroadcastChat(m_Username + " joined the game!", this);
|
||||
}
|
||||
|
||||
SendPlayerMoveLook();
|
||||
|
@ -2037,46 +2106,6 @@ void cClientHandle::AddWantedChunk(int a_ChunkX, int a_ChunkZ)
|
|||
|
||||
|
||||
|
||||
void cClientHandle::ProcessPendingMessages(void)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
AString Message;
|
||||
|
||||
// Extract one message from the PendingMessages buffer:
|
||||
{
|
||||
cCSLock Lock(m_CSMessages);
|
||||
if (m_PendingMessages.empty())
|
||||
{
|
||||
// No more messages in the buffer, bail out
|
||||
return;
|
||||
}
|
||||
Message = m_PendingMessages.front();
|
||||
m_PendingMessages.pop_front();
|
||||
} // Lock(m_CSMessages)
|
||||
|
||||
// If a command, perform it:
|
||||
if (cRoot::Get()->GetServer()->Command(*this, Message))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not a command, broadcast as a simple message:
|
||||
AString Msg;
|
||||
Printf(Msg, "<%s%s%s> %s",
|
||||
m_Player->GetColor().c_str(),
|
||||
m_Player->GetName().c_str(),
|
||||
cChatColor::White.c_str(),
|
||||
Message.c_str()
|
||||
);
|
||||
m_Player->GetWorld()->BroadcastChat(Msg);
|
||||
} // while (true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::PacketBufferFull(void)
|
||||
{
|
||||
// Too much data in the incoming queue, the server is probably too busy, kick the client:
|
||||
|
@ -2116,30 +2145,10 @@ void cClientHandle::PacketError(unsigned char a_PacketType)
|
|||
|
||||
void cClientHandle::DataReceived(const char * a_Data, int a_Size)
|
||||
{
|
||||
// Data is received from the client, hand it off to the protocol:
|
||||
if ((m_Player != NULL) && (m_Player->GetWorld() != NULL))
|
||||
{
|
||||
/*
|
||||
_X: Lock the world, so that plugins reacting to protocol events have already the chunkmap locked.
|
||||
There was a possibility of a deadlock between SocketThreads and TickThreads, resulting from each
|
||||
holding one CS an requesting the other one (ChunkMap CS vs Plugin CS) (FS #375). To break this, it's
|
||||
sufficient to break any of the four Coffman conditions for a deadlock. We'll solve this by requiring
|
||||
the ChunkMap CS for all SocketThreads operations before they lock the PluginCS - thus creating a kind
|
||||
of a lock hierarchy. However, this incurs a performance penalty, we're de facto locking the chunkmap
|
||||
for each incoming packet. A better, but more involved solutin would be to lock the chunkmap only when
|
||||
the incoming packet really has a plugin CS lock request.
|
||||
Also, it is still possible for a packet to slip through - when a player still doesn't have their world
|
||||
assigned and several packets arrive at once.
|
||||
*/
|
||||
cWorld::cLock(*m_Player->GetWorld());
|
||||
|
||||
m_Protocol->DataReceived(a_Data, a_Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Protocol->DataReceived(a_Data, a_Size);
|
||||
}
|
||||
// Data is received from the client, store it in the buffer to be processed by the Tick thread:
|
||||
m_TimeSinceLastPacket = 0;
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class cRedstone;
|
|||
class cWindow;
|
||||
class cFallingBlock;
|
||||
class cItemHandler;
|
||||
class cWorld;
|
||||
|
||||
|
||||
|
||||
|
@ -194,6 +195,9 @@ public:
|
|||
|
||||
void SendData(const char * a_Data, int a_Size);
|
||||
|
||||
/// Called when the player moves into a different world; queues sreaming the new chunks
|
||||
void MoveToWorld(cWorld & a_World, bool a_SendRespawnPacket);
|
||||
|
||||
private:
|
||||
|
||||
int m_ViewDistance; // Number of chunks the player can see in each direction; 4 is the minimum ( http://wiki.vg/Protocol_FAQ#.E2.80.A6all_connecting_clients_spasm_and_jerk_uncontrollably.21 )
|
||||
|
@ -212,11 +216,12 @@ private:
|
|||
|
||||
cProtocol * m_Protocol;
|
||||
|
||||
cCriticalSection m_CSIncomingData;
|
||||
AString m_IncomingData;
|
||||
|
||||
cCriticalSection m_CSOutgoingData;
|
||||
cByteBuffer m_OutgoingData;
|
||||
AString m_OutgoingDataOverflow; //< For data that didn't fit into the m_OutgoingData ringbuffer temporarily
|
||||
|
||||
cCriticalSection m_CriticalSection;
|
||||
AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily
|
||||
|
||||
Vector3d m_ConfirmPosition;
|
||||
|
||||
|
@ -252,18 +257,22 @@ private:
|
|||
|
||||
enum eState
|
||||
{
|
||||
csConnected, // The client has just connected, waiting for their handshake / login
|
||||
csAuthenticating, // The client has logged in, waiting for external authentication
|
||||
csDownloadingWorld, // The client is waiting for chunks, we're waiting for the loader to provide and send them
|
||||
csConfirmingPos, // The client has been sent the position packet, waiting for them to repeat the position back
|
||||
csPlaying, // Normal gameplay
|
||||
csDestroying, // The client is being destroyed, don't queue any more packets / don't add to chunks
|
||||
csDestroyed, // The client has been destroyed, the destructor is to be called from the owner thread
|
||||
csConnected, ///< The client has just connected, waiting for their handshake / login
|
||||
csAuthenticating, ///< The client has logged in, waiting for external authentication
|
||||
csAuthenticated, ///< The client has been authenticated, will start streaming chunks in the next tick
|
||||
csDownloadingWorld, ///< The client is waiting for chunks, we're waiting for the loader to provide and send them
|
||||
csConfirmingPos, ///< The client has been sent the position packet, waiting for them to repeat the position back
|
||||
csPlaying, ///< Normal gameplay
|
||||
csDestroying, ///< The client is being destroyed, don't queue any more packets / don't add to chunks
|
||||
csDestroyed, ///< The client has been destroyed, the destructor is to be called from the owner thread
|
||||
|
||||
// TODO: Add Kicking here as well
|
||||
} ;
|
||||
|
||||
eState m_State;
|
||||
|
||||
/// m_State needs to be locked in the Destroy() function so that the destruction code doesn't run twice on two different threads
|
||||
cCriticalSection m_CSDestroyingState;
|
||||
|
||||
bool m_bKeepThreadGoing;
|
||||
|
||||
|
@ -279,12 +288,6 @@ private:
|
|||
/// Running sum of m_NumExplosionsPerTick[]
|
||||
int m_RunningSumExplosions;
|
||||
|
||||
/// Lock for the m_PendingMessages buffer
|
||||
cCriticalSection m_CSMessages;
|
||||
|
||||
/// Buffer for received messages to be processed in the Tick thread
|
||||
AStringList m_PendingMessages;
|
||||
|
||||
static int s_ClientCount;
|
||||
int m_UniqueID;
|
||||
|
||||
|
@ -311,9 +314,6 @@ private:
|
|||
/// Handles the block placing packet when it is a real block placement (not block-using, item-using or eating)
|
||||
void HandlePlaceBlock(int a_BlockX, int a_BlockY, int a_BlockZ, char a_BlockFace, int a_CursorX, int a_CursorY, int a_CursorZ, cItemHandler & a_ItemHandler);
|
||||
|
||||
/// Processes the messages in m_PendingMessages; called from the Tick thread
|
||||
void ProcessPendingMessages(void);
|
||||
|
||||
// cSocketThreads::cCallback overrides:
|
||||
virtual void DataReceived (const char * a_Data, int a_Size) override; // Data is received from the client
|
||||
virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
|
||||
// DeadlockDetect.cpp
|
||||
|
||||
// Declares the cDeadlockDetect class that tries to detect deadlocks and aborts the server when it detects one
|
||||
|
||||
#include "Globals.h"
|
||||
#include "DeadlockDetect.h"
|
||||
#include "Root.h"
|
||||
#include "World.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Number of milliseconds per cycle
|
||||
const int CYCLE_MILLISECONDS = 500;
|
||||
|
||||
/// When the number of cycles for the same world age hits this value, it is considered a deadlock
|
||||
const int NUM_CYCLES_LIMIT = 40; // 40 = twenty seconds
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cDeadlockDetect::cDeadlockDetect(void) :
|
||||
super("DeadlockDetect")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cDeadlockDetect::Start(void)
|
||||
{
|
||||
// Read the initial world data:
|
||||
class cFillIn :
|
||||
public cWorldListCallback
|
||||
{
|
||||
public:
|
||||
cFillIn(cDeadlockDetect * a_Detect) :
|
||||
m_Detect(a_Detect)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool Item(cWorld * a_World) override
|
||||
{
|
||||
m_Detect->SetWorldAge(a_World->GetName(), a_World->GetWorldAge());
|
||||
return false;
|
||||
}
|
||||
|
||||
protected:
|
||||
cDeadlockDetect * m_Detect;
|
||||
} FillIn(this);
|
||||
cRoot::Get()->ForEachWorld(FillIn);
|
||||
return super::Start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cDeadlockDetect::Stop(void)
|
||||
{
|
||||
m_EvtTerminate.Set();
|
||||
super::Stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cDeadlockDetect::Execute(void)
|
||||
{
|
||||
// Loop until the event is signalled
|
||||
while (m_EvtTerminate.Wait(CYCLE_MILLISECONDS) == cEvent::wrTimeout)
|
||||
{
|
||||
// Check the world ages:
|
||||
class cChecker :
|
||||
public cWorldListCallback
|
||||
{
|
||||
public:
|
||||
cChecker(cDeadlockDetect * a_Detect) :
|
||||
m_Detect(a_Detect)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
cDeadlockDetect * m_Detect;
|
||||
|
||||
virtual bool Item(cWorld * a_World) override
|
||||
{
|
||||
m_Detect->CheckWorldAge(a_World->GetName(), a_World->GetWorldAge());
|
||||
return false;
|
||||
}
|
||||
} Checker(this);
|
||||
cRoot::Get()->ForEachWorld(Checker);
|
||||
} // while (should run)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cDeadlockDetect::SetWorldAge(const AString & a_WorldName, Int64 a_Age)
|
||||
{
|
||||
m_WorldAges[a_WorldName].m_Age = a_Age;
|
||||
m_WorldAges[a_WorldName].m_NumCyclesSame = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cDeadlockDetect::CheckWorldAge(const AString & a_WorldName, Int64 a_Age)
|
||||
{
|
||||
WorldAges::iterator itr = m_WorldAges.find(a_WorldName);
|
||||
if (itr == m_WorldAges.end())
|
||||
{
|
||||
ASSERT(!"Unknown world in cDeadlockDetect");
|
||||
return;
|
||||
}
|
||||
if (itr->second.m_Age == a_Age)
|
||||
{
|
||||
itr->second.m_NumCyclesSame += 1;
|
||||
if (itr->second.m_NumCyclesSame > NUM_CYCLES_LIMIT)
|
||||
{
|
||||
DeadlockDetected();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
itr->second.m_Age = a_Age;
|
||||
itr->second.m_NumCyclesSame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cDeadlockDetect::DeadlockDetected(void)
|
||||
{
|
||||
ASSERT(!"Deadlock detected");
|
||||
|
||||
// TODO: Make a crashdump / coredump
|
||||
|
||||
// Crash the server intentionally:
|
||||
*((int *)0) = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
|
||||
// DeadlockDetect.h
|
||||
|
||||
// Declares the cDeadlockDetect class that tries to detect deadlocks and aborts the server when it detects one
|
||||
|
||||
/*
|
||||
This class simply monitors each world's m_WorldAge, which is expected to grow on each tick.
|
||||
If the world age doesn't grow for several seconds, it's either because the server is super-overloaded,
|
||||
or because the world tick thread hangs in a deadlock. We presume the latter and therefore kill the server.
|
||||
Once we learn to write crashdumps programmatically, we should do so just before killing, to enable debugging.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OSSupport/IsThread.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cDeadlockDetect :
|
||||
public cIsThread
|
||||
{
|
||||
typedef cIsThread super;
|
||||
|
||||
public:
|
||||
cDeadlockDetect(void);
|
||||
|
||||
/// Starts the detection. Hides cIsThread's Start, because we need some initialization
|
||||
bool Start(void);
|
||||
|
||||
/// Stops the detection. Hides cIsThread's Stop, because we need to signal m_EvtTerminate
|
||||
void Stop(void);
|
||||
|
||||
protected:
|
||||
struct sWorldAge
|
||||
{
|
||||
/// Last m_WorldAge that has been detected in this world
|
||||
Int64 m_Age;
|
||||
|
||||
/// Number of cycles for which the age has been the same
|
||||
int m_NumCyclesSame;
|
||||
} ;
|
||||
|
||||
/// Maps world name -> sWorldAge
|
||||
typedef std::map<AString, sWorldAge> WorldAges;
|
||||
|
||||
WorldAges m_WorldAges;
|
||||
|
||||
cEvent m_EvtTerminate;
|
||||
|
||||
|
||||
// cIsThread overrides:
|
||||
virtual void Execute(void) override;
|
||||
|
||||
/// Sets the initial world age
|
||||
void SetWorldAge(const AString & a_WorldName, Int64 a_Age);
|
||||
|
||||
/// Checks if the world's age has changed, updates the world's stats; calls DeadlockDetected() if deadlock detected
|
||||
void CheckWorldAge(const AString & a_WorldName, Int64 a_Age);
|
||||
|
||||
/// Called when a deadlock is detected. Aborts the server.
|
||||
void DeadlockDetected(void);
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
cEvent::cEvent(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
m_Event = CreateEvent( 0, FALSE, FALSE, 0 );
|
||||
m_Event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (m_Event == NULL)
|
||||
{
|
||||
LOGERROR("cEvent: cannot create event, GLE = %d. Aborting server.", GetLastError());
|
||||
|
@ -78,19 +78,19 @@ cEvent::~cEvent()
|
|||
|
||||
void cEvent::Wait(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD res = WaitForSingleObject(m_Event, INFINITE);
|
||||
if (res != WAIT_OBJECT_0)
|
||||
{
|
||||
LOGWARN("cEvent: waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError());
|
||||
}
|
||||
#else
|
||||
int res = sem_wait(m_Event);
|
||||
if (res != 0 )
|
||||
{
|
||||
LOGWARN("cEvent: waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
DWORD res = WaitForSingleObject(m_Event, INFINITE);
|
||||
if (res != WAIT_OBJECT_0)
|
||||
{
|
||||
LOGWARN("cEvent: waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError());
|
||||
}
|
||||
#else
|
||||
int res = sem_wait(m_Event);
|
||||
if (res != 0 )
|
||||
{
|
||||
LOGWARN("cEvent: waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -99,18 +99,70 @@ void cEvent::Wait(void)
|
|||
|
||||
void cEvent::Set(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (!SetEvent(m_Event))
|
||||
{
|
||||
LOGWARN("cEvent: Could not set cEvent: GLE = %d", GetLastError());
|
||||
}
|
||||
#else
|
||||
int res = sem_post(m_Event);
|
||||
if (res != 0)
|
||||
{
|
||||
LOGWARN("cEvent: Could not set cEvent: %i, errno = %d", res, errno);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
if (!SetEvent(m_Event))
|
||||
{
|
||||
LOGWARN("cEvent: Could not set cEvent: GLE = %d", GetLastError());
|
||||
}
|
||||
#else
|
||||
int res = sem_post(m_Event);
|
||||
if (res != 0)
|
||||
{
|
||||
LOGWARN("cEvent: Could not set cEvent: %i, errno = %d", res, errno);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cEvent::eWaitResult cEvent::Wait(int a_TimeoutMilliSec)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD res = WaitForSingleObject(m_Event, (DWORD)a_TimeoutMilliSec);
|
||||
switch (res)
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
{
|
||||
// The semaphore was signalled
|
||||
return wrSignalled;
|
||||
}
|
||||
case WAIT_TIMEOUT:
|
||||
{
|
||||
// The timeout was hit
|
||||
return wrTimeout;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOGWARNING("cEvent: timed-waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError());
|
||||
return wrError;
|
||||
}
|
||||
}
|
||||
#else
|
||||
timespec timeout;
|
||||
timeout.tv_sec = a_TimeoutMilliSec / 1000;
|
||||
timeout.tv_nsec = (a_TimeoutMilliSec % 1000) * 1000000;
|
||||
int res = sem_timedwait(m_Event, &timeout);
|
||||
switch (res)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
// The semaphore was signalled
|
||||
return wrSignalled;
|
||||
}
|
||||
case ETIMEDOUT:
|
||||
{
|
||||
// The timeout was hit
|
||||
return wrTimeout;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOGWARNING("cEvent: timed-waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno);
|
||||
return wrError;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,12 +19,22 @@
|
|||
class cEvent
|
||||
{
|
||||
public:
|
||||
enum eWaitResult
|
||||
{
|
||||
wrSignalled,
|
||||
wrTimeout,
|
||||
wrError,
|
||||
} ;
|
||||
|
||||
cEvent(void);
|
||||
~cEvent();
|
||||
|
||||
void Wait(void);
|
||||
void Set (void);
|
||||
|
||||
/// Waits for the semaphore with a timeout
|
||||
eWaitResult Wait(int a_TimeoutMilliSec);
|
||||
|
||||
private:
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
|
@ -116,36 +116,38 @@ bool cIsThread::Start(void)
|
|||
|
||||
|
||||
|
||||
void cIsThread::Stop(void)
|
||||
{
|
||||
if (m_Handle == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_ShouldTerminate = true;
|
||||
Wait();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cIsThread::Wait(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_Handle == NULL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
LOGD("Waiting for thread %s to finish", m_ThreadName.c_str());
|
||||
|
||||
if (m_Handle == NULL)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Cannot log, logger may already be stopped:
|
||||
// LOG("Waiting for thread \"%s\" to terminate.", m_ThreadName.c_str());
|
||||
#ifdef _WIN32
|
||||
int res = WaitForSingleObject(m_Handle, INFINITE);
|
||||
m_Handle = NULL;
|
||||
// Cannot log, logger may already be stopped:
|
||||
// LOG("Thread \"%s\" %s terminated, GLE = %d", m_ThreadName.c_str(), (res == WAIT_OBJECT_0) ? "" : "not", GetLastError());
|
||||
LOGD("Thread %s finished", m_ThreadName.c_str());
|
||||
return (res == WAIT_OBJECT_0);
|
||||
|
||||
#else // _WIN32
|
||||
|
||||
if (!m_HasStarted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Cannot log, logger may already be stopped:
|
||||
// LOG("Waiting for thread \"%s\" to terminate.", m_ThreadName.c_str());
|
||||
int res = pthread_join(m_Handle, NULL);
|
||||
m_HasStarted = false;
|
||||
// Cannot log, logger may already be stopped:
|
||||
// LOG("Thread \"%s\" %s terminated, errno = %d", m_ThreadName.c_str(), (res == 0) ? "" : "not", errno);
|
||||
m_Handle = NULL;
|
||||
LOGD("Thread %s finished", m_ThreadName.c_str());
|
||||
return (res == 0);
|
||||
|
||||
#endif // else _WIN32
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,9 @@ public:
|
|||
/// Starts the thread; returns without waiting for the actual start
|
||||
bool Start(void);
|
||||
|
||||
/// Signals the thread to terminate and waits until it's finished
|
||||
void Stop(void);
|
||||
|
||||
/// Waits for the thread to finish. Doesn't signalize the ShouldTerminate flag
|
||||
bool Wait(void);
|
||||
|
||||
|
@ -54,7 +57,9 @@ private:
|
|||
|
||||
static DWORD_PTR __stdcall thrExecute(LPVOID a_Param)
|
||||
{
|
||||
HWND IdentificationWnd = CreateWindow("STATIC", ((cIsThread *)a_Param)->m_ThreadName.c_str(), 0, 0, 0, 0, WS_OVERLAPPED, NULL, NULL, NULL, NULL);
|
||||
((cIsThread *)a_Param)->Execute();
|
||||
DestroyWindow(IdentificationWnd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -70,7 +75,6 @@ private:
|
|||
}
|
||||
|
||||
#endif // else _WIN32
|
||||
|
||||
} ;
|
||||
|
||||
|
||||
|
|
|
@ -100,6 +100,8 @@ cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName)
|
|||
m_LastJumpHeight = (float)(GetPosY());
|
||||
m_LastGroundHeight = (float)(GetPosY());
|
||||
m_Stance = GetPosY() + 1.62;
|
||||
|
||||
cRoot::Get()->GetServer()->PlayerCreated(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -110,6 +112,9 @@ cPlayer::~cPlayer(void)
|
|||
{
|
||||
LOGD("Deleting cPlayer \"%s\" at %p, ID %d", m_PlayerName.c_str(), this, GetUniqueID());
|
||||
|
||||
// Notify the server that the player is being destroyed
|
||||
cRoot::Get()->GetServer()->PlayerDestroying(this);
|
||||
|
||||
SaveToDisk();
|
||||
|
||||
m_World->RemovePlayer( this );
|
||||
|
@ -127,8 +132,16 @@ cPlayer::~cPlayer(void)
|
|||
|
||||
bool cPlayer::Initialize(cWorld * a_World)
|
||||
{
|
||||
ASSERT(a_World != NULL);
|
||||
|
||||
if (super::Initialize(a_World))
|
||||
{
|
||||
// Remove the client handle from the server, it will be ticked from this object from now on
|
||||
if (m_ClientHandle != NULL)
|
||||
{
|
||||
cRoot::Get()->GetServer()->ClientMovedToWorld(m_ClientHandle);
|
||||
}
|
||||
|
||||
GetWorld()->AddPlayer(this);
|
||||
return true;
|
||||
}
|
||||
|
@ -142,6 +155,7 @@ bool cPlayer::Initialize(cWorld * a_World)
|
|||
void cPlayer::Destroyed()
|
||||
{
|
||||
CloseWindow(false);
|
||||
|
||||
m_ClientHandle = NULL;
|
||||
}
|
||||
|
||||
|
@ -151,22 +165,17 @@ void cPlayer::Destroyed()
|
|||
|
||||
void cPlayer::SpawnOn(cClientHandle & a_Client)
|
||||
{
|
||||
/*
|
||||
LOGD("cPlayer::SpawnOn(%s) for \"%s\" at pos {%.2f, %.2f, %.2f}",
|
||||
a_Client.GetUsername().c_str(), m_PlayerName.c_str(), m_Pos.x, m_Pos.y, m_Pos.z
|
||||
);
|
||||
*/
|
||||
|
||||
if (m_bVisible && (m_ClientHandle != (&a_Client)))
|
||||
if (!m_bVisible || (m_ClientHandle == (&a_Client)))
|
||||
{
|
||||
a_Client.SendPlayerSpawn(*this);
|
||||
a_Client.SendEntityHeadLook(*this);
|
||||
a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem() );
|
||||
a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots() );
|
||||
a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings() );
|
||||
a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate() );
|
||||
a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet() );
|
||||
return;
|
||||
}
|
||||
a_Client.SendPlayerSpawn(*this);
|
||||
a_Client.SendEntityHeadLook(*this);
|
||||
a_Client.SendEntityEquipment(*this, 0, m_Inventory.GetEquippedItem() );
|
||||
a_Client.SendEntityEquipment(*this, 1, m_Inventory.GetEquippedBoots() );
|
||||
a_Client.SendEntityEquipment(*this, 2, m_Inventory.GetEquippedLeggings() );
|
||||
a_Client.SendEntityEquipment(*this, 3, m_Inventory.GetEquippedChestplate() );
|
||||
a_Client.SendEntityEquipment(*this, 4, m_Inventory.GetEquippedHelmet() );
|
||||
}
|
||||
|
||||
|
||||
|
@ -175,18 +184,28 @@ void cPlayer::SpawnOn(cClientHandle & a_Client)
|
|||
|
||||
void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
|
||||
{
|
||||
if (!m_ClientHandle->IsPlaying())
|
||||
if (m_ClientHandle != NULL)
|
||||
{
|
||||
// We're not yet in the game, ignore everything
|
||||
return;
|
||||
if (m_ClientHandle->IsDestroyed())
|
||||
{
|
||||
// This should not happen, because destroying a client will remove it from the world, but just in case
|
||||
m_ClientHandle = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_ClientHandle->IsPlaying())
|
||||
{
|
||||
// We're not yet in the game, ignore everything
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super::Tick(a_Dt, a_Chunk);
|
||||
|
||||
// set player swimming state
|
||||
SetSwimState( a_Chunk);
|
||||
// Set player swimming state
|
||||
SetSwimState(a_Chunk);
|
||||
|
||||
// handle air drowning stuff
|
||||
// Handle air drowning stuff
|
||||
HandleAir();
|
||||
|
||||
if (m_bDirtyPosition)
|
||||
|
@ -1106,20 +1125,15 @@ bool cPlayer::MoveToWorld(const char * a_WorldName)
|
|||
m_ClientHandle->RemoveFromAllChunks();
|
||||
m_World->RemoveEntity(this);
|
||||
|
||||
// If the dimension is different, we can send the respawn packet
|
||||
// http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02
|
||||
m_ClientHandle->MoveToWorld(*World, (OldDimension != World->GetDimension()));
|
||||
|
||||
// Add player to all the necessary parts of the new world
|
||||
SetWorld(World);
|
||||
World->AddEntity(this);
|
||||
World->AddPlayer(this);
|
||||
|
||||
// If the dimension is different, we can send the respawn packet
|
||||
// http://wiki.vg/Protocol#0x09 says "don't send if dimension is the same" as of 2013_07_02
|
||||
if (OldDimension != World->GetDimension())
|
||||
{
|
||||
m_ClientHandle->SendRespawn();
|
||||
}
|
||||
|
||||
// Stream the new chunks:
|
||||
m_ClientHandle->StreamChunks();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
|
||||
virtual ~cPlayer();
|
||||
|
||||
virtual bool Initialize(cWorld * a_World); // tolua_export
|
||||
virtual bool Initialize(cWorld * a_World) override;
|
||||
|
||||
virtual void SpawnOn(cClientHandle & a_Client) override;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "Protocol16x.h"
|
||||
#include "../ClientHandle.h"
|
||||
#include "../Root.h"
|
||||
#include "../Server.h"
|
||||
#include "../World.h"
|
||||
#include "../ChatColor.h"
|
||||
|
||||
|
@ -729,11 +730,11 @@ void cProtocolRecognizer::HandleServerPing(void)
|
|||
{
|
||||
// http://wiki.vg/wiki/index.php?title=Protocol&oldid=3099#Server_List_Ping_.280xFE.29
|
||||
Printf(Reply, "%s%s%i%s%i",
|
||||
cRoot::Get()->GetDefaultWorld()->GetDescription().c_str(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetDefaultWorld()->GetNumPlayers(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetDefaultWorld()->GetMaxPlayers()
|
||||
cRoot::Get()->GetServer()->GetDescription().c_str(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetServer()->GetNumPlayers(),
|
||||
cChatColor::Delimiter.c_str(),
|
||||
cRoot::Get()->GetServer()->GetMaxPlayers()
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
@ -759,9 +760,9 @@ void cProtocolRecognizer::HandleServerPing(void)
|
|||
|
||||
// http://wiki.vg/wiki/index.php?title=Server_List_Ping&oldid=3100
|
||||
AString NumPlayers;
|
||||
Printf(NumPlayers, "%d", cRoot::Get()->GetDefaultWorld()->GetNumPlayers());
|
||||
Printf(NumPlayers, "%d", cRoot::Get()->GetServer()->GetNumPlayers());
|
||||
AString MaxPlayers;
|
||||
Printf(MaxPlayers, "%d", cRoot::Get()->GetDefaultWorld()->GetMaxPlayers());
|
||||
Printf(MaxPlayers, "%d", cRoot::Get()->GetServer()->GetMaxPlayers());
|
||||
|
||||
AString ProtocolVersionNum;
|
||||
Printf(ProtocolVersionNum, "%d", cRoot::Get()->m_PrimaryServerVersion);
|
||||
|
@ -775,7 +776,7 @@ void cProtocolRecognizer::HandleServerPing(void)
|
|||
Reply.push_back(0);
|
||||
Reply.append(ProtocolVersionTxt);
|
||||
Reply.push_back(0);
|
||||
Reply.append(cRoot::Get()->GetDefaultWorld()->GetDescription());
|
||||
Reply.append(cRoot::Get()->GetServer()->GetDescription());
|
||||
Reply.push_back(0);
|
||||
Reply.append(NumPlayers);
|
||||
Reply.push_back(0);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "Chunk.h"
|
||||
#include "Protocol/ProtocolRecognizer.h" // for protocol version constants
|
||||
#include "CommandOutput.h"
|
||||
#include "DeadlockDetect.h"
|
||||
|
||||
#include "../iniFile/iniFile.h"
|
||||
|
||||
|
@ -90,6 +91,7 @@ void cRoot::InputThread(void * a_Params)
|
|||
|
||||
void cRoot::Start(void)
|
||||
{
|
||||
cDeadlockDetect dd;
|
||||
delete m_Log;
|
||||
m_Log = new cMCLogger();
|
||||
|
||||
|
@ -162,6 +164,9 @@ void cRoot::Start(void)
|
|||
LOG("Starting worlds...");
|
||||
StartWorlds();
|
||||
|
||||
LOG("Starting deadlock detector...");
|
||||
dd.Start();
|
||||
|
||||
LOG("Starting server...");
|
||||
m_Server->Start();
|
||||
|
||||
|
@ -183,17 +188,21 @@ void cRoot::Start(void)
|
|||
|
||||
// Deallocate stuffs
|
||||
LOG("Shutting down server...");
|
||||
m_Server->Shutdown(); // This waits for threads to stop and d/c clients
|
||||
m_Server->Shutdown();
|
||||
|
||||
LOG("Shutting down deadlock detector...");
|
||||
dd.Stop();
|
||||
|
||||
LOG("Stopping world threads...");
|
||||
StopWorlds();
|
||||
|
||||
LOG("Stopping authenticator...");
|
||||
m_Authenticator.Stop();
|
||||
|
||||
|
||||
LOG("Freeing MonsterConfig...");
|
||||
delete m_MonsterConfig; m_MonsterConfig = 0;
|
||||
delete m_MonsterConfig; m_MonsterConfig = NULL;
|
||||
LOG("Stopping WebAdmin...");
|
||||
delete m_WebAdmin; m_WebAdmin = 0;
|
||||
delete m_WebAdmin; m_WebAdmin = NULL;
|
||||
LOG("Unloading recipes...");
|
||||
delete m_FurnaceRecipe; m_FurnaceRecipe = NULL;
|
||||
delete m_CraftingRecipes; m_CraftingRecipes = NULL;
|
||||
|
@ -270,8 +279,9 @@ void cRoot::LoadWorlds(void)
|
|||
|
||||
void cRoot::StartWorlds(void)
|
||||
{
|
||||
for( WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr )
|
||||
for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr)
|
||||
{
|
||||
itr->second->Start();
|
||||
itr->second->InitializeSpawn();
|
||||
}
|
||||
}
|
||||
|
@ -282,9 +292,9 @@ void cRoot::StartWorlds(void)
|
|||
|
||||
void cRoot::StopWorlds(void)
|
||||
{
|
||||
for( WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr )
|
||||
for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr)
|
||||
{
|
||||
itr->second->StopThreads();
|
||||
itr->second->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,7 +354,7 @@ bool cRoot::ForEachWorld(cWorldListCallback & a_Callback)
|
|||
|
||||
|
||||
|
||||
void cRoot::TickWorlds(float a_Dt)
|
||||
void cRoot::TickCommands(void)
|
||||
{
|
||||
// Execute any pending commands:
|
||||
cCommandQueue PendingCommands;
|
||||
|
@ -356,12 +366,6 @@ void cRoot::TickWorlds(float a_Dt)
|
|||
{
|
||||
ExecuteConsoleCommand(itr->m_Command, *(itr->m_Output));
|
||||
}
|
||||
|
||||
// Tick the worlds:
|
||||
for (WorldMap::iterator itr = m_WorldsByName.begin(); itr != m_WorldsByName.end(); ++itr)
|
||||
{
|
||||
itr->second->Tick(a_Dt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -85,9 +85,10 @@ public:
|
|||
|
||||
/// Called by cAuthenticator to auth the specified user
|
||||
void AuthenticateUser(int a_ClientID);
|
||||
|
||||
void TickWorlds(float a_Dt);
|
||||
|
||||
/// Executes commands queued in the command queue
|
||||
void TickCommands(void);
|
||||
|
||||
/// Returns the number of chunks loaded
|
||||
int GetTotalChunkCount(void); // tolua_export
|
||||
|
||||
|
|
|
@ -59,18 +59,59 @@ typedef std::list< cClientHandle* > ClientList;
|
|||
|
||||
|
||||
|
||||
struct cServer::sServerState
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cServer::cTickThread:
|
||||
|
||||
cServer::cTickThread::cTickThread(cServer & a_Server) :
|
||||
super("ServerTickThread"),
|
||||
m_Server(a_Server)
|
||||
{
|
||||
sServerState()
|
||||
: pTickThread(NULL)
|
||||
, bStopTickThread(false)
|
||||
{}
|
||||
}
|
||||
|
||||
cThread* pTickThread; bool bStopTickThread;
|
||||
|
||||
cEvent RestartEvent;
|
||||
std::string ServerID;
|
||||
};
|
||||
|
||||
|
||||
|
||||
void cServer::cTickThread::Execute(void)
|
||||
{
|
||||
cTimer Timer;
|
||||
|
||||
long long msPerTick = 50; // TODO - Put this in server config file
|
||||
long long LastTime = Timer.GetNowTime();
|
||||
|
||||
while (!m_ShouldTerminate)
|
||||
{
|
||||
long long NowTime = Timer.GetNowTime();
|
||||
float DeltaTime = (float)(NowTime-LastTime);
|
||||
m_ShouldTerminate = !m_Server.Tick(DeltaTime);
|
||||
long long TickTime = Timer.GetNowTime() - NowTime;
|
||||
|
||||
if (TickTime < msPerTick)
|
||||
{
|
||||
// Stretch tick time until it's at least msPerTick
|
||||
cSleep::MilliSleep((unsigned int)(msPerTick - TickTime));
|
||||
}
|
||||
|
||||
LastTime = NowTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cServer:
|
||||
|
||||
cServer::cServer(void) :
|
||||
m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"),
|
||||
m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"),
|
||||
m_bIsConnected(false),
|
||||
m_bRestarting(false),
|
||||
m_RCONServer(*this),
|
||||
m_TickThread(*this)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -121,8 +162,45 @@ void cServer::RemoveClient(const cClientHandle * a_Client)
|
|||
|
||||
|
||||
|
||||
void cServer::ClientMovedToWorld(const cClientHandle * a_Client)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
m_ClientsToRemove.push_back(const_cast<cClientHandle *>(a_Client));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::PlayerCreated(const cPlayer * a_Player)
|
||||
{
|
||||
// To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread
|
||||
cCSLock Lock(m_CSPlayerCountDiff);
|
||||
m_PlayerCountDiff += 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::PlayerDestroying(const cPlayer * a_Player)
|
||||
{
|
||||
// To avoid deadlocks, the player count is not handled directly, but rather posted onto the tick thread
|
||||
cCSLock Lock(m_CSPlayerCountDiff);
|
||||
m_PlayerCountDiff -= 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cServer::InitServer(cIniFile & a_SettingsIni)
|
||||
{
|
||||
m_Description = a_SettingsIni.GetValue ("Server", "Description", "MCServer! - In C++!").c_str();
|
||||
m_MaxPlayers = a_SettingsIni.GetValueI("Server", "MaxPlayers", 100);
|
||||
m_PlayerCount = 0;
|
||||
m_PlayerCountDiff = 0;
|
||||
|
||||
if (m_bIsConnected)
|
||||
{
|
||||
LOGERROR("ERROR: Trying to initialize server while server is already running!");
|
||||
|
@ -164,18 +242,17 @@ bool cServer::InitServer(cIniFile & a_SettingsIni)
|
|||
|
||||
m_bIsConnected = true;
|
||||
|
||||
m_pState->ServerID = "-";
|
||||
m_ServerID = "-";
|
||||
if (a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true))
|
||||
{
|
||||
MTRand mtrand1;
|
||||
unsigned int r1 = (mtrand1.randInt()%1147483647) + 1000000000;
|
||||
unsigned int r2 = (mtrand1.randInt()%1147483647) + 1000000000;
|
||||
unsigned int r1 = (mtrand1.randInt() % 1147483647) + 1000000000;
|
||||
unsigned int r2 = (mtrand1.randInt() % 1147483647) + 1000000000;
|
||||
std::ostringstream sid;
|
||||
sid << std::hex << r1;
|
||||
sid << std::hex << r2;
|
||||
std::string ServerID = sid.str();
|
||||
ServerID.resize(16, '0');
|
||||
m_pState->ServerID = ServerID;
|
||||
m_ServerID = sid.str();
|
||||
m_ServerID.resize(16, '0');
|
||||
}
|
||||
|
||||
m_ClientViewDistance = a_SettingsIni.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE);
|
||||
|
@ -201,29 +278,10 @@ bool cServer::InitServer(cIniFile & a_SettingsIni)
|
|||
|
||||
|
||||
|
||||
cServer::cServer(void)
|
||||
: m_pState(new sServerState)
|
||||
, m_ListenThreadIPv4(*this, cSocket::IPv4, "Client")
|
||||
, m_ListenThreadIPv6(*this, cSocket::IPv6, "Client")
|
||||
, m_Millisecondsf(0)
|
||||
, m_Milliseconds(0)
|
||||
, m_bIsConnected(false)
|
||||
, m_bRestarting(false)
|
||||
, m_RCONServer(*this)
|
||||
int cServer::GetNumPlayers(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cServer::~cServer()
|
||||
{
|
||||
// TODO: Shut down the server gracefully
|
||||
m_pState->bStopTickThread = true;
|
||||
delete m_pState->pTickThread; m_pState->pTickThread = NULL;
|
||||
|
||||
delete m_pState;
|
||||
cCSLock Lock(m_CSPlayerCount);
|
||||
return m_PlayerCount;
|
||||
}
|
||||
|
||||
|
||||
|
@ -284,55 +342,22 @@ void cServer::OnConnectionAccepted(cSocket & a_Socket)
|
|||
|
||||
|
||||
|
||||
void cServer::BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
{
|
||||
if ((*itr == a_Exclude) || !(*itr)->IsLoggedIn())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
(*itr)->SendChat(a_Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cServer::Tick(float a_Dt)
|
||||
{
|
||||
m_Millisecondsf += a_Dt;
|
||||
if (m_Millisecondsf > 1.f)
|
||||
// Apply the queued playercount adjustments (postponed to avoid deadlocks)
|
||||
int PlayerCountDiff = 0;
|
||||
{
|
||||
m_Milliseconds += (int)m_Millisecondsf;
|
||||
m_Millisecondsf = m_Millisecondsf - (int)m_Millisecondsf;
|
||||
cCSLock Lock(m_CSPlayerCountDiff);
|
||||
std::swap(PlayerCountDiff, m_PlayerCountDiff);
|
||||
}
|
||||
|
||||
cRoot::Get()->TickWorlds(a_Dt); // TODO - Maybe give all worlds their own thread?
|
||||
|
||||
cClientHandleList RemoveClients;
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
{
|
||||
if ((*itr)->IsDestroyed())
|
||||
{
|
||||
RemoveClients.push_back(*itr); // Remove the client later, when CS is not held, to avoid deadlock ( http://forum.mc-server.org/showthread.php?tid=374 )
|
||||
itr = m_Clients.erase(itr);
|
||||
continue;
|
||||
}
|
||||
(*itr)->Tick(a_Dt);
|
||||
++itr;
|
||||
} // for itr - m_Clients[]
|
||||
cCSLock Lock(m_CSPlayerCount);
|
||||
m_PlayerCount += PlayerCountDiff;
|
||||
}
|
||||
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
|
||||
{
|
||||
delete *itr;
|
||||
} // for itr - RemoveClients[]
|
||||
|
||||
cRoot::Get()->GetPluginManager()->Tick(a_Dt);
|
||||
|
||||
cRoot::Get()->TickCommands();
|
||||
|
||||
TickClients(a_Dt);
|
||||
|
||||
if (!m_bRestarting)
|
||||
{
|
||||
|
@ -341,7 +366,7 @@ bool cServer::Tick(float a_Dt)
|
|||
else
|
||||
{
|
||||
m_bRestarting = false;
|
||||
m_pState->RestartEvent.Set();
|
||||
m_RestartEvent.Set();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -350,33 +375,39 @@ bool cServer::Tick(float a_Dt)
|
|||
|
||||
|
||||
|
||||
void ServerTickThread( void * a_Param )
|
||||
void cServer::TickClients(float a_Dt)
|
||||
{
|
||||
LOG("ServerTickThread");
|
||||
cServer *CServerObj = (cServer*)a_Param;
|
||||
|
||||
cTimer Timer;
|
||||
|
||||
long long msPerTick = 50; // TODO - Put this in server config file
|
||||
long long LastTime = Timer.GetNowTime();
|
||||
|
||||
bool bKeepGoing = true;
|
||||
while( bKeepGoing )
|
||||
cClientHandleList RemoveClients;
|
||||
{
|
||||
long long NowTime = Timer.GetNowTime();
|
||||
float DeltaTime = (float)(NowTime-LastTime);
|
||||
bKeepGoing = CServerObj->Tick( DeltaTime );
|
||||
long long TickTime = Timer.GetNowTime() - NowTime;
|
||||
cCSLock Lock(m_CSClients);
|
||||
|
||||
if( TickTime < msPerTick ) // Stretch tick time until it's at least msPerTick
|
||||
// Remove clients that have moved to a world (the world will be ticking them from now on)
|
||||
for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
|
||||
{
|
||||
cSleep::MilliSleep( (unsigned int)( msPerTick - TickTime ) );
|
||||
}
|
||||
|
||||
LastTime = NowTime;
|
||||
m_Clients.remove(*itr);
|
||||
} // for itr - m_ClientsToRemove[]
|
||||
m_ClientsToRemove.clear();
|
||||
|
||||
// Tick the remaining clients, take out those that have been destroyed into RemoveClients
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
{
|
||||
if ((*itr)->IsDestroyed())
|
||||
{
|
||||
// Remove the client later, when CS is not held, to avoid deadlock ( http://forum.mc-server.org/showthread.php?tid=374 )
|
||||
RemoveClients.push_back(*itr);
|
||||
itr = m_Clients.erase(itr);
|
||||
continue;
|
||||
}
|
||||
(*itr)->Tick(a_Dt);
|
||||
++itr;
|
||||
} // for itr - m_Clients[]
|
||||
}
|
||||
|
||||
LOG("TICK THREAD STOPPED");
|
||||
|
||||
// Delete the clients that have been destroyed
|
||||
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
|
||||
{
|
||||
delete *itr;
|
||||
} // for itr - RemoveClients[]
|
||||
}
|
||||
|
||||
|
||||
|
@ -385,7 +416,6 @@ void ServerTickThread( void * a_Param )
|
|||
|
||||
bool cServer::Start(void)
|
||||
{
|
||||
m_pState->pTickThread = new cThread( ServerTickThread, this, "cServer::ServerTickThread" );
|
||||
if (!m_ListenThreadIPv4.Start())
|
||||
{
|
||||
return false;
|
||||
|
@ -394,7 +424,10 @@ bool cServer::Start(void)
|
|||
{
|
||||
return false;
|
||||
}
|
||||
m_pState->pTickThread->Start( true );
|
||||
if (!m_TickThread.Start())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -478,32 +511,13 @@ void cServer::BindBuiltInConsoleCommands(void)
|
|||
|
||||
|
||||
|
||||
void cServer::SendMessage(const AString & a_Message, cPlayer * a_Player /* = NULL */, bool a_bExclude /* = false */ )
|
||||
{
|
||||
if ((a_Player != NULL) && !a_bExclude)
|
||||
{
|
||||
cClientHandle * Client = a_Player->GetClientHandle();
|
||||
if (Client != NULL)
|
||||
{
|
||||
Client->SendChat(a_Message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
BroadcastChat(a_Message, (a_Player != NULL) ? a_Player->GetClientHandle() : NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::Shutdown()
|
||||
void cServer::Shutdown(void)
|
||||
{
|
||||
m_ListenThreadIPv4.Stop();
|
||||
m_ListenThreadIPv6.Stop();
|
||||
|
||||
m_bRestarting = true;
|
||||
m_pState->RestartEvent.Wait();
|
||||
m_RestartEvent.Wait();
|
||||
|
||||
cRoot::Get()->SaveAllChunks();
|
||||
|
||||
|
@ -520,15 +534,6 @@ void cServer::Shutdown()
|
|||
|
||||
|
||||
|
||||
const AString & cServer::GetServerID(void) const
|
||||
{
|
||||
return m_pState->ServerID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::KickUser(int a_ClientID, const AString & a_Reason)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
|
@ -553,6 +558,7 @@ void cServer::AuthenticateUser(int a_ClientID)
|
|||
if ((*itr)->GetUniqueID() == a_ClientID)
|
||||
{
|
||||
(*itr)->Authenticate();
|
||||
return;
|
||||
}
|
||||
} // for itr - m_Clients[]
|
||||
}
|
||||
|
@ -562,7 +568,7 @@ void cServer::AuthenticateUser(int a_ClientID)
|
|||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cServer::cClientPacketThread:
|
||||
// cServer::cNotifyWriteThread:
|
||||
|
||||
cServer::cNotifyWriteThread::cNotifyWriteThread(void) :
|
||||
super("ClientPacketThread"),
|
||||
|
|
|
@ -37,11 +37,16 @@ class cServer // tolua_export
|
|||
public: // tolua_export
|
||||
bool InitServer(cIniFile & a_SettingsIni);
|
||||
|
||||
bool IsConnected(void) const { return m_bIsConnected;} // returns connection status
|
||||
// tolua_begin
|
||||
|
||||
void BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export
|
||||
const AString & GetDescription(void) const {return m_Description; }
|
||||
|
||||
bool Tick(float a_Dt);
|
||||
// Player counts:
|
||||
int GetMaxPlayers(void) const {return m_MaxPlayers; }
|
||||
int GetNumPlayers(void);
|
||||
void SetMaxPlayers(int a_MaxPlayers) { m_MaxPlayers = a_MaxPlayers; }
|
||||
|
||||
// tolua_end
|
||||
|
||||
bool Start(void);
|
||||
|
||||
|
@ -53,14 +58,12 @@ public: // tolua_export
|
|||
/// Binds the built-in console commands with the plugin manager
|
||||
static void BindBuiltInConsoleCommands(void);
|
||||
|
||||
void Shutdown();
|
||||
void Shutdown(void);
|
||||
|
||||
void SendMessage(const AString & a_Message, cPlayer * a_Player = NULL, bool a_bExclude = false ); // tolua_export
|
||||
|
||||
void KickUser(int a_ClientID, const AString & a_Reason);
|
||||
void AuthenticateUser(int a_ClientID); // Called by cAuthenticator to auth the specified user
|
||||
|
||||
const AString & GetServerID(void) const; // tolua_export
|
||||
const AString & GetServerID(void) const { return m_ServerID; } // tolua_export
|
||||
|
||||
void ClientDestroying(const cClientHandle * a_Client); // Called by cClientHandle::Destroy(); stop m_SocketThreads from calling back into a_Client
|
||||
|
||||
|
@ -72,6 +75,15 @@ public: // tolua_export
|
|||
|
||||
void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads
|
||||
|
||||
/// Don't tick a_Client anymore, it will be ticked from its cPlayer instead
|
||||
void ClientMovedToWorld(const cClientHandle * a_Client);
|
||||
|
||||
/// Notifies the server that a player was created; the server uses this to adjust the number of players
|
||||
void PlayerCreated(const cPlayer * a_Player);
|
||||
|
||||
/// Notifies the server that a player is being destroyed; the server uses this to adjust the number of players
|
||||
void PlayerDestroying(const cPlayer * a_Player);
|
||||
|
||||
CryptoPP::RSA::PrivateKey & GetPrivateKey(void) { return m_PrivateKey; }
|
||||
CryptoPP::RSA::PublicKey & GetPublicKey (void) { return m_PublicKey; }
|
||||
|
||||
|
@ -103,25 +115,41 @@ private:
|
|||
void NotifyClientWrite(const cClientHandle * a_Client);
|
||||
} ;
|
||||
|
||||
struct sServerState;
|
||||
sServerState * m_pState;
|
||||
/// The server tick thread takes care of the players who aren't yet spawned in a world
|
||||
class cTickThread :
|
||||
public cIsThread
|
||||
{
|
||||
typedef cIsThread super;
|
||||
|
||||
public:
|
||||
cTickThread(cServer & a_Server);
|
||||
|
||||
protected:
|
||||
cServer & m_Server;
|
||||
|
||||
// cIsThread overrides:
|
||||
virtual void Execute(void) override;
|
||||
} ;
|
||||
|
||||
|
||||
cNotifyWriteThread m_NotifyWriteThread;
|
||||
|
||||
cListenThread m_ListenThreadIPv4;
|
||||
cListenThread m_ListenThreadIPv6;
|
||||
|
||||
cCriticalSection m_CSClients; // Locks client list
|
||||
cClientHandleList m_Clients; // Clients that are connected to the server
|
||||
cCriticalSection m_CSClients; ///< Locks client lists
|
||||
cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld
|
||||
cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick()
|
||||
|
||||
cCriticalSection m_CSPlayerCount; ///< Locks the m_PlayerCount
|
||||
int m_PlayerCount; ///< Number of players currently playing in the server
|
||||
cCriticalSection m_CSPlayerCountDiff; ///< Locks the m_PlayerCountDiff
|
||||
int m_PlayerCountDiff; ///< Adjustment to m_PlayerCount to be applied in the Tick thread
|
||||
|
||||
cSocketThreads m_SocketThreads;
|
||||
|
||||
int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini
|
||||
|
||||
// Time since server was started
|
||||
float m_Millisecondsf;
|
||||
unsigned int m_Milliseconds;
|
||||
|
||||
bool m_bIsConnected; // true - connected false - not connected
|
||||
|
||||
bool m_bRestarting;
|
||||
|
@ -131,13 +159,26 @@ private:
|
|||
|
||||
cRCONServer m_RCONServer;
|
||||
|
||||
AString m_Description;
|
||||
int m_MaxPlayers;
|
||||
|
||||
cTickThread m_TickThread;
|
||||
cEvent m_RestartEvent;
|
||||
|
||||
/// The server ID used for client authentication
|
||||
AString m_ServerID;
|
||||
|
||||
|
||||
cServer(void);
|
||||
~cServer();
|
||||
|
||||
/// Loads, or generates, if missing, RSA keys for protocol encryption
|
||||
void PrepareKeys(void);
|
||||
|
||||
bool Tick(float a_Dt);
|
||||
|
||||
/// Ticks the clients in m_Clients, manages the list in respect to removing clients
|
||||
void TickClients(float a_Dt);
|
||||
|
||||
// cListenThread::cCallback overrides:
|
||||
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
|
||||
}; // tolua_export
|
||||
|
|
418
source/World.cpp
418
source/World.cpp
|
@ -1,3 +1,4 @@
|
|||
|
||||
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
|
||||
|
||||
#include "BlockID.h"
|
||||
|
@ -128,6 +129,9 @@ protected:
|
|||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cWorldLightingProgress:
|
||||
|
||||
/// A simple thread that displays the progress of world lighting in cWorld::InitializeSpawn()
|
||||
class cWorldLightingProgress :
|
||||
public cIsThread
|
||||
|
@ -187,10 +191,47 @@ cWorld::cLock::cLock(cWorld & a_World) :
|
|||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cWorld::cTickThread:
|
||||
|
||||
cWorld::cTickThread::cTickThread(cWorld & a_World) :
|
||||
super(Printf("WorldTickThread: %s", a_World.GetName().c_str())),
|
||||
m_World(a_World)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::cTickThread::Execute(void)
|
||||
{
|
||||
const int ClocksPerTick = CLOCKS_PER_SEC / 20;
|
||||
clock_t LastTime = clock();
|
||||
while (!m_ShouldTerminate)
|
||||
{
|
||||
clock_t Start = clock();
|
||||
m_World.Tick((float)(1000 * (Start - LastTime)) / CLOCKS_PER_SEC);
|
||||
clock_t Now = clock();
|
||||
if (Now - Start < ClocksPerTick)
|
||||
{
|
||||
cSleep::MilliSleep(1000 * (ClocksPerTick - (Now - Start)) / CLOCKS_PER_SEC);
|
||||
}
|
||||
LastTime = Start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cWorld:
|
||||
|
||||
cWorld::cWorld(const AString & a_WorldName) :
|
||||
m_WorldName(a_WorldName),
|
||||
m_IniFileName(m_WorldName + "/world.ini"),
|
||||
m_StorageSchema("Default"),
|
||||
m_WorldAgeSecs(0),
|
||||
m_TimeOfDaySecs(0),
|
||||
m_WorldAge(0),
|
||||
|
@ -199,109 +240,12 @@ cWorld::cWorld(const AString & a_WorldName) :
|
|||
m_LastSpawnMonster(0),
|
||||
m_RSList(0),
|
||||
m_Weather(eWeather_Sunny),
|
||||
m_WeatherInterval(24000) // Guaranteed 1 day of sunshine at server start :)
|
||||
m_WeatherInterval(24000), // Guaranteed 1 day of sunshine at server start :)
|
||||
m_TickThread(*this)
|
||||
{
|
||||
LOGD("cWorld::cWorld(%s)", a_WorldName.c_str());
|
||||
m_WorldName = a_WorldName;
|
||||
m_IniFileName = m_WorldName + "/world.ini";
|
||||
LOGD("cWorld::cWorld(\"%s\")", a_WorldName.c_str());
|
||||
|
||||
cMakeDir::MakeDir(m_WorldName.c_str());
|
||||
|
||||
MTRand r1;
|
||||
m_SpawnX = (double)((r1.randInt() % 1000) - 500);
|
||||
m_SpawnY = cChunkDef::Height;
|
||||
m_SpawnZ = (double)((r1.randInt() % 1000) - 500);
|
||||
m_GameMode = eGameMode_Creative;
|
||||
|
||||
AString StorageSchema("Default");
|
||||
|
||||
cIniFile IniFile(m_IniFileName);
|
||||
IniFile.ReadFile();
|
||||
AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld");
|
||||
m_Dimension = StringToDimension(Dimension);
|
||||
switch (m_Dimension)
|
||||
{
|
||||
case dimNether:
|
||||
case dimOverworld:
|
||||
case dimEnd:
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOGWARNING("Unknown dimension: \"%s\". Setting to Overworld", Dimension.c_str());
|
||||
m_Dimension = dimOverworld;
|
||||
break;
|
||||
}
|
||||
} // switch (m_Dimension)
|
||||
m_SpawnX = IniFile.GetValueSetF("SpawnPosition", "X", m_SpawnX);
|
||||
m_SpawnY = IniFile.GetValueSetF("SpawnPosition", "Y", m_SpawnY);
|
||||
m_SpawnZ = IniFile.GetValueSetF("SpawnPosition", "Z", m_SpawnZ);
|
||||
StorageSchema = IniFile.GetValueSet ("Storage", "Schema", StorageSchema);
|
||||
m_MaxCactusHeight = IniFile.GetValueSetI("Plants", "MaxCactusHeight", 3);
|
||||
m_MaxSugarcaneHeight = IniFile.GetValueSetI("Plants", "MaxSugarcaneHeight", 3);
|
||||
m_IsCactusBonemealable = IniFile.GetValueSetB("Plants", "IsCactusBonemealable", false);
|
||||
m_IsCarrotsBonemealable = IniFile.GetValueSetB("Plants", "IsCarrotsBonemealable", true);
|
||||
m_IsCropsBonemealable = IniFile.GetValueSetB("Plants", "IsCropsBonemealable", true);
|
||||
m_IsGrassBonemealable = IniFile.GetValueSetB("Plants", "IsGrassBonemealable", true);
|
||||
m_IsMelonStemBonemealable = IniFile.GetValueSetB("Plants", "IsMelonStemBonemealable", true);
|
||||
m_IsMelonBonemealable = IniFile.GetValueSetB("Plants", "IsMelonBonemealable", false);
|
||||
m_IsPotatoesBonemealable = IniFile.GetValueSetB("Plants", "IsPotatoesBonemealable", true);
|
||||
m_IsPumpkinStemBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinStemBonemealable", true);
|
||||
m_IsPumpkinBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinBonemealable", false);
|
||||
m_IsSaplingBonemealable = IniFile.GetValueSetB("Plants", "IsSaplingBonemealable", true);
|
||||
m_IsSugarcaneBonemealable = IniFile.GetValueSetB("Plants", "IsSugarcaneBonemealable", false);
|
||||
m_bEnabledPVP = IniFile.GetValueSetB("PVP", "Enabled", true);
|
||||
m_IsDeepSnowEnabled = IniFile.GetValueSetB("Physics", "DeepSnow", false);
|
||||
|
||||
m_GameMode = (eGameMode)IniFile.GetValueSetI("GameMode", "GameMode", m_GameMode);
|
||||
|
||||
m_Lighting.Start(this);
|
||||
m_Storage.Start(this, StorageSchema);
|
||||
m_Generator.Start(this, IniFile);
|
||||
|
||||
m_bAnimals = true;
|
||||
m_SpawnMonsterRate = 200; // 1 mob each 10 seconds
|
||||
cIniFile IniFile2("settings.ini");
|
||||
if (IniFile2.ReadFile())
|
||||
{
|
||||
m_bAnimals = IniFile2.GetValueB("Monsters", "AnimalsOn", true);
|
||||
m_SpawnMonsterRate = (Int64)(IniFile2.GetValueF("Monsters", "AnimalSpawnInterval", 10) * 20); // Convert from secs to ticks
|
||||
|
||||
// TODO: Move this into cServer instead:
|
||||
SetMaxPlayers(IniFile2.GetValueI("Server", "MaxPlayers", 100));
|
||||
m_Description = IniFile2.GetValue("Server", "Description", "MCServer! - In C++!").c_str();
|
||||
}
|
||||
|
||||
m_ChunkMap = new cChunkMap(this);
|
||||
|
||||
m_ChunkSender.Start(this);
|
||||
|
||||
m_LastSave = 0;
|
||||
m_LastUnload = 0;
|
||||
|
||||
// preallocate some memory for ticking blocks so we don<6F>t need to allocate that often
|
||||
m_BlockTickQueue.reserve(1000);
|
||||
m_BlockTickQueueCopy.reserve(1000);
|
||||
|
||||
// Simulators:
|
||||
m_SimulatorManager = new cSimulatorManager(*this);
|
||||
m_WaterSimulator = InitializeFluidSimulator(IniFile, "Water", E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER);
|
||||
m_LavaSimulator = InitializeFluidSimulator(IniFile, "Lava", E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA);
|
||||
m_SandSimulator = new cSandSimulator(*this, IniFile);
|
||||
m_FireSimulator = new cFireSimulator(*this, IniFile);
|
||||
m_RedstoneSimulator = new cRedstoneSimulator(*this);
|
||||
|
||||
// Water and Lava simulators get registered in InitializeFluidSimulator()
|
||||
m_SimulatorManager->RegisterSimulator(m_SandSimulator, 1);
|
||||
m_SimulatorManager->RegisterSimulator(m_FireSimulator, 1);
|
||||
m_SimulatorManager->RegisterSimulator(m_RedstoneSimulator, 1);
|
||||
|
||||
// Save any changes that the defaults may have done to the ini file:
|
||||
if (!IniFile.WriteFile())
|
||||
{
|
||||
LOGWARNING("Could not write world config to %s", m_IniFileName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -504,10 +448,126 @@ void cWorld::InitializeSpawn(void)
|
|||
|
||||
|
||||
|
||||
void cWorld::StopThreads(void)
|
||||
void cWorld::Start(void)
|
||||
{
|
||||
// TODO: Find a proper spawn location, based on the biomes (not in ocean)
|
||||
m_SpawnX = (double)((m_TickRand.randInt() % 1000) - 500);
|
||||
m_SpawnY = cChunkDef::Height;
|
||||
m_SpawnZ = (double)((m_TickRand.randInt() % 1000) - 500);
|
||||
m_GameMode = eGameMode_Creative;
|
||||
|
||||
cIniFile IniFile(m_IniFileName);
|
||||
if (!IniFile.ReadFile())
|
||||
{
|
||||
LOGWARNING("Cannot read world settings from \"%s\", defaults will be used.", m_IniFileName.c_str());
|
||||
}
|
||||
AString Dimension = IniFile.GetValueSet("General", "Dimension", "Overworld");
|
||||
m_Dimension = StringToDimension(Dimension);
|
||||
switch (m_Dimension)
|
||||
{
|
||||
case dimNether:
|
||||
case dimOverworld:
|
||||
case dimEnd:
|
||||
{
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOGWARNING("Unknown dimension: \"%s\". Setting to Overworld", Dimension.c_str());
|
||||
m_Dimension = dimOverworld;
|
||||
break;
|
||||
}
|
||||
} // switch (m_Dimension)
|
||||
m_SpawnX = IniFile.GetValueSetF("SpawnPosition", "X", m_SpawnX);
|
||||
m_SpawnY = IniFile.GetValueSetF("SpawnPosition", "Y", m_SpawnY);
|
||||
m_SpawnZ = IniFile.GetValueSetF("SpawnPosition", "Z", m_SpawnZ);
|
||||
m_StorageSchema = IniFile.GetValueSet ("Storage", "Schema", m_StorageSchema);
|
||||
m_MaxCactusHeight = IniFile.GetValueSetI("Plants", "MaxCactusHeight", 3);
|
||||
m_MaxSugarcaneHeight = IniFile.GetValueSetI("Plants", "MaxSugarcaneHeight", 3);
|
||||
m_IsCactusBonemealable = IniFile.GetValueSetB("Plants", "IsCactusBonemealable", false);
|
||||
m_IsCarrotsBonemealable = IniFile.GetValueSetB("Plants", "IsCarrotsBonemealable", true);
|
||||
m_IsCropsBonemealable = IniFile.GetValueSetB("Plants", "IsCropsBonemealable", true);
|
||||
m_IsGrassBonemealable = IniFile.GetValueSetB("Plants", "IsGrassBonemealable", true);
|
||||
m_IsMelonStemBonemealable = IniFile.GetValueSetB("Plants", "IsMelonStemBonemealable", true);
|
||||
m_IsMelonBonemealable = IniFile.GetValueSetB("Plants", "IsMelonBonemealable", false);
|
||||
m_IsPotatoesBonemealable = IniFile.GetValueSetB("Plants", "IsPotatoesBonemealable", true);
|
||||
m_IsPumpkinStemBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinStemBonemealable", true);
|
||||
m_IsPumpkinBonemealable = IniFile.GetValueSetB("Plants", "IsPumpkinBonemealable", false);
|
||||
m_IsSaplingBonemealable = IniFile.GetValueSetB("Plants", "IsSaplingBonemealable", true);
|
||||
m_IsSugarcaneBonemealable = IniFile.GetValueSetB("Plants", "IsSugarcaneBonemealable", false);
|
||||
m_bEnabledPVP = IniFile.GetValueSetB("PVP", "Enabled", true);
|
||||
m_IsDeepSnowEnabled = IniFile.GetValueSetB("Physics", "DeepSnow", false);
|
||||
|
||||
m_GameMode = (eGameMode)IniFile.GetValueSetI("GameMode", "GameMode", m_GameMode);
|
||||
|
||||
m_bAnimals = true;
|
||||
m_SpawnMonsterRate = 200; // 1 mob each 10 seconds
|
||||
cIniFile IniFile2("settings.ini");
|
||||
if (IniFile2.ReadFile())
|
||||
{
|
||||
m_bAnimals = IniFile2.GetValueB("Monsters", "AnimalsOn", true);
|
||||
m_SpawnMonsterRate = (Int64)(IniFile2.GetValueF("Monsters", "AnimalSpawnInterval", 10) * 20); // Convert from secs to ticks
|
||||
|
||||
}
|
||||
|
||||
m_ChunkMap = new cChunkMap(this);
|
||||
|
||||
m_LastSave = 0;
|
||||
m_LastUnload = 0;
|
||||
|
||||
// preallocate some memory for ticking blocks so we don<6F>t need to allocate that often
|
||||
m_BlockTickQueue.reserve(1000);
|
||||
m_BlockTickQueueCopy.reserve(1000);
|
||||
|
||||
// Simulators:
|
||||
m_SimulatorManager = new cSimulatorManager(*this);
|
||||
m_WaterSimulator = InitializeFluidSimulator(IniFile, "Water", E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER);
|
||||
m_LavaSimulator = InitializeFluidSimulator(IniFile, "Lava", E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA);
|
||||
m_SandSimulator = new cSandSimulator(*this, IniFile);
|
||||
m_FireSimulator = new cFireSimulator(*this, IniFile);
|
||||
m_RedstoneSimulator = new cRedstoneSimulator(*this);
|
||||
|
||||
// Water and Lava simulators get registered in InitializeFluidSimulator()
|
||||
m_SimulatorManager->RegisterSimulator(m_SandSimulator, 1);
|
||||
m_SimulatorManager->RegisterSimulator(m_FireSimulator, 1);
|
||||
m_SimulatorManager->RegisterSimulator(m_RedstoneSimulator, 1);
|
||||
|
||||
m_Lighting.Start(this);
|
||||
m_Storage.Start(this, m_StorageSchema);
|
||||
m_Generator.Start(this, IniFile);
|
||||
m_ChunkSender.Start(this);
|
||||
m_TickThread.Start();
|
||||
|
||||
// Save any changes that the defaults may have done to the ini file:
|
||||
if (!IniFile.WriteFile())
|
||||
{
|
||||
LOGWARNING("Could not write world config to %s", m_IniFileName.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::Stop(void)
|
||||
{
|
||||
// Delete the clients that have been in this world:
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
{
|
||||
(*itr)->Destroy();
|
||||
delete *itr;
|
||||
} // for itr - m_Clients[]
|
||||
m_Clients.clear();
|
||||
}
|
||||
|
||||
m_TickThread.Stop();
|
||||
m_Lighting.Stop();
|
||||
m_Generator.Stop();
|
||||
m_ChunkSender.Stop();
|
||||
m_Storage.Stop();
|
||||
}
|
||||
|
||||
|
||||
|
@ -529,7 +589,7 @@ void cWorld::Tick(float a_Dt)
|
|||
m_WorldAge = (Int64)(m_WorldAgeSecs * 20.0);
|
||||
m_TimeOfDay = (Int64)(m_TimeOfDaySecs * 20.0);
|
||||
|
||||
// Broadcase time update every 40 ticks (2 seconds)
|
||||
// Broadcast time update every 40 ticks (2 seconds)
|
||||
if (m_LastTimeUpdate < m_WorldAge - 40)
|
||||
{
|
||||
BroadcastTimeUpdate();
|
||||
|
@ -538,7 +598,9 @@ void cWorld::Tick(float a_Dt)
|
|||
|
||||
m_ChunkMap->Tick(a_Dt);
|
||||
|
||||
TickClients(a_Dt);
|
||||
TickQueuedBlocks(a_Dt);
|
||||
TickQueuedTasks();
|
||||
|
||||
GetSimulatorManager()->Simulate(a_Dt);
|
||||
|
||||
|
@ -740,6 +802,78 @@ void cWorld::TickSpawnMobs(float a_Dt)
|
|||
|
||||
|
||||
|
||||
void cWorld::TickQueuedTasks(void)
|
||||
{
|
||||
// Make a copy of the tasks to avoid deadlocks on accessing m_Tasks
|
||||
cTasks Tasks;
|
||||
{
|
||||
cCSLock Lock(m_CSTasks);
|
||||
std::swap(Tasks, m_Tasks);
|
||||
}
|
||||
|
||||
// Execute and delete each task:
|
||||
for (cTasks::iterator itr = m_Tasks.begin(), end = m_Tasks.end(); itr != end; ++itr)
|
||||
{
|
||||
(*itr)->Run(*this);
|
||||
delete *itr;
|
||||
} // for itr - m_Tasks[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::TickClients(float a_Dt)
|
||||
{
|
||||
cClientHandleList RemoveClients;
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
|
||||
// Remove clients scheduled for removal:
|
||||
for (cClientHandleList::iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
|
||||
{
|
||||
m_Clients.remove(*itr);
|
||||
} // for itr - m_ClientsToRemove[]
|
||||
m_ClientsToRemove.clear();
|
||||
|
||||
// Add clients scheduled for adding:
|
||||
for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
|
||||
{
|
||||
if (std::find(m_Clients.begin(), m_Clients.end(), *itr) != m_Clients.end())
|
||||
{
|
||||
ASSERT(!"Adding a client that is already in the clientlist");
|
||||
continue;
|
||||
}
|
||||
m_Clients.push_back(*itr);
|
||||
} // for itr - m_ClientsToRemove[]
|
||||
m_ClientsToAdd.clear();
|
||||
|
||||
// Tick the clients, take out those that have been destroyed into RemoveClients
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
{
|
||||
if ((*itr)->IsDestroyed())
|
||||
{
|
||||
// Remove the client later, when CS is not held, to avoid deadlock
|
||||
RemoveClients.push_back(*itr);
|
||||
itr = m_Clients.erase(itr);
|
||||
continue;
|
||||
}
|
||||
(*itr)->Tick(a_Dt);
|
||||
++itr;
|
||||
} // for itr - m_Clients[]
|
||||
}
|
||||
|
||||
// Delete the clients that have been destroyed
|
||||
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
|
||||
{
|
||||
delete *itr;
|
||||
} // for itr - RemoveClients[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::WakeUpSimulators(int a_BlockX, int a_BlockY, int a_BlockZ)
|
||||
{
|
||||
return m_ChunkMap->WakeUpSimulators(a_BlockX, a_BlockY, a_BlockZ);
|
||||
|
@ -1900,28 +2034,24 @@ void cWorld::CollectPickupsByPlayer(cPlayer * a_Player)
|
|||
|
||||
|
||||
|
||||
void cWorld::SetMaxPlayers(int iMax)
|
||||
{
|
||||
m_MaxPlayers = MAX_PLAYERS;
|
||||
if (iMax > 0 && iMax < MAX_PLAYERS)
|
||||
{
|
||||
m_MaxPlayers = iMax;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::AddPlayer(cPlayer * a_Player)
|
||||
{
|
||||
cCSLock Lock(m_CSPlayers);
|
||||
|
||||
ASSERT(std::find(m_Players.begin(), m_Players.end(), a_Player) == m_Players.end()); // Is it already in the list? HOW?
|
||||
|
||||
m_Players.remove(a_Player); // Make sure the player is registered only once
|
||||
m_Players.push_back(a_Player);
|
||||
{
|
||||
cCSLock Lock(m_CSPlayers);
|
||||
|
||||
ASSERT(std::find(m_Players.begin(), m_Players.end(), a_Player) == m_Players.end()); // Is it already in the list? HOW?
|
||||
|
||||
m_Players.remove(a_Player); // Make sure the player is registered only once
|
||||
m_Players.push_back(a_Player);
|
||||
}
|
||||
|
||||
// Add the player's client to the list of clients to be ticked:
|
||||
if (a_Player->GetClientHandle() != NULL)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
m_ClientsToAdd.push_back(a_Player->GetClientHandle());
|
||||
}
|
||||
|
||||
// The player has already been added to the chunkmap as the entity, do NOT add again!
|
||||
}
|
||||
|
||||
|
@ -1932,8 +2062,17 @@ void cWorld::AddPlayer(cPlayer * a_Player)
|
|||
void cWorld::RemovePlayer(cPlayer * a_Player)
|
||||
{
|
||||
m_ChunkMap->RemoveEntity(a_Player);
|
||||
cCSLock Lock(m_CSPlayers);
|
||||
m_Players.remove(a_Player);
|
||||
{
|
||||
cCSLock Lock(m_CSPlayers);
|
||||
m_Players.remove(a_Player);
|
||||
}
|
||||
|
||||
// Remove the player's client from the list of clients to be ticked:
|
||||
if (a_Player->GetClientHandle() != NULL)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
m_ClientsToRemove.push_back(a_Player->GetClientHandle());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -2279,6 +2418,25 @@ void cWorld::SaveAllChunks(void)
|
|||
|
||||
|
||||
|
||||
void cWorld::QueueSaveAllChunks(void)
|
||||
{
|
||||
QueueTask(new cWorld::cTaskSaveAllChunks);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::QueueTask(cTask * a_Task)
|
||||
{
|
||||
cCSLock Lock(m_CSTasks);
|
||||
m_Tasks.push_back(a_Task);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorld::AddEntity(cEntity * a_Entity)
|
||||
{
|
||||
m_ChunkMap->AddEntity(a_Entity);
|
||||
|
@ -2306,11 +2464,13 @@ void cWorld::RemoveEntity(cEntity * a_Entity)
|
|||
|
||||
|
||||
|
||||
/*
|
||||
unsigned int cWorld::GetNumPlayers(void)
|
||||
{
|
||||
cCSLock Lock(m_CSPlayers);
|
||||
return m_Players.size();
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
@ -2522,3 +2682,15 @@ cFluidSimulator * cWorld::InitializeFluidSimulator(cIniFile & a_IniFile, const c
|
|||
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// cWorld::cTaskSaveAllChunks:
|
||||
|
||||
void cWorld::cTaskSaveAllChunks::Run(cWorld & a_World)
|
||||
{
|
||||
a_World.SaveAllChunks();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
111
source/World.h
111
source/World.h
|
@ -69,6 +69,24 @@ public:
|
|||
public:
|
||||
cLock(cWorld & a_World);
|
||||
} ;
|
||||
|
||||
/// A common ancestor for all tasks queued onto the tick thread
|
||||
class cTask
|
||||
{
|
||||
public:
|
||||
virtual void Run(cWorld & a_World) = 0;
|
||||
} ;
|
||||
|
||||
typedef std::vector<cTask *> cTasks;
|
||||
|
||||
class cTaskSaveAllChunks :
|
||||
public cTask
|
||||
{
|
||||
protected:
|
||||
// cTask overrides:
|
||||
virtual void Run(cWorld & a_World) override;
|
||||
} ;
|
||||
|
||||
|
||||
// tolua_begin
|
||||
|
||||
|
@ -136,8 +154,8 @@ public:
|
|||
void BroadcastAttachEntity (const cEntity & a_Entity, const cEntity * a_Vehicle);
|
||||
void BroadcastBlockAction (int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType, const cClientHandle * a_Exclude = NULL);
|
||||
void BroadcastBlockBreakAnimation(int a_EntityID, int a_BlockX, int a_BlockY, int a_BlockZ, char a_Stage, const cClientHandle * a_Exclude = NULL);
|
||||
void BroadcastBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = NULL); ///< If there is a block entity at the specified coods, sends it to all clients except a_Exclude
|
||||
void BroadcastChat (const AString & a_Message, const cClientHandle * a_Exclude = NULL);
|
||||
void BroadcastBlockEntity (int a_BlockX, int a_BlockY, int a_BlockZ, const cClientHandle * a_Exclude = NULL); ///< If there is a block entity at the specified coods, sends it to all clients except a_Exclude
|
||||
void BroadcastChat (const AString & a_Message, const cClientHandle * a_Exclude = NULL); // tolua_export
|
||||
void BroadcastChunkData (int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer, const cClientHandle * a_Exclude = NULL);
|
||||
void BroadcastCollectPickup (const cPickup & a_Pickup, const cPlayer & a_Player, const cClientHandle * a_Exclude = NULL);
|
||||
void BroadcastDestroyEntity (const cEntity & a_Entity, const cClientHandle * a_Exclude = NULL);
|
||||
|
@ -204,13 +222,6 @@ public:
|
|||
|
||||
void CollectPickupsByPlayer(cPlayer * a_Player);
|
||||
|
||||
// MOTD
|
||||
const AString & GetDescription(void) const {return m_Description; } // FIXME: This should not be in cWorld
|
||||
|
||||
// Max Players
|
||||
unsigned int GetMaxPlayers(void) const {return m_MaxPlayers; } // tolua_export
|
||||
void SetMaxPlayers(int iMax); // tolua_export
|
||||
|
||||
void AddPlayer( cPlayer* a_Player );
|
||||
void RemovePlayer( cPlayer* a_Player );
|
||||
|
||||
|
@ -223,8 +234,6 @@ public:
|
|||
/// Finds a player from a partial or complete player name and calls the callback - case-insensitive
|
||||
bool FindAndDoWithPlayer(const AString & a_PlayerNameHint, cPlayerListCallback & a_Callback); // >> EXPORTED IN MANUALBINDINGS <<
|
||||
|
||||
unsigned int GetNumPlayers(); // tolua_export
|
||||
|
||||
// TODO: This interface is dangerous - rewrite to DoWithClosestPlayer(pos, sight, action)
|
||||
cPlayer * FindClosestPlayer(const Vector3f & a_Pos, float a_SightLimit);
|
||||
|
||||
|
@ -470,10 +479,17 @@ public:
|
|||
if(a_Z < 0 && a_Z % cChunkDef::Width != 0) a_ChunkZ--;
|
||||
}
|
||||
|
||||
void SaveAllChunks(void); // tolua_export
|
||||
/// Saves all chunks immediately. Dangerous interface, may deadlock, use QueueSaveAllChunks() instead
|
||||
void SaveAllChunks(void); // tolua_export
|
||||
|
||||
/// Queues a task to save all chunks onto the tick thread. The prefferred way of saving chunks from external sources
|
||||
void QueueSaveAllChunks(void); // tolua_export
|
||||
|
||||
/// Queues a task onto the tick thread. The task object will be deleted once the task is finished
|
||||
void QueueTask(cTask * a_Task);
|
||||
|
||||
/// Returns the number of chunks loaded
|
||||
int GetNumChunks() const; // tolua_export
|
||||
int GetNumChunks() const; // tolua_export
|
||||
|
||||
/// Returns the number of chunks loaded and dirty, and in the lighting queue
|
||||
void GetChunkStats(int & a_NumValid, int & a_NumDirty, int & a_NumInLightingQueue);
|
||||
|
@ -484,12 +500,14 @@ public:
|
|||
inline int GetStorageLoadQueueLength(void) { return m_Storage.GetLoadQueueLength(); } // tolua_export
|
||||
inline int GetStorageSaveQueueLength(void) { return m_Storage.GetSaveQueueLength(); } // tolua_export
|
||||
|
||||
void Tick(float a_Dt);
|
||||
|
||||
void InitializeSpawn(void);
|
||||
|
||||
/// Starts threads that belong to this world
|
||||
void Start(void);
|
||||
|
||||
/// Stops threads that belong to this world (part of deinit)
|
||||
void StopThreads(void);
|
||||
void Stop(void);
|
||||
|
||||
void TickQueuedBlocks(float a_Dt);
|
||||
|
||||
struct BlockTickQueueItem
|
||||
|
@ -540,7 +558,28 @@ public:
|
|||
private:
|
||||
|
||||
friend class cRoot;
|
||||
|
||||
class cTickThread :
|
||||
public cIsThread
|
||||
{
|
||||
typedef cIsThread super;
|
||||
public:
|
||||
cTickThread(cWorld & a_World);
|
||||
|
||||
protected:
|
||||
cWorld & m_World;
|
||||
|
||||
// cIsThread overrides:
|
||||
virtual void Execute(void) override;
|
||||
} ;
|
||||
|
||||
|
||||
AString m_WorldName;
|
||||
AString m_IniFileName;
|
||||
|
||||
/// Name of the storage schema used to load and save chunks
|
||||
AString m_StorageSchema;
|
||||
|
||||
/// The dimension of the world, used by the client to provide correct lighting scheme
|
||||
eDimension m_Dimension;
|
||||
|
||||
|
@ -583,8 +622,6 @@ private:
|
|||
|
||||
cWorldStorage m_Storage;
|
||||
|
||||
AString m_Description;
|
||||
|
||||
unsigned int m_MaxPlayers;
|
||||
|
||||
cChunkMap * m_ChunkMap;
|
||||
|
@ -616,15 +653,43 @@ private:
|
|||
|
||||
cChunkSender m_ChunkSender;
|
||||
cLightingThread m_Lighting;
|
||||
|
||||
AString m_WorldName;
|
||||
AString m_IniFileName;
|
||||
cTickThread m_TickThread;
|
||||
|
||||
/// Guards the m_Tasks
|
||||
cCriticalSection m_CSTasks;
|
||||
|
||||
/// Tasks that have been queued onto the tick thread; guarded by m_CSTasks
|
||||
cTasks m_Tasks;
|
||||
|
||||
/// Guards m_Clients
|
||||
cCriticalSection m_CSClients;
|
||||
|
||||
/// List of clients in this world, these will be ticked by this world
|
||||
cClientHandleList m_Clients;
|
||||
|
||||
/// Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them
|
||||
cClientHandleList m_ClientsToRemove;
|
||||
|
||||
/// Clients that are scheduled for adding, waiting for TickClients to add them
|
||||
cClientHandleList m_ClientsToAdd;
|
||||
|
||||
|
||||
cWorld(const AString & a_WorldName);
|
||||
~cWorld();
|
||||
|
||||
void TickWeather(float a_Dt); // Handles weather each tick
|
||||
void TickSpawnMobs(float a_Dt); // Handles mob spawning each tick
|
||||
void Tick(float a_Dt);
|
||||
|
||||
/// Handles the weather in each tick
|
||||
void TickWeather(float a_Dt);
|
||||
|
||||
/// Handles the mob spawning each tick
|
||||
void TickSpawnMobs(float a_Dt);
|
||||
|
||||
/// Executes all tasks queued onto the tick thread
|
||||
void TickQueuedTasks(void);
|
||||
|
||||
/// Ticks all clients that are in this world
|
||||
void TickClients(float a_Dt);
|
||||
|
||||
/// Creates a new fluid simulator, loads its settings from the inifile (a_FluidName section)
|
||||
cFluidSimulator * InitializeFluidSimulator(cIniFile & a_IniFile, const char * a_FluidName, BLOCKTYPE a_SimulateBlock, BLOCKTYPE a_StationaryBlock);
|
||||
|
|
|
@ -84,6 +84,15 @@ bool cWorldStorage::Start(cWorld * a_World, const AString & a_StorageSchemaName)
|
|||
|
||||
|
||||
|
||||
void cWorldStorage::Stop(void)
|
||||
{
|
||||
WaitForFinish();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cWorldStorage::WaitForFinish(void)
|
||||
{
|
||||
LOG("Waiting for the world storage to finish saving");
|
||||
|
|
|
@ -75,6 +75,7 @@ public:
|
|||
void UnqueueSave(const cChunkCoords & a_Chunk);
|
||||
|
||||
bool Start(cWorld * a_World, const AString & a_StorageSchemaName); // Hide the cIsThread's Start() method, we need to provide args
|
||||
void Stop(void); // Hide the cIsThread's Stop() method, we need to signal the event
|
||||
void WaitForFinish(void);
|
||||
void WaitForQueuesEmpty(void);
|
||||
|
||||
|
|
Loading…
Reference in New Issue