1
0
cuberite-2a/source/cServer.cpp
faketruth 738b1b3467 Improved Core's WebAmin interface a bit.
Can now kick players through WebAdmin
Can now enable/disable whitelist through WebAdmin
Tick speed is limited in a better way now, instead of always sleeping 50ms before each tick, it now add only sleeps additional time when the tick time was faster than 50ms. Server should run slightly faster because of this (and use more cpu%)


git-svn-id: http://mc-server.googlecode.com/svn/trunk@167 0a769ca7-a7f5-676a-18bf-c427514a06d6
2012-01-22 20:15:11 +00:00

535 lines
14 KiB
C++

// ReDucTor is an awesome guy who helped me a lot
#include "cServer.h"
#include "cClientHandle.h"
#include "cMCLogger.h"
#include "cThread.h"
#include "cEvent.h"
#include "cSleep.h"
#include "cTimer.h"
#include "cMonster.h"
#include "cSocket.h"
#include "cRoot.h"
#include "cWorld.h"
#include "cChunk.h"
#include "cPluginManager.h"
#include "cGroupManager.h"
#include "cChatColor.h"
#include "cPlayer.h"
#include "cInventory.h"
#include "cItem.h"
#include "cRecipeChecker.h"
#include "cFurnaceRecipe.h"
#include "cTracer.h"
#include "cWebAdmin.h"
#include "MersenneTwister.h"
#include "../iniFile/iniFile.h"
#include "Vector3f.h"
#include "packets/cPacket_Chat.h"
#ifndef _WIN32
#define sprintf_s(dst, size, format, ...) sprintf(dst, format, __VA_ARGS__ )
#endif
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
extern "C" {
#include "zlib.h"
}
#ifdef SendMessage
#undef SendMessage
#endif
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;
ClientList Clients;
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();
}
}
std::string GetWSAError()
{
#ifdef _WIN32
switch( WSAGetLastError() )
{
case WSANOTINITIALISED:
return "WSANOTINITIALISED";
case WSAENETDOWN:
return "WSAENETDOWN";
case WSAEFAULT:
return "WSAEFAULT";
case WSAENOTCONN:
return "WSAENOTCONN";
case WSAEINTR:
return "WSAEINTR";
case WSAEINPROGRESS:
return "WSAEINPROGRESS";
case WSAENETRESET:
return "WSAENETRESET";
case WSAENOTSOCK:
return "WSAENOTSOCK";
case WSAEOPNOTSUPP:
return "WSAEOPNOTSUPP";
case WSAESHUTDOWN:
return "WSAESHUTDOWN";
case WSAEWOULDBLOCK:
return "WSAEWOULDBLOCK";
case WSAEMSGSIZE:
return "WSAEMSGSIZE";
case WSAEINVAL:
return "WSAEINVAL";
case WSAECONNABORTED:
return "WSAECONNABORTED";
case WSAETIMEDOUT:
return "WSAETIMEDOUT";
case WSAECONNRESET:
return "WSAECONNRESET";
}
#endif
return "No Error";
}
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("| Minecraft Alpha Server |\n");
printf("| Created by Kevin Bansberg |\n");
printf("| A.K.A. |\n");
printf("| FakeTruth |\n");
printf("| Monsters by Alex Sonek |\n");
printf("| A.K.A. Duralex |\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.");
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::GetLastErrorString() );
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::GetLastErrorString() );
return false;
}
if( m_pState->SListenClient.Listen( 10 ) != 0)
{
LOGERROR("listen fail (%s)", cSocket::GetLastErrorString() );
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 );
/* Replaced below with 1.0.0 compatible ServerID generation
std::string ServerID = IniFile.GetValue("Server", "ServerID");
if( ServerID.empty() )
{
ServerID = "MCServer";
IniFile.SetValue("Server", "ServerID", ServerID, true );
IniFile.WriteFile();
}
*/
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;
}
}
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()
{
if( m_pState->SListenClient ) m_pState->SListenClient.CloseSocket();
m_pState->SListenClient = 0;
m_pState->bStopListenThread = true;
delete m_pState->pListenThread; m_pState->pListenThread = 0;
m_pState->bStopTickThread = true;
delete m_pState->pTickThread; m_pState->pTickThread = 0;
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 */ )
{
for( ClientList::iterator itr = m_pState->Clients.begin(); itr != m_pState->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() )
{
char * ClientIP = SClient.GetIPString();
if( ClientIP == 0 )
return;
LOG("%s connected!", ClientIP);
cClientHandle *NewHandle = new cClientHandle( SClient );
m_pState->Clients.push_back( NewHandle ); // TODO - lock list
}
}
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?
//World->LockClientHandle(); // TODO - Lock client list
for( ClientList::iterator itr = m_pState->Clients.begin(); itr != m_pState->Clients.end();)
{
(*itr)->HandlePendingPackets();
if( (*itr)->IsDestroyed() )
{
cClientHandle* RemoveMe = *itr;
++itr;
m_pState->Clients.remove( RemoveMe );
delete RemoveMe;
continue;
}
(*itr)->Tick(a_Dt);
++itr;
}
//World->UnlockClientHandle();
cRoot::Get()->GetPluginManager()->Tick( a_Dt );
if( !m_bRestarting )
return true;
else
{
m_bRestarting = false;
m_pState->RestartEvent.Set();
LOG("<<<<>>>>SIGNALLED SEMAPHORE");
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 );
}
std::vector< std::string > StringSplit(std::string str, std::string delim)
{
std::vector< std::string > results;
size_t cutAt;
while( (cutAt = str.find_first_of(delim)) != str.npos )
{
if(cutAt > 0)
{
results.push_back(str.substr(0,cutAt));
}
str = str.substr(cutAt+1);
}
if(str.length() > 0)
{
results.push_back(str);
}
return results;
}
template <class T>
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();
}
std::string & StrToUpper(std::string& s)
{
std::string::iterator i = s.begin();
std::string::iterator end = s.end();
while (i != end) {
*i = (char)toupper(*i);
++i;
}
return s;
}
int NoCaseCompare( std::string s1, std::string s2 )
{
return StrToUpper( s1 ).compare( StrToUpper( s2 ) );
}
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;
std::string Command( a_Cmd );
if( Command.length() <= 0 ) return false;
if( Command[0] != '/' ) return false;
std::vector< std::string > split = StringSplit( Command, " " );
if( split.size() == 0 )
return false;
if( split[0].compare("/coords") == 0 )
{
char c_Str[128];
sprintf_s( c_Str, 128, "[X:%0.2f] [Y:%0.2f] [Z:%0.2f]", a_Client.GetPlayer()->GetPosX(), a_Client.GetPlayer()->GetPosY(), a_Client.GetPlayer()->GetPosZ() );
a_Client.Send( cPacket_Chat( cChatColor::Green + c_Str ) );
return true;
}
return false;
}
void cServer::ServerCommand( const char* a_Cmd )
{
std::string Command( a_Cmd );
std::vector< std::string > split = StringSplit( Command, " " );
if( split.size() > 0 )
{
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("numchunks - Shows number of chunks currently loaded\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()->GetWorld()->SaveAllChunks(); // TODO - Force ALL worlds to save their chunks
return;
}
if( split[0].compare( "list" ) == 0 )
{
cWorld::EntityList Entities = cRoot::Get()->GetWorld()->GetEntities();
std::string PlayerString;
int NumPlayers = 0;
cRoot::Get()->GetWorld()->LockEntities();
for( cWorld::EntityList::iterator itr = Entities.begin(); itr != Entities.end(); ++itr)
{
if( (*itr)->GetEntityType() != cEntity::E_PLAYER ) continue;
PlayerString.push_back(' ');
PlayerString += ((cPlayer*)*itr)->GetName();
NumPlayers++;
}
cRoot::Get()->GetWorld()->UnlockEntities();
printf( "Players (%i):%s\n", NumPlayers, PlayerString.c_str() );
return;
}
if( split[0].compare( "numchunks" ) == 0 )
{
printf("Num loaded chunks: %i\n", cRoot::Get()->GetWorld()->GetNumChunks() );
return;
}
if(split[0].compare("monsters") == 0 ){
cMonster::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()->GetWorld()->SaveAllChunks();
//cWorld* World = cRoot::Get()->GetWorld();
//World->LockClientHandle(); // TODO - Lock client list
for( ClientList::iterator itr = m_pState->Clients.begin(); itr != m_pState->Clients.end(); ++itr )
{
delete *itr;
}
m_pState->Clients.clear();
//World->UnlockClientHandle();
}
const char* cServer::GetServerID()
{
return m_pState->ServerID.c_str();
}