about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsavander <savander.pl@gmail.com>2014-09-27 00:34:35 +0200
committersavander <savander.pl@gmail.com>2014-09-27 00:34:35 +0200
commit2a54f759145f86fce4ad2a333de6d9cd911d8861 (patch)
tree8509c538c26ddae9c493fbaed33a8380508d7caf
parent8c329ade7507d3f1641018f302bf2396af6a471a (diff)
downloadzcatch-2a54f759145f86fce4ad2a333de6d9cd911d8861.tar.gz
zcatch-2a54f759145f86fce4ad2a333de6d9cd911d8861.zip
zCatch:Ranking
-rw-r--r--src/engine/shared/config_variables.h10
-rw-r--r--src/game/server/entities/ranking.cpp273
-rw-r--r--src/game/server/gamecontext.cpp3
-rw-r--r--src/game/server/gamecontext.h8
-rw-r--r--src/game/server/gamemodes/zcatch.cpp20
-rw-r--r--src/game/server/gamemodes/zcatch.h3
-rw-r--r--src/game/server/player.h1
-rw-r--r--src/game/server/ranking.cpp271
-rw-r--r--src/game/server/ranking.h70
9 files changed, 656 insertions, 3 deletions
diff --git a/src/engine/shared/config_variables.h b/src/engine/shared/config_variables.h
index 0d3b197c..b766b6c9 100644
--- a/src/engine/shared/config_variables.h
+++ b/src/engine/shared/config_variables.h
@@ -105,6 +105,16 @@ MACRO_CONFIG_INT(EcOutputLevel, ec_output_level, 1, 0, 2, CFGFLAG_ECON, "Adjusts
 
 MACRO_CONFIG_INT(SvGlobalBantime, sv_global_bantime, 60, 0, 1440, CFGFLAG_SERVER, "The time a client gets banned if the ban server reports it. 0 to disable")
 
+
+#if defined(CONF_SQL)
+//zCatch mysql.
+MACRO_CONFIG_STR(SvSqlUser, sv_sql_user, 32, "nameless", CFGFLAG_SERVER, "SQL User")
+MACRO_CONFIG_STR(SvSqlPass, sv_sql_pass, 32, "tee", CFGFLAG_SERVER, "SQL Password")
+MACRO_CONFIG_STR(SvSqlIp, sv_sql_ip, 32, "127.0.0.1", CFGFLAG_SERVER, "SQL Database IP")
+MACRO_CONFIG_INT(SvSqlPort, sv_sql_port, 3306, 0, 65535, CFGFLAG_SERVER, "SQL Database port")
+MACRO_CONFIG_STR(SvSqlDatabase, sv_sql_database, 16, "teeworlds", CFGFLAG_SERVER, "SQL Database name")
+#endif
+
 MACRO_CONFIG_INT(Debug, debug, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Debug mode")
 MACRO_CONFIG_INT(DbgStress, dbg_stress, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress systems")
 MACRO_CONFIG_INT(DbgStressNetwork, dbg_stress_network, 0, 0, 0, CFGFLAG_CLIENT|CFGFLAG_SERVER, "Stress network")
diff --git a/src/game/server/entities/ranking.cpp b/src/game/server/entities/ranking.cpp
new file mode 100644
index 00000000..910157b8
--- /dev/null
+++ b/src/game/server/entities/ranking.cpp
@@ -0,0 +1,273 @@
+#if defined(CONF_SQL)
+#include <string.h>
+#include <algorithm>
+
+#include "ranking.h"
+#include <engine/shared/config.h>
+
+static LOCK gs_SqlLock = 0;
+
+CRanking::CRanking(CGameContext *pGameServer) : m_pGameServer(pGameServer),
+		m_pServer(pGameServer->Server()),
+		m_pDatabase(g_Config.m_SvSqlDatabase),
+		m_pUser(g_Config.m_SvSqlUser),
+		m_pPass(g_Config.m_SvSqlPass),
+		m_pIp(g_Config.m_SvSqlIp),
+		m_Port(g_Config.m_SvSqlPort)
+{
+	m_pDriver = NULL;
+
+	if(gs_SqlLock == 0)
+		gs_SqlLock = lock_create();
+
+	Init();
+}
+
+CRanking::~CRanking()
+{
+	lock_wait(gs_SqlLock);
+	lock_release(gs_SqlLock);
+
+	try
+	{
+		delete m_pStatement;
+		delete m_pConnection;
+		dbg_msg("SQL", "SQL connection disconnected");
+	}
+	catch (sql::SQLException &e)
+	{
+		dbg_msg("SQL", "ERROR: No SQL connection");
+	}
+}
+void CRanking::Init()
+{
+	// create connection
+	if(Connect())
+	{
+		try
+		{
+			// create tables
+			char aBuf[1024];
+
+			str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS zcatch_ranks (Name VARCHAR(%d) BINARY NOT NULL, Wins INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8 ;", MAX_NAME_LENGTH);
+			m_pStatement->execute(aBuf);
+
+			dbg_msg("SQL", "Ranking table were created successfully");
+			// delete statement
+			delete m_pResults;
+		}
+		catch (sql::SQLException &e)
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+			dbg_msg("SQL", aBuf);
+			dbg_msg("SQL", "ERROR: Tables were NOT created");
+		}
+
+		// disconnect from database
+		Disconnect();
+	}
+}
+
+bool CRanking::Connect()
+{
+	if (m_pDriver != NULL && m_pConnection != NULL)
+	{
+		try
+		{
+			// Connect to specific database
+			m_pConnection->setSchema(m_pDatabase);
+		}
+		catch (sql::SQLException &e)
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+			dbg_msg("SQL", aBuf);
+
+			dbg_msg("SQL", "ERROR: SQL connection failed");
+			return false;
+		}
+		return true;
+	}
+
+	try
+	{
+		char aBuf[256];
+
+		sql::ConnectOptionsMap connection_properties;
+		connection_properties["hostName"]      = sql::SQLString(m_pIp);
+		connection_properties["port"]          = m_Port;
+		connection_properties["userName"]      = sql::SQLString(m_pUser);
+		connection_properties["password"]      = sql::SQLString(m_pPass);
+		connection_properties["OPT_RECONNECT"] = true;
+
+		// Create connection
+		m_pDriver = get_driver_instance();
+		m_pConnection = m_pDriver->connect(connection_properties);
+
+		// Create Statement
+		m_pStatement = m_pConnection->createStatement();
+
+		// Create database if not exists
+		str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s", m_pDatabase);
+		m_pStatement->execute(aBuf);
+
+		// Connect to specific database
+		m_pConnection->setSchema(m_pDatabase);
+		dbg_msg("SQL", "SQL connection established");
+		return true;
+	}
+	catch (sql::SQLException &e)
+	{
+		char aBuf[256];
+		str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+		dbg_msg("SQL", aBuf);
+
+		dbg_msg("SQL", "ERROR: SQL connection failed");
+		return false;
+	}
+	catch (const std::exception& ex)
+	{
+		// ...
+		dbg_msg("SQL", "1 %s",ex.what());
+
+	}
+	catch (const std::string& ex)
+	{
+		// ...
+		dbg_msg("SQL", "2 %s",ex.c_str());
+	}
+	catch( int )
+	{
+		dbg_msg("SQL", "3 %s");
+	}
+	catch( float )
+	{
+		dbg_msg("SQL", "4 %s");
+	}
+
+	catch( char[] )
+	{
+		dbg_msg("SQL", "5 %s");
+	}
+
+	catch( char )
+	{
+		dbg_msg("SQL", "6 %s");
+	}
+	catch (...)
+	{
+		dbg_msg("SQL", "Unknown Error cause by the MySQL/C++ Connector, my advice compile server_debug and use it");
+
+		dbg_msg("SQL", "ERROR: SQL connection failed");
+		return false;
+	}
+	return false;
+}
+void CRanking::Disconnect()
+{
+}
+
+void CRanking::SaveRanking(int ClientID){
+
+	CSqlRankData *Tmp = new CSqlRankData();
+	Tmp->m_ClientID = ClientID;
+	str_copy(Tmp->m_aName, Server()->ClientName(ClientID), MAX_NAME_LENGTH);
+	Tmp->m_pSqlData = this;
+
+	void *SaveRanking = thread_create(SaveRankingThread, Tmp);
+#if defined(CONF_FAMILY_UNIX)
+	pthread_detach((pthread_t)SaveRanking);
+#endif
+}
+void CRanking::SaveRankingThread(void *pUser){
+
+	lock_wait(gs_SqlLock);
+	CSqlRankData *pData = (CSqlRankData *)pUser;
+
+	// Connect to database
+	if(pData->m_pSqlData->Connect())
+	{
+		try
+		{
+			char aBuf[768];
+
+			// check strings
+			pData->m_pSqlData->ClearString(pData->m_aName);
+
+			str_format(aBuf, sizeof(aBuf), "SELECT * FROM zcatch_ranks WHERE Name='%s' ORDER BY Wins ASC LIMIT 1;", pData->m_aName);
+			pData->m_pSqlData->m_pResults = pData->m_pSqlData->m_pStatement->executeQuery(aBuf);
+
+			if(pData->m_pSqlData->m_pResults->next())
+			{
+				str_format(aBuf, sizeof(aBuf), "SELECT Wins FROM zctach_ranks WHERE Name ='%s'",pData->m_aName);
+				pData->m_pSqlData->m_pResults = pData->m_pSqlData->m_pStatement->executeQuery(aBuf);
+
+				if(pData->m_pSqlData->m_pResults->rowsCount() == 1){
+					pData->m_pSqlData->m_pResults->next();
+
+					pData->m_pSqlData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
+					str_format(aBuf, sizeof(aBuf), "INSERT INTO zcatch_ranks(Name, Wins) VALUES ('%s') ON duplicate key UPDATE Name=VALUES(Name), Wins=Wins+1;", pData->m_aName);
+					pData->m_pSqlData->m_pStatement->execute(aBuf);
+				}
+			}
+
+			pData->m_pSqlData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
+			delete pData->m_pSqlData->m_pResults;
+
+			// if no entry found... create a new one
+			str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_race(Name, Wins2 VALUES ('%s', '1');",pData->m_aName);
+			pData->m_pSqlData->m_pStatement->execute(aBuf);
+
+			dbg_msg("SQL", "Updating rank done");
+		}
+		catch (sql::SQLException &e)
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+			dbg_msg("SQL", aBuf);
+			dbg_msg("SQL", "ERROR: Could not update rank");
+		}
+		pData->m_pSqlData->Disconnect();
+	}
+
+	delete pData;
+	lock_release(gs_SqlLock);
+}
+
+
+// anti SQL injection
+void CRanking::ClearString(char *pString, int size)
+{
+	char newString[size*2-1];
+	int pos = 0;
+
+	for(int i=0;i<size;i++)
+	{
+		if(pString[i] == '\\')
+		{
+			newString[pos++] = '\\';
+			newString[pos++] = '\\';
+		}
+		else if(pString[i] == '\'')
+		{
+			newString[pos++] = '\\';
+			newString[pos++] = '\'';
+		}
+		else if(pString[i] == '"')
+		{
+			newString[pos++] = '\\';
+			newString[pos++] = '"';
+		}
+		else
+		{
+			newString[pos++] = pString[i];
+		}
+	}
+
+	newString[pos] = '\0';
+
+	strcpy(pString,newString);
+}
+#endif
+
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index 27d17c11..0b618d2d 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -2065,6 +2065,9 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
 		m_pController = new CGameControllerDM(this);*/
 	m_pController = new CGameController_zCatch(this);
 
+#if defined(CONF_SQL)
+		m_Ranking = new CRanking(this);
+#endif
 	// setup core world
 	//for(int i = 0; i < MAX_CLIENTS; i++)
 	//	game.players[i].core.world = &game.world.core;
diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h
index 71f6308a..41d6d899 100644
--- a/src/game/server/gamecontext.h
+++ b/src/game/server/gamecontext.h
@@ -10,13 +10,16 @@
 #include <game/layers.h>
 #include <game/voting.h>
 
+
 #include "eventhandler.h"
 #include "gamecontroller.h"
 #include "gameworld.h"
+#include "ranking.h"
 #include "player.h"
 
+
 #define MAX_MUTES 35
-#define ZCATCH_VERSION "0.4.8 BETA"
+#define ZCATCH_VERSION "0.4.9 BETA"
 
 /*
 	Tick
@@ -48,6 +51,7 @@ class CGameContext : public IGameServer
 	CNetObjHandler m_NetObjHandler;
 	CTuningParams m_Tuning;
 
+
 	static void ConTuneParam(IConsole::IResult *pResult, void *pUserData);
 	static void ConTuneReset(IConsole::IResult *pResult, void *pUserData);
 	static void ConTuneDump(IConsole::IResult *pResult, void *pUserData);
@@ -95,6 +99,8 @@ public:
 	CEventHandler m_Events;
 	CPlayer *m_apPlayers[MAX_CLIENTS];
 
+    class CRanking *m_Ranking;
+
 	IGameController *m_pController;
 	CGameWorld m_World;
 
diff --git a/src/game/server/gamemodes/zcatch.cpp b/src/game/server/gamemodes/zcatch.cpp
index cd36d12f..6034d78a 100644
--- a/src/game/server/gamemodes/zcatch.cpp
+++ b/src/game/server/gamemodes/zcatch.cpp
@@ -248,6 +248,21 @@ void CGameController_zCatch::OnCharacterSpawn(class CCharacter *pChr)
 
 void CGameController_zCatch::EndRound()
 {
+
+	//CheckWinner
+	int m_WinnerClientID = 0;
+	for(int i = 0; i < MAX_CLIENTS; i++)
+	{
+			if(GameServer()->m_apPlayers[i])
+			{
+				if(GameServer()->m_apPlayers[i]->m_zCatchNumVictims)
+					m_WinnerClientID = GameServer()->m_apPlayers[i]->GetCID();
+			}
+	}
+
+#if defined(CONF_SQL)
+	m_Ranking->SaveRanking(m_WinnerClientID);
+#endif
 	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
 		if(GameServer()->m_apPlayers[i])
@@ -258,9 +273,13 @@ void CGameController_zCatch::EndRound()
 				GameServer()->m_apPlayers[i]->SetTeamDirect(GameServer()->m_pController->ClampTeam(1));
 
 				char aBuf[128];
+				str_format(aBuf, sizeof(aBuf), "Winner  --  %s", Server()->ClientName(m_WinnerClientID));
+				GameServer()->SendChatTarget(i, aBuf);
 				str_format(aBuf, sizeof(aBuf), "Kills: %d | Deaths: %d", GameServer()->m_apPlayers[i]->m_Kills, GameServer()->m_apPlayers[i]->m_Deaths);
 				GameServer()->SendChatTarget(i, aBuf);
 
+
+
 				if(GameServer()->m_apPlayers[i]->m_TicksSpec != 0 || GameServer()->m_apPlayers[i]->m_TicksIngame != 0)
 				{
 					double TimeInSpec = (GameServer()->m_apPlayers[i]->m_TicksSpec * 100.0) / (GameServer()->m_apPlayers[i]->m_TicksIngame + GameServer()->m_apPlayers[i]->m_TicksSpec);
@@ -280,6 +299,7 @@ void CGameController_zCatch::EndRound()
 	GameServer()->m_World.m_Paused = true;
 	m_GameOverTick = Server()->Tick();
 	m_SuddenDeath = 0;
+
 }
 
 bool CGameController_zCatch::CanChangeTeam(CPlayer *pPlayer, int JoinTeam)
diff --git a/src/game/server/gamemodes/zcatch.h b/src/game/server/gamemodes/zcatch.h
index 4f110a03..9602c75c 100644
--- a/src/game/server/gamemodes/zcatch.h
+++ b/src/game/server/gamemodes/zcatch.h
@@ -6,11 +6,12 @@
 #define GAME_SERVER_GAMEMODES_ZCATCH_H
 
 #include <game/server/gamecontroller.h>
+#include "../ranking.h"
 
 class CGameController_zCatch: public IGameController
 {
 	int m_OldMode;
-
+	CRanking *m_Ranking;
 public:
 	CGameController_zCatch(class CGameContext *pGameServer);
 	virtual void Tick();
diff --git a/src/game/server/player.h b/src/game/server/player.h
index 7189d7e2..416dccd7 100644
--- a/src/game/server/player.h
+++ b/src/game/server/player.h
@@ -6,7 +6,6 @@
 // this include should perhaps be removed
 #include "entities/character.h"
 #include "gamecontext.h"
-
 // player object
 class CPlayer
 {
diff --git a/src/game/server/ranking.cpp b/src/game/server/ranking.cpp
new file mode 100644
index 00000000..1e990e34
--- /dev/null
+++ b/src/game/server/ranking.cpp
@@ -0,0 +1,271 @@
+#if defined(CONF_SQL)
+#include <string.h>
+#include <algorithm>
+
+#include "ranking.h"
+#include <engine/shared/config.h>
+
+static LOCK gs_SqlLock = 0;
+
+CRanking::CRanking(CGameContext *pGameServer) : m_pGameServer(pGameServer),
+		m_pServer(pGameServer->Server()),
+		m_pDatabase(g_Config.m_SvSqlDatabase),
+		m_pUser(g_Config.m_SvSqlUser),
+		m_pPass(g_Config.m_SvSqlPass),
+		m_pIp(g_Config.m_SvSqlIp),
+		m_Port(g_Config.m_SvSqlPort)
+{
+	m_pDriver = NULL;
+
+	if(gs_SqlLock == 0)
+		gs_SqlLock = lock_create();
+
+	Init();
+}
+
+CRanking::~CRanking()
+{
+	lock_wait(gs_SqlLock);
+	lock_release(gs_SqlLock);
+
+	try
+	{
+		delete m_pStatement;
+		delete m_pConnection;
+		dbg_msg("SQL", "SQL connection disconnected");
+	}
+	catch (sql::SQLException &e)
+	{
+		dbg_msg("SQL", "ERROR: No SQL connection");
+	}
+}
+void CRanking::Init()
+{
+	// create connection
+	if(Connect())
+	{
+		try
+		{
+			// create tables
+			char aBuf[1024];
+
+			str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS zcatch_ranks (Name VARCHAR(%d) BINARY NOT NULL, Wins INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8 ;", MAX_NAME_LENGTH);
+			m_pStatement->execute(aBuf);
+
+			dbg_msg("SQL", "Ranking table were created successfully");
+		}
+		catch (sql::SQLException &e)
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+			dbg_msg("SQL", aBuf);
+			dbg_msg("SQL", "ERROR: Tables were NOT created");
+		}
+
+		// disconnect from database
+		Disconnect();
+	}
+}
+
+bool CRanking::Connect()
+{
+	if (m_pDriver != NULL && m_pConnection != NULL)
+	{
+		try
+		{
+			// Connect to specific database
+			m_pConnection->setSchema(m_pDatabase);
+		}
+		catch (sql::SQLException &e)
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+			dbg_msg("SQL", aBuf);
+
+			dbg_msg("SQL", "ERROR: SQL connection failed");
+			return false;
+		}
+		return true;
+	}
+
+	try
+	{
+		char aBuf[256];
+
+		sql::ConnectOptionsMap connection_properties;
+		connection_properties["hostName"]      = sql::SQLString(m_pIp);
+		connection_properties["port"]          = m_Port;
+		connection_properties["userName"]      = sql::SQLString(m_pUser);
+		connection_properties["password"]      = sql::SQLString(m_pPass);
+		connection_properties["OPT_RECONNECT"] = true;
+
+		// Create connection
+		m_pDriver = get_driver_instance();
+		m_pConnection = m_pDriver->connect(connection_properties);
+
+		// Create Statement
+		m_pStatement = m_pConnection->createStatement();
+
+		// Create database if not exists
+		str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s", m_pDatabase);
+		m_pStatement->execute(aBuf);
+
+		// Connect to specific database
+		m_pConnection->setSchema(m_pDatabase);
+		dbg_msg("SQL", "SQL connection established");
+		return true;
+	}
+	catch (sql::SQLException &e)
+	{
+		char aBuf[256];
+		str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+		dbg_msg("SQL", aBuf);
+
+		dbg_msg("SQL", "ERROR: SQL connection failed");
+		return false;
+	}
+	catch (const std::exception& ex)
+	{
+		// ...
+		dbg_msg("SQL", "1 %s",ex.what());
+
+	}
+	catch (const std::string& ex)
+	{
+		// ...
+		dbg_msg("SQL", "2 %s",ex.c_str());
+	}
+	catch( int )
+	{
+		dbg_msg("SQL", "3 %s");
+	}
+	catch( float )
+	{
+		dbg_msg("SQL", "4 %s");
+	}
+
+	catch( char[] )
+	{
+		dbg_msg("SQL", "5 %s");
+	}
+
+	catch( char )
+	{
+		dbg_msg("SQL", "6 %s");
+	}
+	catch (...)
+	{
+		dbg_msg("SQL", "Unknown Error cause by the MySQL/C++ Connector, my advice compile server_debug and use it");
+
+		dbg_msg("SQL", "ERROR: SQL connection failed");
+		return false;
+	}
+	return false;
+}
+void CRanking::Disconnect()
+{
+}
+
+void CRanking::SaveRanking(int ClientID){
+
+	CSqlRankData *Tmp = new CSqlRankData();
+	Tmp->m_ClientID = ClientID;
+	str_copy(Tmp->m_aName, Server()->ClientName(ClientID), MAX_NAME_LENGTH);
+	Tmp->m_pSqlData = this;
+
+	void *SaveRanking = thread_create(SaveRankingThread, Tmp);
+#if defined(CONF_FAMILY_UNIX)
+	pthread_detach((pthread_t)SaveRanking);
+#endif
+}
+void CRanking::SaveRankingThread(void *pUser){
+
+	lock_wait(gs_SqlLock);
+	CSqlRankData *pData = (CSqlRankData *)pUser;
+
+	// Connect to database
+	if(pData->m_pSqlData->Connect())
+	{
+		try
+		{
+			char aBuf[768];
+
+			// check strings
+			pData->m_pSqlData->ClearString(pData->m_aName);
+
+			str_format(aBuf, sizeof(aBuf), "SELECT * FROM zcatch_ranks WHERE Name='%s' ORDER BY Wins ASC LIMIT 1;", pData->m_aName);
+			pData->m_pSqlData->m_pResults = pData->m_pSqlData->m_pStatement->executeQuery(aBuf);
+
+			if(pData->m_pSqlData->m_pResults->next())
+			{
+				str_format(aBuf, sizeof(aBuf), "SELECT Wins FROM zctach_ranks WHERE Name ='%s'",pData->m_aName);
+				pData->m_pSqlData->m_pResults = pData->m_pSqlData->m_pStatement->executeQuery(aBuf);
+
+				if(pData->m_pSqlData->m_pResults->rowsCount() == 1){
+					pData->m_pSqlData->m_pResults->next();
+
+					pData->m_pSqlData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
+					str_format(aBuf, sizeof(aBuf), "INSERT INTO zcatch_ranks(Name, Wins) VALUES ('%s') ON duplicate key UPDATE Name=VALUES(Name), Wins=Wins+1;", pData->m_aName);
+					pData->m_pSqlData->m_pStatement->execute(aBuf);
+				}
+			}
+
+			pData->m_pSqlData->GameServer()->SendChatTarget(pData->m_ClientID, aBuf);
+			delete pData->m_pSqlData->m_pResults;
+
+			// if no entry found... create a new one
+			str_format(aBuf, sizeof(aBuf), "INSERT IGNORE INTO %s_race(Name, Wins2 VALUES ('%s', '1');",pData->m_aName);
+			pData->m_pSqlData->m_pStatement->execute(aBuf);
+
+			dbg_msg("SQL", "Updating rank done");
+		}
+		catch (sql::SQLException &e)
+		{
+			char aBuf[256];
+			str_format(aBuf, sizeof(aBuf), "MySQL Error: %s", e.what());
+			dbg_msg("SQL", aBuf);
+			dbg_msg("SQL", "ERROR: Could not update rank");
+		}
+		pData->m_pSqlData->Disconnect();
+	}
+
+	delete pData;
+	lock_release(gs_SqlLock);
+}
+
+
+// anti SQL injection
+void CRanking::ClearString(char *pString, int size)
+{
+	char newString[size*2-1];
+	int pos = 0;
+
+	for(int i=0;i<size;i++)
+	{
+		if(pString[i] == '\\')
+		{
+			newString[pos++] = '\\';
+			newString[pos++] = '\\';
+		}
+		else if(pString[i] == '\'')
+		{
+			newString[pos++] = '\\';
+			newString[pos++] = '\'';
+		}
+		else if(pString[i] == '"')
+		{
+			newString[pos++] = '\\';
+			newString[pos++] = '"';
+		}
+		else
+		{
+			newString[pos++] = pString[i];
+		}
+	}
+
+	newString[pos] = '\0';
+
+	strcpy(pString,newString);
+}
+#endif
+
diff --git a/src/game/server/ranking.h b/src/game/server/ranking.h
new file mode 100644
index 00000000..6c4d1495
--- /dev/null
+++ b/src/game/server/ranking.h
@@ -0,0 +1,70 @@
+#ifndef GAME_SERVER_RANKING_H
+#define GAME_SERVER_RANKING_H
+
+#include "gamecontext.h"
+
+#include <mysql_connection.h>
+
+#include <cppconn/driver.h>
+#include <cppconn/exception.h>
+#include <cppconn/statement.h>
+
+
+class CRanking
+{
+	CGameContext *m_pGameServer;
+	IServer *m_pServer;
+
+
+	sql::Driver *m_pDriver;
+	sql::Connection *m_pConnection;
+	sql::Statement *m_pStatement;
+	sql::ResultSet *m_pResults;
+
+	// copy of config vars
+	const char* m_pDatabase;
+	const char* m_pUser;
+	const char* m_pPass;
+	const char* m_pIp;
+	int m_Port;
+
+	CGameContext *GameServer()
+	{
+		return m_pGameServer;
+	}
+	IServer *Server()
+	{
+		return m_pServer;
+	}
+
+	static void SaveRankingThread(void *pUser);
+
+	void Init();
+
+	bool Connect();
+	void Disconnect();
+	// anti SQL injection
+	void ClearString(char *pString, int size = 32);
+
+
+
+public:
+
+	CRanking(CGameContext *pGameServer);
+	~CRanking();
+
+	void SaveRanking(int ClientID);
+};
+
+struct CSqlRankData
+{
+	CRanking *m_pSqlData;
+	int m_ClientID;
+#if defined(CONF_FAMILY_WINDOWS)
+	char m_aName[16]; // Don't edit this, or all your teeth will fall http://bugs.mysql.com/bug.php?id=50046
+#else
+	char m_aName[MAX_NAME_LENGTH * 2 - 1];
+#endif
+};
+
+#endif