From 92c59963f82f81aa3202657e7fdbb2592924ede3 Mon Sep 17 00:00:00 2001 From: "cedeel@gmail.com" Date: Thu, 14 Jun 2012 13:06:06 +0000 Subject: Attempt to bring sanity to newlines across systems. git-svn-id: http://mc-server.googlecode.com/svn/trunk@606 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/cServer.cpp | 1476 ++++++++++++++++++++++++++-------------------------- 1 file changed, 738 insertions(+), 738 deletions(-) (limited to 'source/cServer.cpp') diff --git a/source/cServer.cpp b/source/cServer.cpp index c0b38c88b..65691e0cf 100644 --- a/source/cServer.cpp +++ b/source/cServer.cpp @@ -1,738 +1,738 @@ - -// ReDucTor is an awesome guy who helped me a lot - -#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules - -#include "cServer.h" -#include "cClientHandle.h" -#include "cSleep.h" -#include "cTimer.h" -#include "cMonster.h" -#include "cSocket.h" -#include "cRoot.h" -#include "cWorld.h" -#include "ChunkDef.h" -#include "cPluginManager.h" -#include "cGroupManager.h" -#include "cChatColor.h" -#include "cPlayer.h" -#include "cInventory.h" -#include "cItem.h" -#include "cFurnaceRecipe.h" -#include "cTracer.h" -#include "cWebAdmin.h" -#include "cChunk.h" - -#include "MersenneTwister.h" - -#include "../iniFile/iniFile.h" -#include "Vector3f.h" - -#include "packets/cPacket_Chat.h" - -#include -#include -#include - - - - - -extern "C" { - #include "zlib.h" -} - - - -bool g_bWaterPhysics = false; - -typedef std::list< cClientHandle* > ClientList; - - - - - -struct cServer::sServerState -{ - sServerState() - : pListenThread( 0 ) - , pTickThread( 0 ) - , bStopListenThread( false ) - , bStopTickThread( false ) - {} - cSocket SListenClient; // socket listening for client calls - - cThread* pListenThread; bool bStopListenThread; - cThread* pTickThread; bool bStopTickThread; - - cEvent RestartEvent; - std::string ServerID; -}; - - - - - -cServer * cServer::GetServer() -{ - LOGWARN("WARNING: Using deprecated function cServer::GetServer() use cRoot::Get()->GetServer() instead!"); - return cRoot::Get()->GetServer(); -} - - - - - -void cServer::ServerListenThread( void *a_Args ) -{ - LOG("ServerListenThread"); - cServer* self = (cServer*)a_Args; - sServerState* m_pState = self->m_pState; - while( !m_pState->bStopListenThread ) - { - self->StartListenClient(); - } -} - - - - - -void cServer::ClientDestroying(const cClientHandle * a_Client) -{ - m_SocketThreads.StopReading(a_Client); -} - - - - - -void cServer::NotifyClientWrite(const cClientHandle * a_Client) -{ - m_NotifyWriteThread.NotifyClientWrite(a_Client); -} - - - - - -void cServer::WriteToClient(const cSocket * a_Socket, const AString & a_Data) -{ - m_SocketThreads.Write(a_Socket, a_Data); -} - - - - - -void cServer::QueueClientClose(const cSocket * a_Socket) -{ - m_SocketThreads.QueueClose(a_Socket); -} - - - - - -void cServer::RemoveClient(const cSocket * a_Socket) -{ - m_SocketThreads.RemoveClient(a_Socket); -} - - - - - -bool cServer::InitServer( int a_Port ) -{ - if( m_bIsConnected ) - { - LOGERROR("ERROR: Trying to initialize server while server is already running!"); - return false; - } - - printf("/============================\\\n"); - printf("| Custom Minecraft Server |\n"); - printf("| Created by Kevin Bansberg |\n"); - printf("| A.K.A. FakeTruth |\n"); - printf("| Monsters by Alex Sonek |\n"); - printf("| A.K.A. Duralex |\n"); - printf("| Stuff by Mattes D |\n"); - printf("| A.K.A. _Xoft(o) |\n"); - printf("\\============================/\n"); - printf("More info: WWW.MC-SERVER.ORG\n"); - printf(" WWW.AE-C.NET\n"); - printf(" WWW.RBTHINKTANK.COM\n"); - printf("email: faketruth@gmail.com\n\n"); - - LOG("Starting up server."); - LOGINFO("Compatible clients: %s, protocol version %d", MCS_CLIENT_VERSION, MCS_PROTOCOL_VERSION); - - if( cSocket::WSAStartup() != 0 ) // Only does anything on Windows, but whatever - { - LOGERROR("WSAStartup() != 0"); - return false; - } - - m_pState->SListenClient = cSocket::CreateSocket(); - - if( !m_pState->SListenClient.IsValid() ) - { - LOGERROR("m_SListenClient==INVALID_SOCKET (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); - return false; - } - - if( m_pState->SListenClient.SetReuseAddress() == -1 ) - { - LOGERROR("setsockopt == -1"); - return false; - } - - cSocket::SockAddr_In local; - local.Family = cSocket::ADDRESS_FAMILY_INTERNET; - local.Address = cSocket::INTERNET_ADDRESS_ANY; - local.Port = (unsigned short)a_Port; // 25565 - - if( m_pState->SListenClient.Bind( local ) != 0 ) - { - LOGERROR("bind fail (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); - return false; - } - - if( m_pState->SListenClient.Listen( 10 ) != 0) - { - LOGERROR("listen fail (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); - return false; - } - - m_iServerPort = a_Port; - LOG("Port %i has been bound, server is open for connections", m_iServerPort); - m_bIsConnected = true; - - cIniFile IniFile("settings.ini"); - if (IniFile.ReadFile()) - { - g_bWaterPhysics = IniFile.GetValueB("Physics", "Water", false ); - - m_pState->ServerID = "-"; - if (IniFile.GetValueB("Authentication", "Authenticate")) - { - MTRand mtrand1; - 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_ClientViewDistance = IniFile.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE); - if (m_ClientViewDistance < cClientHandle::MIN_VIEW_DISTANCE) - { - m_ClientViewDistance = cClientHandle::MIN_VIEW_DISTANCE; - LOGINFO("Setting default viewdistance to the minimum of %d", m_ClientViewDistance); - } - if (m_ClientViewDistance > cClientHandle::MAX_VIEW_DISTANCE) - { - m_ClientViewDistance = cClientHandle::MAX_VIEW_DISTANCE; - LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance); - } - IniFile.WriteFile(); - } - - m_NotifyWriteThread.Start(this); - - return true; -} - - - - - -cServer::cServer() - : m_pState( new sServerState ) - , m_Millisecondsf( 0 ) - , m_Milliseconds( 0 ) - , m_bIsConnected( false ) - , m_iServerPort( 0 ) - , m_bRestarting( false ) -{ -} - - - - - -cServer::~cServer() -{ - // TODO: Shut down the server gracefully - if ( m_pState->SListenClient ) - { - m_pState->SListenClient.CloseSocket(); - } - m_pState->SListenClient = 0; - - m_pState->bStopListenThread = true; - delete m_pState->pListenThread; m_pState->pListenThread = NULL; - m_pState->bStopTickThread = true; - delete m_pState->pTickThread; m_pState->pTickThread = NULL; - - delete m_pState; -} - - - - - -// TODO - Need to modify this or something, so it broadcasts to all worlds? And move this to cWorld? -void cServer::Broadcast( const cPacket & a_Packet, cClientHandle* a_Exclude /* = 0 */ ) -{ - cCSLock Lock(m_CSClients); - for( ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - if ((*itr == a_Exclude) || !(*itr)->IsLoggedIn()) - { - continue; - } - (*itr)->Send( a_Packet ); - } -} - - - - - -void cServer::StartListenClient() -{ - cSocket SClient = m_pState->SListenClient.Accept(); - - if (!SClient.IsValid()) - { - return; - } - - const AString & ClientIP = SClient.GetIPString(); - if (ClientIP.empty()) - { - LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting."); - SClient.CloseSocket(); - return; - } - - LOG("Client \"%s\" connected!", ClientIP.c_str()); - - cClientHandle *NewHandle = new cClientHandle(SClient, m_ClientViewDistance); - if (!m_SocketThreads.AddClient(&(NewHandle->GetSocket()), NewHandle)) - { - // For some reason SocketThreads have rejected the handle, clean it up - LOGERROR("Client \"%s\" cannot be handled, server probably unstable", SClient.GetIPString().c_str()); - SClient.CloseSocket(); - delete NewHandle; - return; - } - - cCSLock Lock(m_CSClients); - m_Clients.push_back( NewHandle ); -} - - - - - -bool cServer::Tick(float a_Dt) -{ - //LOG("1. Tick %0.2f", a_Dt); - if( a_Dt > 100.f ) a_Dt = 100.f; // Don't go over 1/10 second - - m_Millisecondsf += a_Dt; - if( m_Millisecondsf > 1.f ) - { - m_Milliseconds += (int)m_Millisecondsf; - m_Millisecondsf = m_Millisecondsf - (int)m_Millisecondsf; - } - - 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[] - } - for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) - { - delete *itr; - } // for itr - RemoveClients[] - - cRoot::Get()->GetPluginManager()->Tick( a_Dt ); - - if( !m_bRestarting ) - { - return true; - } - else - { - m_bRestarting = false; - m_pState->RestartEvent.Set(); - return false; - } -} - - - - - -void ServerTickThread( void * a_Param ) -{ - 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 ) - { - long long NowTime = Timer.GetNowTime(); - float DeltaTime = (float)(NowTime-LastTime); - bKeepGoing = CServerObj->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; - } - - LOG("TICK THREAD STOPPED"); -} - - - - - -void cServer::StartListenThread() -{ - m_pState->pListenThread = new cThread( ServerListenThread, this, "cServer::ServerListenThread" ); - m_pState->pTickThread = new cThread( ServerTickThread, this, "cServer::ServerTickThread" ); - m_pState->pListenThread->Start( true ); - m_pState->pTickThread->Start( true ); -} - - - - - -template -bool from_string( - T& t, - const std::string& s, - std::ios_base& (*f)(std::ios_base&) -) -{ - std::istringstream iss(s); - return !(iss >> f >> t).fail(); -} - - - - - -bool cServer::Command( cClientHandle & a_Client, const char* a_Cmd ) -{ - cPluginManager* PM = cRoot::Get()->GetPluginManager(); - if( PM->CallHook( cPluginManager::E_PLUGIN_CHAT, 2, a_Cmd, a_Client.GetPlayer() ) ) - { - return true; - } - return false; -} - - - - - -void cServer::ServerCommand( const char * a_Cmd ) -{ - AString Command( a_Cmd ); - AStringVector split = StringSplit( Command, " " ); - if( split.empty()) - { - return; - } - - if( split[0].compare( "help" ) == 0 ) - { - printf("================== ALL COMMANDS ===================\n"); - printf("help - Shows this message\n"); - printf("save-all - Saves all loaded chunks to disk\n"); - printf("list - Lists all players currently in server\n"); - printf("unload - Unloads all unused chunks\n"); - printf("numchunks - Shows number of chunks currently loaded\n"); - printf("chunkstats - Shows chunks statistics\n"); - printf("say - Sends a chat message to all players\n"); - printf("restart - Kicks all clients, and saves everything\n"); - printf(" and clears memory\n"); - printf("stop - Saves everything and closes server\n"); - printf("===================================================\n"); - return; - } - if( split[0].compare( "stop" ) == 0 || split[0].compare( "restart" ) == 0 ) - { - return; - } - if( split[0].compare( "save-all" ) == 0 ) - { - cRoot::Get()->SaveAllChunks(); // TODO - Force ALL worlds to save their chunks - return; - } - if (split[0].compare("unload") == 0) - { - LOG("Num loaded chunks before: %i", cRoot::Get()->GetTotalChunkCount() ); - cRoot::Get()->GetDefaultWorld()->UnloadUnusedChunks(); // TODO: Iterate through ALL worlds - LOG("Num loaded chunks after: %i", cRoot::Get()->GetTotalChunkCount() ); - return; - } - if( split[0].compare( "list" ) == 0 ) - { - class cPlayerLogger : public cPlayerListCallback - { - virtual bool Item(cPlayer * a_Player) override - { - LOG("\t%s @ %s", a_Player->GetName().c_str(), a_Player->GetClientHandle()->GetSocket().GetIPString().c_str()); - return false; - } - } Logger; - cRoot::Get()->ForEachPlayer(Logger); - return; - } - if( split[0].compare( "numchunks" ) == 0 ) - { - LOG("Num loaded chunks: %i", cRoot::Get()->GetTotalChunkCount() ); - return; - } - if (split[0].compare("chunkstats") == 0) - { - // TODO: For each world - int NumValid = 0; - int NumDirty = 0; - int NumInLighting = 0; - cWorld * World = cRoot::Get()->GetDefaultWorld(); - int NumInGenerator = World->GetGeneratorQueueLength(); - int NumInSaveQueue = World->GetStorageSaveQueueLength(); - int NumInLoadQueue = World->GetStorageLoadQueueLength(); - World->GetChunkStats(NumValid, NumDirty, NumInLighting); - LOG("Num loaded chunks: %d", NumValid); - LOG("Num dirty chunks: %d", NumDirty); - LOG("Num chunks in lighting queue: %d", NumInLighting); - LOG("Num chunks in generator queue: %d", NumInGenerator); - LOG("Num chunks in storage load queue: %d", NumInLoadQueue); - LOG("Num chunks in storage save queue: %d", NumInSaveQueue); - int Mem = NumValid * sizeof(cChunk); - LOG("Memory used by chunks: %d KiB (%d MiB)", (Mem + 1023) / 1024, (Mem + 1024 * 1024 - 1) / (1024 * 1024)); - LOG("Per-chunk memory size breakdown:"); - LOG(" block types: %6d bytes (%3d KiB)", sizeof(cChunkDef::BlockTypes), (sizeof(cChunkDef::BlockTypes) + 1023) / 1024); - LOG(" block metadata: %6d bytes (%3d KiB)", sizeof(cChunkDef::BlockNibbles), (sizeof(cChunkDef::BlockNibbles) + 1023) / 1024); - LOG(" block lighting: %6d bytes (%3d KiB)", 2 * sizeof(cChunkDef::BlockNibbles), (2 * sizeof(cChunkDef::BlockNibbles) + 1023) / 1024); - LOG(" heightmap: %6d bytes (%3d KiB)", sizeof(cChunkDef::HeightMap), (sizeof(cChunkDef::HeightMap) + 1023) / 1024); - LOG(" biomemap: %6d bytes (%3d KiB)", sizeof(cChunkDef::BiomeMap), (sizeof(cChunkDef::BiomeMap) + 1023) / 1024); - int Rest = sizeof(cChunk) - sizeof(cChunkDef::BlockTypes) - 3 * sizeof(cChunkDef::BlockNibbles) - sizeof(cChunkDef::HeightMap) - sizeof(cChunkDef::BiomeMap); - LOG(" other: %6d bytes (%3d KiB)", Rest, (Rest + 1023) / 1024); - return; - } - - if(split[0].compare("monsters") == 0 ) - { - // TODO: cWorld::ListMonsters(); - return; - } - - if(split.size() > 1) - { - if( split[0].compare( "say" ) == 0 ) - { - std::string Message = cChatColor::Purple + "[SERVER] " + Command.substr( Command.find_first_of("say") + 4 ); - LOG("%s", Message.c_str() ); - Broadcast( cPacket_Chat(Message) ); - return; - } - } - printf("Unknown command, type 'help' for all commands.\n"); - // LOG("You didn't enter anything? (%s)", a_Cmd.c_str() ); -} - - - - - -void cServer::SendMessage( const char* a_Message, cPlayer* a_Player /* = 0 */, bool a_bExclude /* = false */ ) -{ - cPacket_Chat Chat( a_Message ); - if( a_Player && !a_bExclude ) - { - cClientHandle* Client = a_Player->GetClientHandle(); - if( Client ) Client->Send( Chat ); - return; - } - - Broadcast( Chat, (a_Player)?a_Player->GetClientHandle():0 ); -} - - - - - -void cServer::Shutdown() -{ - m_bRestarting = true; - m_pState->RestartEvent.Wait(); - - cRoot::Get()->SaveAllChunks(); - - cCSLock Lock(m_CSClients); - for( ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr ) - { - delete *itr; - } - m_Clients.clear(); -} - - - - - -const AString & cServer::GetServerID(void) const -{ - return m_pState->ServerID; -} - - - - - -void cServer::KickUser(int a_ClientID, const AString & a_Reason) -{ - cCSLock Lock(m_CSClients); - for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - if ((*itr)->GetUniqueID() == a_ClientID) - { - (*itr)->Kick(a_Reason); - } - } // for itr - m_Clients[] -} - - - - - -void cServer::AuthenticateUser(int a_ClientID) -{ - cCSLock Lock(m_CSClients); - for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) - { - if ((*itr)->GetUniqueID() == a_ClientID) - { - (*itr)->Authenticate(); - } - } // for itr - m_Clients[] -} - - - - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// cServer::cClientPacketThread: - -cServer::cNotifyWriteThread::cNotifyWriteThread(void) : - super("ClientPacketThread"), - m_Server(NULL) -{ -} - - - - - -cServer::cNotifyWriteThread::~cNotifyWriteThread() -{ - m_ShouldTerminate = true; - m_Event.Set(); - Wait(); -} - - - - - -bool cServer::cNotifyWriteThread::Start(cServer * a_Server) -{ - m_Server = a_Server; - return super::Start(); -} - - - - - -void cServer::cNotifyWriteThread::Execute(void) -{ - cClientHandleList Clients; - while (!m_ShouldTerminate) - { - cCSLock Lock(m_CS); - while (m_Clients.size() == 0) - { - cCSUnlock Unlock(Lock); - m_Event.Wait(); - if (m_ShouldTerminate) - { - return; - } - } - - // Copy the clients to notify and unlock the CS: - Clients.splice(Clients.begin(), m_Clients); - Lock.Unlock(); - - for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr) - { - m_Server->m_SocketThreads.NotifyWrite(*itr); - } // for itr - Clients[] - Clients.clear(); - } // while (!mShouldTerminate) -} - - - - - -void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client) -{ - { - cCSLock Lock(m_CS); - m_Clients.remove(const_cast(a_Client)); // Put it there only once - m_Clients.push_back(const_cast(a_Client)); - } - m_Event.Set(); -} - - - - + +// ReDucTor is an awesome guy who helped me a lot + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "cServer.h" +#include "cClientHandle.h" +#include "cSleep.h" +#include "cTimer.h" +#include "cMonster.h" +#include "cSocket.h" +#include "cRoot.h" +#include "cWorld.h" +#include "ChunkDef.h" +#include "cPluginManager.h" +#include "cGroupManager.h" +#include "cChatColor.h" +#include "cPlayer.h" +#include "cInventory.h" +#include "cItem.h" +#include "cFurnaceRecipe.h" +#include "cTracer.h" +#include "cWebAdmin.h" +#include "cChunk.h" + +#include "MersenneTwister.h" + +#include "../iniFile/iniFile.h" +#include "Vector3f.h" + +#include "packets/cPacket_Chat.h" + +#include +#include +#include + + + + + +extern "C" { + #include "zlib.h" +} + + + +bool g_bWaterPhysics = false; + +typedef std::list< cClientHandle* > ClientList; + + + + + +struct cServer::sServerState +{ + sServerState() + : pListenThread( 0 ) + , pTickThread( 0 ) + , bStopListenThread( false ) + , bStopTickThread( false ) + {} + cSocket SListenClient; // socket listening for client calls + + cThread* pListenThread; bool bStopListenThread; + cThread* pTickThread; bool bStopTickThread; + + cEvent RestartEvent; + std::string ServerID; +}; + + + + + +cServer * cServer::GetServer() +{ + LOGWARN("WARNING: Using deprecated function cServer::GetServer() use cRoot::Get()->GetServer() instead!"); + return cRoot::Get()->GetServer(); +} + + + + + +void cServer::ServerListenThread( void *a_Args ) +{ + LOG("ServerListenThread"); + cServer* self = (cServer*)a_Args; + sServerState* m_pState = self->m_pState; + while( !m_pState->bStopListenThread ) + { + self->StartListenClient(); + } +} + + + + + +void cServer::ClientDestroying(const cClientHandle * a_Client) +{ + m_SocketThreads.StopReading(a_Client); +} + + + + + +void cServer::NotifyClientWrite(const cClientHandle * a_Client) +{ + m_NotifyWriteThread.NotifyClientWrite(a_Client); +} + + + + + +void cServer::WriteToClient(const cSocket * a_Socket, const AString & a_Data) +{ + m_SocketThreads.Write(a_Socket, a_Data); +} + + + + + +void cServer::QueueClientClose(const cSocket * a_Socket) +{ + m_SocketThreads.QueueClose(a_Socket); +} + + + + + +void cServer::RemoveClient(const cSocket * a_Socket) +{ + m_SocketThreads.RemoveClient(a_Socket); +} + + + + + +bool cServer::InitServer( int a_Port ) +{ + if( m_bIsConnected ) + { + LOGERROR("ERROR: Trying to initialize server while server is already running!"); + return false; + } + + printf("/============================\\\n"); + printf("| Custom Minecraft Server |\n"); + printf("| Created by Kevin Bansberg |\n"); + printf("| A.K.A. FakeTruth |\n"); + printf("| Monsters by Alex Sonek |\n"); + printf("| A.K.A. Duralex |\n"); + printf("| Stuff by Mattes D |\n"); + printf("| A.K.A. _Xoft(o) |\n"); + printf("\\============================/\n"); + printf("More info: WWW.MC-SERVER.ORG\n"); + printf(" WWW.AE-C.NET\n"); + printf(" WWW.RBTHINKTANK.COM\n"); + printf("email: faketruth@gmail.com\n\n"); + + LOG("Starting up server."); + LOGINFO("Compatible clients: %s, protocol version %d", MCS_CLIENT_VERSION, MCS_PROTOCOL_VERSION); + + if( cSocket::WSAStartup() != 0 ) // Only does anything on Windows, but whatever + { + LOGERROR("WSAStartup() != 0"); + return false; + } + + m_pState->SListenClient = cSocket::CreateSocket(); + + if( !m_pState->SListenClient.IsValid() ) + { + LOGERROR("m_SListenClient==INVALID_SOCKET (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); + return false; + } + + if( m_pState->SListenClient.SetReuseAddress() == -1 ) + { + LOGERROR("setsockopt == -1"); + return false; + } + + cSocket::SockAddr_In local; + local.Family = cSocket::ADDRESS_FAMILY_INTERNET; + local.Address = cSocket::INTERNET_ADDRESS_ANY; + local.Port = (unsigned short)a_Port; // 25565 + + if( m_pState->SListenClient.Bind( local ) != 0 ) + { + LOGERROR("bind fail (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); + return false; + } + + if( m_pState->SListenClient.Listen( 10 ) != 0) + { + LOGERROR("listen fail (%s)", cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); + return false; + } + + m_iServerPort = a_Port; + LOG("Port %i has been bound, server is open for connections", m_iServerPort); + m_bIsConnected = true; + + cIniFile IniFile("settings.ini"); + if (IniFile.ReadFile()) + { + g_bWaterPhysics = IniFile.GetValueB("Physics", "Water", false ); + + m_pState->ServerID = "-"; + if (IniFile.GetValueB("Authentication", "Authenticate")) + { + MTRand mtrand1; + 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_ClientViewDistance = IniFile.GetValueSetI("Server", "DefaultViewDistance", cClientHandle::DEFAULT_VIEW_DISTANCE); + if (m_ClientViewDistance < cClientHandle::MIN_VIEW_DISTANCE) + { + m_ClientViewDistance = cClientHandle::MIN_VIEW_DISTANCE; + LOGINFO("Setting default viewdistance to the minimum of %d", m_ClientViewDistance); + } + if (m_ClientViewDistance > cClientHandle::MAX_VIEW_DISTANCE) + { + m_ClientViewDistance = cClientHandle::MAX_VIEW_DISTANCE; + LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance); + } + IniFile.WriteFile(); + } + + m_NotifyWriteThread.Start(this); + + return true; +} + + + + + +cServer::cServer() + : m_pState( new sServerState ) + , m_Millisecondsf( 0 ) + , m_Milliseconds( 0 ) + , m_bIsConnected( false ) + , m_iServerPort( 0 ) + , m_bRestarting( false ) +{ +} + + + + + +cServer::~cServer() +{ + // TODO: Shut down the server gracefully + if ( m_pState->SListenClient ) + { + m_pState->SListenClient.CloseSocket(); + } + m_pState->SListenClient = 0; + + m_pState->bStopListenThread = true; + delete m_pState->pListenThread; m_pState->pListenThread = NULL; + m_pState->bStopTickThread = true; + delete m_pState->pTickThread; m_pState->pTickThread = NULL; + + delete m_pState; +} + + + + + +// TODO - Need to modify this or something, so it broadcasts to all worlds? And move this to cWorld? +void cServer::Broadcast( const cPacket & a_Packet, cClientHandle* a_Exclude /* = 0 */ ) +{ + cCSLock Lock(m_CSClients); + for( ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + { + if ((*itr == a_Exclude) || !(*itr)->IsLoggedIn()) + { + continue; + } + (*itr)->Send( a_Packet ); + } +} + + + + + +void cServer::StartListenClient() +{ + cSocket SClient = m_pState->SListenClient.Accept(); + + if (!SClient.IsValid()) + { + return; + } + + const AString & ClientIP = SClient.GetIPString(); + if (ClientIP.empty()) + { + LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting."); + SClient.CloseSocket(); + return; + } + + LOG("Client \"%s\" connected!", ClientIP.c_str()); + + cClientHandle *NewHandle = new cClientHandle(SClient, m_ClientViewDistance); + if (!m_SocketThreads.AddClient(&(NewHandle->GetSocket()), NewHandle)) + { + // For some reason SocketThreads have rejected the handle, clean it up + LOGERROR("Client \"%s\" cannot be handled, server probably unstable", SClient.GetIPString().c_str()); + SClient.CloseSocket(); + delete NewHandle; + return; + } + + cCSLock Lock(m_CSClients); + m_Clients.push_back( NewHandle ); +} + + + + + +bool cServer::Tick(float a_Dt) +{ + //LOG("1. Tick %0.2f", a_Dt); + if( a_Dt > 100.f ) a_Dt = 100.f; // Don't go over 1/10 second + + m_Millisecondsf += a_Dt; + if( m_Millisecondsf > 1.f ) + { + m_Milliseconds += (int)m_Millisecondsf; + m_Millisecondsf = m_Millisecondsf - (int)m_Millisecondsf; + } + + 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[] + } + for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr) + { + delete *itr; + } // for itr - RemoveClients[] + + cRoot::Get()->GetPluginManager()->Tick( a_Dt ); + + if( !m_bRestarting ) + { + return true; + } + else + { + m_bRestarting = false; + m_pState->RestartEvent.Set(); + return false; + } +} + + + + + +void ServerTickThread( void * a_Param ) +{ + 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 ) + { + long long NowTime = Timer.GetNowTime(); + float DeltaTime = (float)(NowTime-LastTime); + bKeepGoing = CServerObj->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; + } + + LOG("TICK THREAD STOPPED"); +} + + + + + +void cServer::StartListenThread() +{ + m_pState->pListenThread = new cThread( ServerListenThread, this, "cServer::ServerListenThread" ); + m_pState->pTickThread = new cThread( ServerTickThread, this, "cServer::ServerTickThread" ); + m_pState->pListenThread->Start( true ); + m_pState->pTickThread->Start( true ); +} + + + + + +template +bool from_string( + T& t, + const std::string& s, + std::ios_base& (*f)(std::ios_base&) +) +{ + std::istringstream iss(s); + return !(iss >> f >> t).fail(); +} + + + + + +bool cServer::Command( cClientHandle & a_Client, const char* a_Cmd ) +{ + cPluginManager* PM = cRoot::Get()->GetPluginManager(); + if( PM->CallHook( cPluginManager::E_PLUGIN_CHAT, 2, a_Cmd, a_Client.GetPlayer() ) ) + { + return true; + } + return false; +} + + + + + +void cServer::ServerCommand( const char * a_Cmd ) +{ + AString Command( a_Cmd ); + AStringVector split = StringSplit( Command, " " ); + if( split.empty()) + { + return; + } + + if( split[0].compare( "help" ) == 0 ) + { + printf("================== ALL COMMANDS ===================\n"); + printf("help - Shows this message\n"); + printf("save-all - Saves all loaded chunks to disk\n"); + printf("list - Lists all players currently in server\n"); + printf("unload - Unloads all unused chunks\n"); + printf("numchunks - Shows number of chunks currently loaded\n"); + printf("chunkstats - Shows chunks statistics\n"); + printf("say - Sends a chat message to all players\n"); + printf("restart - Kicks all clients, and saves everything\n"); + printf(" and clears memory\n"); + printf("stop - Saves everything and closes server\n"); + printf("===================================================\n"); + return; + } + if( split[0].compare( "stop" ) == 0 || split[0].compare( "restart" ) == 0 ) + { + return; + } + if( split[0].compare( "save-all" ) == 0 ) + { + cRoot::Get()->SaveAllChunks(); // TODO - Force ALL worlds to save their chunks + return; + } + if (split[0].compare("unload") == 0) + { + LOG("Num loaded chunks before: %i", cRoot::Get()->GetTotalChunkCount() ); + cRoot::Get()->GetDefaultWorld()->UnloadUnusedChunks(); // TODO: Iterate through ALL worlds + LOG("Num loaded chunks after: %i", cRoot::Get()->GetTotalChunkCount() ); + return; + } + if( split[0].compare( "list" ) == 0 ) + { + class cPlayerLogger : public cPlayerListCallback + { + virtual bool Item(cPlayer * a_Player) override + { + LOG("\t%s @ %s", a_Player->GetName().c_str(), a_Player->GetClientHandle()->GetSocket().GetIPString().c_str()); + return false; + } + } Logger; + cRoot::Get()->ForEachPlayer(Logger); + return; + } + if( split[0].compare( "numchunks" ) == 0 ) + { + LOG("Num loaded chunks: %i", cRoot::Get()->GetTotalChunkCount() ); + return; + } + if (split[0].compare("chunkstats") == 0) + { + // TODO: For each world + int NumValid = 0; + int NumDirty = 0; + int NumInLighting = 0; + cWorld * World = cRoot::Get()->GetDefaultWorld(); + int NumInGenerator = World->GetGeneratorQueueLength(); + int NumInSaveQueue = World->GetStorageSaveQueueLength(); + int NumInLoadQueue = World->GetStorageLoadQueueLength(); + World->GetChunkStats(NumValid, NumDirty, NumInLighting); + LOG("Num loaded chunks: %d", NumValid); + LOG("Num dirty chunks: %d", NumDirty); + LOG("Num chunks in lighting queue: %d", NumInLighting); + LOG("Num chunks in generator queue: %d", NumInGenerator); + LOG("Num chunks in storage load queue: %d", NumInLoadQueue); + LOG("Num chunks in storage save queue: %d", NumInSaveQueue); + int Mem = NumValid * sizeof(cChunk); + LOG("Memory used by chunks: %d KiB (%d MiB)", (Mem + 1023) / 1024, (Mem + 1024 * 1024 - 1) / (1024 * 1024)); + LOG("Per-chunk memory size breakdown:"); + LOG(" block types: %6d bytes (%3d KiB)", sizeof(cChunkDef::BlockTypes), (sizeof(cChunkDef::BlockTypes) + 1023) / 1024); + LOG(" block metadata: %6d bytes (%3d KiB)", sizeof(cChunkDef::BlockNibbles), (sizeof(cChunkDef::BlockNibbles) + 1023) / 1024); + LOG(" block lighting: %6d bytes (%3d KiB)", 2 * sizeof(cChunkDef::BlockNibbles), (2 * sizeof(cChunkDef::BlockNibbles) + 1023) / 1024); + LOG(" heightmap: %6d bytes (%3d KiB)", sizeof(cChunkDef::HeightMap), (sizeof(cChunkDef::HeightMap) + 1023) / 1024); + LOG(" biomemap: %6d bytes (%3d KiB)", sizeof(cChunkDef::BiomeMap), (sizeof(cChunkDef::BiomeMap) + 1023) / 1024); + int Rest = sizeof(cChunk) - sizeof(cChunkDef::BlockTypes) - 3 * sizeof(cChunkDef::BlockNibbles) - sizeof(cChunkDef::HeightMap) - sizeof(cChunkDef::BiomeMap); + LOG(" other: %6d bytes (%3d KiB)", Rest, (Rest + 1023) / 1024); + return; + } + + if(split[0].compare("monsters") == 0 ) + { + // TODO: cWorld::ListMonsters(); + return; + } + + if(split.size() > 1) + { + if( split[0].compare( "say" ) == 0 ) + { + std::string Message = cChatColor::Purple + "[SERVER] " + Command.substr( Command.find_first_of("say") + 4 ); + LOG("%s", Message.c_str() ); + Broadcast( cPacket_Chat(Message) ); + return; + } + } + printf("Unknown command, type 'help' for all commands.\n"); + // LOG("You didn't enter anything? (%s)", a_Cmd.c_str() ); +} + + + + + +void cServer::SendMessage( const char* a_Message, cPlayer* a_Player /* = 0 */, bool a_bExclude /* = false */ ) +{ + cPacket_Chat Chat( a_Message ); + if( a_Player && !a_bExclude ) + { + cClientHandle* Client = a_Player->GetClientHandle(); + if( Client ) Client->Send( Chat ); + return; + } + + Broadcast( Chat, (a_Player)?a_Player->GetClientHandle():0 ); +} + + + + + +void cServer::Shutdown() +{ + m_bRestarting = true; + m_pState->RestartEvent.Wait(); + + cRoot::Get()->SaveAllChunks(); + + cCSLock Lock(m_CSClients); + for( ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr ) + { + delete *itr; + } + m_Clients.clear(); +} + + + + + +const AString & cServer::GetServerID(void) const +{ + return m_pState->ServerID; +} + + + + + +void cServer::KickUser(int a_ClientID, const AString & a_Reason) +{ + cCSLock Lock(m_CSClients); + for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + { + if ((*itr)->GetUniqueID() == a_ClientID) + { + (*itr)->Kick(a_Reason); + } + } // for itr - m_Clients[] +} + + + + + +void cServer::AuthenticateUser(int a_ClientID) +{ + cCSLock Lock(m_CSClients); + for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr) + { + if ((*itr)->GetUniqueID() == a_ClientID) + { + (*itr)->Authenticate(); + } + } // for itr - m_Clients[] +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cServer::cClientPacketThread: + +cServer::cNotifyWriteThread::cNotifyWriteThread(void) : + super("ClientPacketThread"), + m_Server(NULL) +{ +} + + + + + +cServer::cNotifyWriteThread::~cNotifyWriteThread() +{ + m_ShouldTerminate = true; + m_Event.Set(); + Wait(); +} + + + + + +bool cServer::cNotifyWriteThread::Start(cServer * a_Server) +{ + m_Server = a_Server; + return super::Start(); +} + + + + + +void cServer::cNotifyWriteThread::Execute(void) +{ + cClientHandleList Clients; + while (!m_ShouldTerminate) + { + cCSLock Lock(m_CS); + while (m_Clients.size() == 0) + { + cCSUnlock Unlock(Lock); + m_Event.Wait(); + if (m_ShouldTerminate) + { + return; + } + } + + // Copy the clients to notify and unlock the CS: + Clients.splice(Clients.begin(), m_Clients); + Lock.Unlock(); + + for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr) + { + m_Server->m_SocketThreads.NotifyWrite(*itr); + } // for itr - Clients[] + Clients.clear(); + } // while (!mShouldTerminate) +} + + + + + +void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client) +{ + { + cCSLock Lock(m_CS); + m_Clients.remove(const_cast(a_Client)); // Put it there only once + m_Clients.push_back(const_cast(a_Client)); + } + m_Event.Set(); +} + + + + -- cgit v1.2.3