about summary refs log tree commit diff
path: root/src/game/server/gamecontext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game/server/gamecontext.cpp')
-rw-r--r--src/game/server/gamecontext.cpp1036
1 files changed, 850 insertions, 186 deletions
diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp
index 736d437f..795bb65f 100644
--- a/src/game/server/gamecontext.cpp
+++ b/src/game/server/gamecontext.cpp
@@ -1,99 +1,143 @@
-#include <string.h>
 #include <new>
-#include <engine/e_server_interface.h>
-#include "gamecontext.hpp"
+#include <base/math.h>
+#include <engine/shared/config.h>
+#include <engine/map.h>
+#include <engine/console.h>
+#include "gamecontext.h"
+#include <game/version.h>
+#include <game/collision.h>
+#include <game/gamecore.h>
+#include "gamemodes/dm.h"
+#include "gamemodes/tdm.h"
+#include "gamemodes/ctf.h"
+#include "gamemodes/mod.h"
 
-GAMECONTEXT game;
+enum
+{
+	RESET,
+	NO_RESET
+};
 
-GAMECONTEXT::GAMECONTEXT()
+void CGameContext::Construct(int Resetting)
 {
+	m_Resetting = 0;
+	m_pServer = 0;
+	
 	for(int i = 0; i < MAX_CLIENTS; i++)
-		players[i] = 0;
+		m_apPlayers[i] = 0;
 	
-	controller = 0;
-	vote_closetime = 0;
+	m_pController = 0;
+	m_VoteCloseTime = 0;
+	m_pVoteOptionFirst = 0;
+	m_pVoteOptionLast = 0;
+
+	if(Resetting==NO_RESET)
+		m_pVoteOptionHeap = new CHeap();
+}
+
+CGameContext::CGameContext(int Resetting)
+{
+	Construct(Resetting);
+}
+
+CGameContext::CGameContext()
+{
+	Construct(NO_RESET);
 }
 
-GAMECONTEXT::~GAMECONTEXT()
+CGameContext::~CGameContext()
 {
 	for(int i = 0; i < MAX_CLIENTS; i++)
-		delete players[i];
+		delete m_apPlayers[i];
+	if(!m_Resetting)
+		delete m_pVoteOptionHeap;
 }
 
-void GAMECONTEXT::clear()
+void CGameContext::Clear()
 {
-	this->~GAMECONTEXT();
+	CHeap *pVoteOptionHeap = m_pVoteOptionHeap;
+	CVoteOption *pVoteOptionFirst = m_pVoteOptionFirst;
+	CVoteOption *pVoteOptionLast = m_pVoteOptionLast;
+	CTuningParams Tuning = m_Tuning;
+
+	m_Resetting = true;
+	this->~CGameContext();
 	mem_zero(this, sizeof(*this));
-	new (this) GAMECONTEXT();
+	new (this) CGameContext(RESET);
+
+	m_pVoteOptionHeap = pVoteOptionHeap;
+	m_pVoteOptionFirst = pVoteOptionFirst;
+	m_pVoteOptionLast = pVoteOptionLast;
+	m_Tuning = Tuning;
 }
 
 
-class CHARACTER *GAMECONTEXT::get_player_char(int client_id)
+class CCharacter *CGameContext::GetPlayerChar(int ClientId)
 {
-	if(client_id < 0 || client_id >= MAX_CLIENTS || !players[client_id])
+	if(ClientId < 0 || ClientId >= MAX_CLIENTS || !m_apPlayers[ClientId])
 		return 0;
-	return players[client_id]->get_character();
+	return m_apPlayers[ClientId]->GetCharacter();
 }
 
-void GAMECONTEXT::create_damageind(vec2 p, float angle, int amount)
+void CGameContext::CreateDamageInd(vec2 p, float Angle, int Amount)
 {
-	float a = 3 * 3.14159f / 2 + angle;
+	float a = 3 * 3.14159f / 2 + Angle;
 	//float a = get_angle(dir);
 	float s = a-pi/3;
 	float e = a+pi/3;
-	for(int i = 0; i < amount; i++)
+	for(int i = 0; i < Amount; i++)
 	{
-		float f = mix(s, e, float(i+1)/float(amount+2));
-		NETEVENT_DAMAGEIND *ev = (NETEVENT_DAMAGEIND *)events.create(NETEVENTTYPE_DAMAGEIND, sizeof(NETEVENT_DAMAGEIND));
+		float f = mix(s, e, float(i+1)/float(Amount+2));
+		NETEVENT_DAMAGEIND *ev = (NETEVENT_DAMAGEIND *)m_Events.Create(NETEVENTTYPE_DAMAGEIND, sizeof(NETEVENT_DAMAGEIND));
 		if(ev)
 		{
-			ev->x = (int)p.x;
-			ev->y = (int)p.y;
-			ev->angle = (int)(f*256.0f);
+			ev->m_X = (int)p.x;
+			ev->m_Y = (int)p.y;
+			ev->m_Angle = (int)(f*256.0f);
 		}
 	}
 }
 
-void GAMECONTEXT::create_hammerhit(vec2 p)
+void CGameContext::CreateHammerHit(vec2 p)
 {
 	// create the event
-	NETEVENT_HAMMERHIT *ev = (NETEVENT_HAMMERHIT *)events.create(NETEVENTTYPE_HAMMERHIT, sizeof(NETEVENT_HAMMERHIT));
+	NETEVENT_HAMMERHIT *ev = (NETEVENT_HAMMERHIT *)m_Events.Create(NETEVENTTYPE_HAMMERHIT, sizeof(NETEVENT_HAMMERHIT));
 	if(ev)
 	{
-		ev->x = (int)p.x;
-		ev->y = (int)p.y;
+		ev->m_X = (int)p.x;
+		ev->m_Y = (int)p.y;
 	}
 }
 
 
-void GAMECONTEXT::create_explosion(vec2 p, int owner, int weapon, bool bnodamage)
+void CGameContext::CreateExplosion(vec2 p, int Owner, int Weapon, bool NoDamage)
 {
 	// create the event
-	NETEVENT_EXPLOSION *ev = (NETEVENT_EXPLOSION *)events.create(NETEVENTTYPE_EXPLOSION, sizeof(NETEVENT_EXPLOSION));
+	NETEVENT_EXPLOSION *ev = (NETEVENT_EXPLOSION *)m_Events.Create(NETEVENTTYPE_EXPLOSION, sizeof(NETEVENT_EXPLOSION));
 	if(ev)
 	{
-		ev->x = (int)p.x;
-		ev->y = (int)p.y;
+		ev->m_X = (int)p.x;
+		ev->m_Y = (int)p.y;
 	}
 
-	if (!bnodamage)
+	if (!NoDamage)
 	{
 		// deal damage
-		CHARACTER *ents[64];
-		float radius = 135.0f;
-		float innerradius = 48.0f;
-		int num = game.world.find_entities(p, radius, (ENTITY**)ents, 64, NETOBJTYPE_CHARACTER);
-		for(int i = 0; i < num; i++)
+		CCharacter *apEnts[64];
+		float Radius = 135.0f;
+		float InnerRadius = 48.0f;
+		int Num = m_World.FindEntities(p, Radius, (CEntity**)apEnts, 64, NETOBJTYPE_CHARACTER);
+		for(int i = 0; i < Num; i++)
 		{
-			vec2 diff = ents[i]->pos - p;
-			vec2 forcedir(0,1);
-			float l = length(diff);
+			vec2 Diff = apEnts[i]->m_Pos - p;
+			vec2 ForceDir(0,1);
+			float l = length(Diff);
 			if(l)
-				forcedir = normalize(diff);
-			l = 1-clamp((l-innerradius)/(radius-innerradius), 0.0f, 1.0f);
-			float dmg = 6 * l;
-			if((int)dmg)
-				ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner, weapon);
+				ForceDir = normalize(Diff);
+			l = 1-clamp((l-InnerRadius)/(Radius-InnerRadius), 0.0f, 1.0f);
+			float Dmg = 6 * l;
+			if((int)Dmg)
+				apEnts[i]->TakeDamage(ForceDir*Dmg*2, (int)Dmg, Owner, Weapon);
 		}
 	}
 }
@@ -110,273 +154,893 @@ void create_smoke(vec2 p)
 	}
 }*/
 
-void GAMECONTEXT::create_playerspawn(vec2 p)
+void CGameContext::CreatePlayerSpawn(vec2 p)
 {
 	// create the event
-	NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)events.create(NETEVENTTYPE_SPAWN, sizeof(NETEVENT_SPAWN));
+	NETEVENT_SPAWN *ev = (NETEVENT_SPAWN *)m_Events.Create(NETEVENTTYPE_SPAWN, sizeof(NETEVENT_SPAWN));
 	if(ev)
 	{
-		ev->x = (int)p.x;
-		ev->y = (int)p.y;
+		ev->m_X = (int)p.x;
+		ev->m_Y = (int)p.y;
 	}
 }
 
-void GAMECONTEXT::create_death(vec2 p, int cid)
+void CGameContext::CreateDeath(vec2 p, int ClientId)
 {
 	// create the event
-	NETEVENT_DEATH *ev = (NETEVENT_DEATH *)events.create(NETEVENTTYPE_DEATH, sizeof(NETEVENT_DEATH));
+	NETEVENT_DEATH *ev = (NETEVENT_DEATH *)m_Events.Create(NETEVENTTYPE_DEATH, sizeof(NETEVENT_DEATH));
 	if(ev)
 	{
-		ev->x = (int)p.x;
-		ev->y = (int)p.y;
-		ev->cid = cid;
+		ev->m_X = (int)p.x;
+		ev->m_Y = (int)p.y;
+		ev->m_ClientId = ClientId;
 	}
 }
 
-void GAMECONTEXT::create_sound(vec2 pos, int sound, int mask)
+void CGameContext::CreateSound(vec2 Pos, int Sound, int Mask)
 {
-	if (sound < 0)
+	if (Sound < 0)
 		return;
 
 	// create a sound
-	NETEVENT_SOUNDWORLD *ev = (NETEVENT_SOUNDWORLD *)events.create(NETEVENTTYPE_SOUNDWORLD, sizeof(NETEVENT_SOUNDWORLD), mask);
+	NETEVENT_SOUNDWORLD *ev = (NETEVENT_SOUNDWORLD *)m_Events.Create(NETEVENTTYPE_SOUNDWORLD, sizeof(NETEVENT_SOUNDWORLD), Mask);
 	if(ev)
 	{
-		ev->x = (int)pos.x;
-		ev->y = (int)pos.y;
-		ev->soundid = sound;
+		ev->m_X = (int)Pos.x;
+		ev->m_Y = (int)Pos.y;
+		ev->m_SoundId = Sound;
 	}
 }
 
-void GAMECONTEXT::create_sound_global(int sound, int target)
+void CGameContext::CreateSoundGlobal(int Sound, int Target)
 {
-	if (sound < 0)
+	if (Sound < 0)
 		return;
 
-	NETMSG_SV_SOUNDGLOBAL msg;
-	msg.soundid = sound;
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(target);
+	CNetMsg_Sv_SoundGlobal Msg;
+	Msg.m_Soundid = Sound;
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, Target);
 }
 
 
-void GAMECONTEXT::send_chat_target(int to, const char *text)
+void CGameContext::SendChatTarget(int To, const char *pText)
 {
-	NETMSG_SV_CHAT msg;
-	msg.team = 0;
-	msg.cid = -1;
-	msg.message = text;
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(to);
+	CNetMsg_Sv_Chat Msg;
+	Msg.m_Team = 0;
+	Msg.m_Cid = -1;
+	Msg.m_pMessage = pText;
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, To);
 }
 
 
-void GAMECONTEXT::send_chat(int chatter_cid, int team, const char *text)
+void CGameContext::SendChat(int ChatterClientId, int Team, const char *pText)
 {
-	if(chatter_cid >= 0 && chatter_cid < MAX_CLIENTS)
-		dbg_msg("chat", "%d:%d:%s: %s", chatter_cid, team, server_clientname(chatter_cid), text);
+	if(ChatterClientId >= 0 && ChatterClientId < MAX_CLIENTS)
+		dbg_msg("chat", "%d:%d:%s: %s", ChatterClientId, Team, Server()->ClientName(ChatterClientId), pText);
 	else
-		dbg_msg("chat", "*** %s", text);
+		dbg_msg("chat", "*** %s", pText);
 
-	if(team == CHAT_ALL)
+	if(Team == CHAT_ALL)
 	{
-		NETMSG_SV_CHAT msg;
-		msg.team = 0;
-		msg.cid = chatter_cid;
-		msg.message = text;
-		msg.pack(MSGFLAG_VITAL);
-		server_send_msg(-1);
+		CNetMsg_Sv_Chat Msg;
+		Msg.m_Team = 0;
+		Msg.m_Cid = ChatterClientId;
+		Msg.m_pMessage = pText;
+		Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
 	}
 	else
 	{
-		NETMSG_SV_CHAT msg;
-		msg.team = 1;
-		msg.cid = chatter_cid;
-		msg.message = text;
+		CNetMsg_Sv_Chat Msg;
+		Msg.m_Team = 1;
+		Msg.m_Cid = ChatterClientId;
+		Msg.m_pMessage = pText;
 		
 		// pack one for the recording only
-		msg.pack(MSGFLAG_VITAL|MSGFLAG_NOSEND);
-		server_send_msg(-1);
+		Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NOSEND, -1);
 
 		// send to the clients
-		msg.pack(MSGFLAG_VITAL|MSGFLAG_NORECORD);
 		for(int i = 0; i < MAX_CLIENTS; i++)
 		{
-			if(game.players[i] && game.players[i]->team == team)
-				server_send_msg(i);
+			if(m_apPlayers[i] && m_apPlayers[i]->GetTeam() == Team)
+				Server()->SendPackMsg(&Msg, MSGFLAG_VITAL|MSGFLAG_NORECORD, i);
 		}
 	}
 }
 
-void GAMECONTEXT::send_emoticon(int cid, int emoticon)
+void CGameContext::SendEmoticon(int ClientId, int Emoticon)
 {
-	NETMSG_SV_EMOTICON msg;
-	msg.cid = cid;
-	msg.emoticon = emoticon;
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(-1);
+	CNetMsg_Sv_Emoticon Msg;
+	Msg.m_Cid = ClientId;
+	Msg.m_Emoticon = Emoticon;
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, -1);
 }
 
-void GAMECONTEXT::send_weapon_pickup(int cid, int weapon)
+void CGameContext::SendWeaponPickup(int ClientId, int Weapon)
 {
-	NETMSG_SV_WEAPONPICKUP msg;
-	msg.weapon = weapon;
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(cid);
+	CNetMsg_Sv_WeaponPickup Msg;
+	Msg.m_Weapon = Weapon;
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientId);
 }
 
 
-void GAMECONTEXT::send_broadcast(const char *text, int cid)
+void CGameContext::SendBroadcast(const char *pText, int ClientId)
 {
-	NETMSG_SV_BROADCAST msg;
-	msg.message = text;
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(cid);
+	CNetMsg_Sv_Broadcast Msg;
+	Msg.m_pMessage = pText;
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientId);
 }
 
 // 
-void GAMECONTEXT::start_vote(const char *desc, const char *command)
+void CGameContext::StartVote(const char *pDesc, const char *pCommand)
 {
 	// check if a vote is already running
-	if(vote_closetime)
+	if(m_VoteCloseTime)
 		return;
 
 	// reset votes
-	vote_enforce = VOTE_ENFORCE_UNKNOWN;
+	m_VoteEnforce = VOTE_ENFORCE_UNKNOWN;
 	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		if(players[i])
-			players[i]->vote = 0;
+		if(m_apPlayers[i])
+		{
+			m_apPlayers[i]->m_Vote = 0;
+			m_apPlayers[i]->m_VotePos = 0;
+		}
 	}
 	
 	// start vote
-	vote_closetime = time_get() + time_freq()*25;
-	str_copy(vote_description, desc, sizeof(vote_description));
-	str_copy(vote_command, command, sizeof(vote_description));
-	send_vote_set(-1);
-	send_vote_status(-1);
+	m_VoteCloseTime = time_get() + time_freq()*25;
+	str_copy(m_aVoteDescription, pDesc, sizeof(m_aVoteDescription));
+	str_copy(m_aVoteCommand, pCommand, sizeof(m_aVoteCommand));
+	SendVoteSet(-1);
+	m_VoteUpdate = true;
 }
 
 
-void GAMECONTEXT::end_vote()
+void CGameContext::EndVote()
 {
-	vote_closetime = 0;
-	send_vote_set(-1);
+	m_VoteCloseTime = 0;
+	SendVoteSet(-1);
 }
 
-void GAMECONTEXT::send_vote_set(int cid)
+void CGameContext::SendVoteSet(int ClientId)
 {
-	NETMSG_SV_VOTE_SET msg;
-	if(vote_closetime)
+	CNetMsg_Sv_VoteSet Msg;
+	if(m_VoteCloseTime)
 	{
-		msg.timeout = (vote_closetime-time_get())/time_freq();
-		msg.description = vote_description;
-		msg.command = vote_command;
+		Msg.m_Timeout = (m_VoteCloseTime-time_get())/time_freq();
+		Msg.m_pDescription = m_aVoteDescription;
+		Msg.m_pCommand = "";
 	}
 	else
 	{
-		msg.timeout = 0;
-		msg.description = "";
-		msg.command = "";
+		Msg.m_Timeout = 0;
+		Msg.m_pDescription = "";
+		Msg.m_pCommand = "";
 	}
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(cid);
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientId);
 }
 
-void GAMECONTEXT::send_vote_status(int cid)
+void CGameContext::SendVoteStatus(int ClientId, int Total, int Yes, int No)
 {
-	NETMSG_SV_VOTE_STATUS msg = {0};
-	for(int i = 0; i < MAX_CLIENTS; i++)
+	CNetMsg_Sv_VoteStatus Msg = {0};
+	Msg.m_Total = Total;
+	Msg.m_Yes = Yes;
+	Msg.m_No = No;
+	Msg.m_Pass = Total - (Yes+No);
+
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientId);
+	
+}
+
+void CGameContext::AbortVoteKickOnDisconnect(int ClientId)
+{
+	if(m_VoteCloseTime && !str_comp_num(m_aVoteCommand, "kick ", 5) && str_toint(&m_aVoteCommand[5]) == ClientId)
+		m_VoteCloseTime = -1;
+}
+
+
+void CGameContext::CheckPureTuning()
+{
+	// might not be created yet during start up
+	if(!m_pController)
+		return;
+	
+	if(	str_comp(m_pController->m_pGameType, "DM")==0 ||
+		str_comp(m_pController->m_pGameType, "TDM")==0 ||
+		str_comp(m_pController->m_pGameType, "CTF")==0)
 	{
-		if(players[i])
+		CTuningParams p;
+		if(mem_comp(&p, &m_Tuning, sizeof(p)) != 0)
 		{
-			msg.total++;
-			if(players[i]->vote > 0)
-				msg.yes++;
-			else if(players[i]->vote < 0)
-				msg.no++;
-			else
-				msg.pass++;
+			dbg_msg("server", "resetting tuning due to pure server");
+			m_Tuning = p;
 		}
 	}	
-
-	msg.pack(MSGFLAG_VITAL);
-	server_send_msg(cid);
-	
 }
 
-void GAMECONTEXT::abort_vote_kick_on_disconnect(int client_id)
+void CGameContext::SendTuningParams(int Cid)
 {
-	if(vote_closetime && !strncmp(vote_command, "kick ", 5) && atoi(&vote_command[5]) == client_id)
-		vote_closetime = -1;
+	CheckPureTuning();
+	
+	CMsgPacker Msg(NETMSGTYPE_SV_TUNEPARAMS);
+	int *pParams = (int *)&m_Tuning;
+	for(unsigned i = 0; i < sizeof(m_Tuning)/sizeof(int); i++)
+		Msg.AddInt(pParams[i]);
+	Server()->SendMsg(&Msg, MSGFLAG_VITAL, Cid);
 }
 
-void GAMECONTEXT::tick()
+void CGameContext::OnTick()
 {
-	world.core.tuning = tuning;
-	world.tick();
+	// check tuning
+	CheckPureTuning();
+
+	// copy tuning
+	m_World.m_Core.m_Tuning = m_Tuning;
+	m_World.Tick();
 
 	//if(world.paused) // make sure that the game object always updates
-	controller->tick();
+	m_pController->Tick();
 		
 	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		if(players[i])
-			players[i]->tick();
+		if(m_apPlayers[i])
+			m_apPlayers[i]->Tick();
 	}
 	
 	// update voting
-	if(vote_closetime)
+	if(m_VoteCloseTime)
 	{
 		// abort the kick-vote on player-leave
-		if(vote_closetime == -1)
+		if(m_VoteCloseTime == -1)
 		{
-			send_chat(-1, GAMECONTEXT::CHAT_ALL, "Vote aborted");
-			end_vote();
+			SendChat(-1, CGameContext::CHAT_ALL, "Vote aborted");
+			EndVote();
 		}
 		else
 		{
-			// count votes
-			int total = 0, yes = 0, no = 0;
-			for(int i = 0; i < MAX_CLIENTS; i++)
+			int Total = 0, Yes = 0, No = 0;
+			if(m_VoteUpdate)
 			{
-				if(players[i])
+				// count votes
+				char aaBuf[MAX_CLIENTS][64] = {{0}};
+				for(int i = 0; i < MAX_CLIENTS; i++)
+					if(m_apPlayers[i])
+						Server()->GetClientIP(i, aaBuf[i], 64);
+				bool aVoteChecked[MAX_CLIENTS] = {0};
+				for(int i = 0; i < MAX_CLIENTS; i++)
 				{
-					total++;
-					if(players[i]->vote > 0)
-						yes++;
-					else if(players[i]->vote < 0)
-						no++;
+					if(!m_apPlayers[i] || m_apPlayers[i]->GetTeam() == -1 || aVoteChecked[i])	// don't count in votes by spectators
+						continue;
+					
+					int ActVote = m_apPlayers[i]->m_Vote;
+					int ActVotePos = m_apPlayers[i]->m_VotePos;
+					
+					// check for more players with the same ip (only use the vote of the one who voted first)
+					for(int j = i+1; j < MAX_CLIENTS; ++j)
+					{
+						if(!m_apPlayers[j] || aVoteChecked[j] || str_comp(aaBuf[j], aaBuf[i]))
+							continue;
+
+						aVoteChecked[j] = true;
+						if(m_apPlayers[j]->m_Vote && (!ActVote || ActVotePos > m_apPlayers[j]->m_VotePos))
+						{
+							ActVote = m_apPlayers[j]->m_Vote;
+							ActVotePos = m_apPlayers[j]->m_VotePos;
+						}
+					}
+
+					Total++;
+					if(ActVote > 0)
+						Yes++;
+					else if(ActVote < 0)
+						No++;
 				}
+
+				if(Yes >= Total/2+1)
+					m_VoteEnforce = VOTE_ENFORCE_YES;
+				else if(No >= Total/2+1 || Yes+No == Total)
+					m_VoteEnforce = VOTE_ENFORCE_NO;
 			}
+			
+			if(m_VoteEnforce == VOTE_ENFORCE_YES)
+			{
+				Console()->ExecuteLine(m_aVoteCommand);
+				EndVote();
+				SendChat(-1, CGameContext::CHAT_ALL, "Vote passed");
+			
+				if(m_apPlayers[m_VoteCreator])
+					m_apPlayers[m_VoteCreator]->m_Last_VoteCall = 0;
+			}
+			else if(m_VoteEnforce == VOTE_ENFORCE_NO || time_get() > m_VoteCloseTime)
+			{
+				EndVote();
+				SendChat(-1, CGameContext::CHAT_ALL, "Vote failed");
+			}
+			else if(m_VoteUpdate)
+			{
+				m_VoteUpdate = false;
+				SendVoteStatus(-1, Total, Yes, No);
+			}
+		}
+	}
+	
+
+#ifdef CONF_DEBUG
+	if(g_Config.m_DbgDummies)
+	{
+		for(int i = 0; i < g_Config.m_DbgDummies ; i++)
+		{
+			CNetObj_PlayerInput Input = {0};
+			Input.m_Direction = (i&1)?-1:1;
+			m_apPlayers[MAX_CLIENTS-i-1]->OnPredictedInput(&Input);
+		}
+	}
+#endif	
+}
+
+// Server hooks
+void CGameContext::OnClientDirectInput(int ClientID, void *pInput)
+{
+	if(!m_World.m_Paused)
+		m_apPlayers[ClientID]->OnDirectInput((CNetObj_PlayerInput *)pInput);
+}
+
+void CGameContext::OnClientPredictedInput(int ClientID, void *pInput)
+{
+	if(!m_World.m_Paused)
+		m_apPlayers[ClientID]->OnPredictedInput((CNetObj_PlayerInput *)pInput);
+}
+
+void CGameContext::OnClientEnter(int ClientId)
+{
+	//world.insert_entity(&players[client_id]);
+	m_apPlayers[ClientId]->Respawn();
+	dbg_msg("game", "join player='%d:%s'", ClientId, Server()->ClientName(ClientId));
+
+
+	char aBuf[512];
+	str_format(aBuf, sizeof(aBuf), "%s entered and joined the %s", Server()->ClientName(ClientId), m_pController->GetTeamName(m_apPlayers[ClientId]->GetTeam()));
+	SendChat(-1, CGameContext::CHAT_ALL, aBuf); 
+
+	dbg_msg("game", "team_join player='%d:%s' team=%d", ClientId, Server()->ClientName(ClientId), m_apPlayers[ClientId]->GetTeam());
+
+	m_VoteUpdate = true;
+}
+
+void CGameContext::OnClientConnected(int ClientId)
+{
+	// Check which team the player should be on
+	const int StartTeam = g_Config.m_SvTournamentMode ? -1 : m_pController->GetAutoTeam(ClientId);
+
+	m_apPlayers[ClientId] = new(ClientId) CPlayer(this, ClientId, StartTeam);
+	//players[client_id].init(client_id);
+	//players[client_id].client_id = client_id;
+	
+	(void)m_pController->CheckTeamBalance();
+
+#ifdef CONF_DEBUG
+	if(g_Config.m_DbgDummies)
+	{
+		if(ClientId >= MAX_CLIENTS-g_Config.m_DbgDummies)
+			return;
+	}
+#endif
+
+	// send active vote
+	if(m_VoteCloseTime)
+		SendVoteSet(ClientId);
+
+	// send motd
+	CNetMsg_Sv_Motd Msg;
+	Msg.m_pMessage = g_Config.m_SvMotd;
+	Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, ClientId);
+}
+
+void CGameContext::OnClientDrop(int ClientId)
+{
+	AbortVoteKickOnDisconnect(ClientId);
+	m_apPlayers[ClientId]->OnDisconnect();
+	delete m_apPlayers[ClientId];
+	m_apPlayers[ClientId] = 0;
+	
+	(void)m_pController->CheckTeamBalance();
+	m_VoteUpdate = true;
+}
+
+void CGameContext::OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientId)
+{
+	void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker);
+	CPlayer *p = m_apPlayers[ClientId];
+	
+	if(!pRawMsg)
+	{
+		dbg_msg("server", "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn());
+		return;
+	}
+	
+	if(MsgId == NETMSGTYPE_CL_SAY)
+	{
+		CNetMsg_Cl_Say *pMsg = (CNetMsg_Cl_Say *)pRawMsg;
+		int Team = pMsg->m_Team;
+		if(Team)
+			Team = p->GetTeam();
+		else
+			Team = CGameContext::CHAT_ALL;
+		
+		if(g_Config.m_SvSpamprotection && p->m_Last_Chat && p->m_Last_Chat+Server()->TickSpeed() > Server()->Tick())
+			return;
+		
+		p->m_Last_Chat = Server()->Tick();
+
+		// check for invalid chars
+		unsigned char *pMessage = (unsigned char *)pMsg->m_pMessage;
+		while (*pMessage)
+		{
+			if(*pMessage < 32)
+				*pMessage = ' ';
+			pMessage++;
+		}
+		
+		SendChat(ClientId, Team, pMsg->m_pMessage);
+	}
+	else if(MsgId == NETMSGTYPE_CL_CALLVOTE)
+	{
+		if(g_Config.m_SvSpamprotection && p->m_Last_VoteTry && p->m_Last_VoteTry+Server()->TickSpeed()*3 > Server()->Tick())
+			return;
+
+		int64 Now = Server()->Tick();
+		p->m_Last_VoteTry = Now;
+		if(m_VoteCloseTime)
+		{
+			SendChatTarget(ClientId, "Wait for current vote to end before calling a new one.");
+			return;
+		}
 		
-			if(vote_enforce == VOTE_ENFORCE_YES || yes >= total/2+1)
+		int Timeleft = p->m_Last_VoteCall + Server()->TickSpeed()*60 - Now;
+		if(p->m_Last_VoteCall && Timeleft > 0)
+		{
+			char aChatmsg[512] = {0};
+			str_format(aChatmsg, sizeof(aChatmsg), "You must wait %d seconds before making another vote", (Timeleft/Server()->TickSpeed())+1);
+			SendChatTarget(ClientId, aChatmsg);
+			return;
+		}
+		
+		char aChatmsg[512] = {0};
+		char aDesc[512] = {0};
+		char aCmd[512] = {0};
+		CNetMsg_Cl_CallVote *pMsg = (CNetMsg_Cl_CallVote *)pRawMsg;
+		if(str_comp_nocase(pMsg->m_Type, "option") == 0)
+		{
+			CVoteOption *pOption = m_pVoteOptionFirst;
+			while(pOption)
 			{
-				console_execute_line(vote_command);
-				end_vote();
-				send_chat(-1, GAMECONTEXT::CHAT_ALL, "Vote passed");
+				if(str_comp_nocase(pMsg->m_Value, pOption->m_aCommand) == 0)
+				{
+					str_format(aChatmsg, sizeof(aChatmsg), "%s called vote to change server option '%s'", Server()->ClientName(ClientId), pOption->m_aCommand);
+					str_format(aDesc, sizeof(aDesc), "%s", pOption->m_aCommand);
+					str_format(aCmd, sizeof(aCmd), "%s", pOption->m_aCommand);
+					break;
+				}
+
+				pOption = pOption->m_pNext;
+			}
 			
-				if(players[vote_creator])
-					players[vote_creator]->last_votecall = 0;
+			if(!pOption)
+			{
+				str_format(aChatmsg, sizeof(aChatmsg), "'%s' isn't an option on this server", pMsg->m_Value);
+				SendChatTarget(ClientId, aChatmsg);
+				return;
 			}
-			else if(vote_enforce == VOTE_ENFORCE_NO || time_get() > vote_closetime || no >= total/2+1 || yes+no == total)
+		}
+		else if(str_comp_nocase(pMsg->m_Type, "kick") == 0)
+		{
+			if(!g_Config.m_SvVoteKick)
 			{
-				end_vote();
-				send_chat(-1, GAMECONTEXT::CHAT_ALL, "Vote failed");
+				SendChatTarget(ClientId, "Server does not allow voting to kick players");
+				return;
 			}
+			
+			int KickId = str_toint(pMsg->m_Value);
+			if(KickId < 0 || KickId >= MAX_CLIENTS || !m_apPlayers[KickId])
+			{
+				SendChatTarget(ClientId, "Invalid client id to kick");
+				return;
+			}
+			
+			str_format(aChatmsg, sizeof(aChatmsg), "%s called for vote to kick '%s'", Server()->ClientName(ClientId), Server()->ClientName(KickId));
+			str_format(aDesc, sizeof(aDesc), "Kick '%s'", Server()->ClientName(KickId));
+			if (!g_Config.m_SvVoteKickBantime)
+				str_format(aCmd, sizeof(aCmd), "kick %d", KickId);
+			else
+			{
+				char aBuf[64] = {0};
+				Server()->GetClientIP(KickId, aBuf, sizeof(aBuf));
+				str_format(aCmd, sizeof(aCmd), "ban %s %d", aBuf, g_Config.m_SvVoteKickBantime);
+			}
+		}
+		
+		if(aCmd[0])
+		{
+			SendChat(-1, CGameContext::CHAT_ALL, aChatmsg);
+			StartVote(aDesc, aCmd);
+			p->m_Vote = 1;
+			p->m_VotePos = m_VotePos = 1;
+			m_VoteCreator = ClientId;
+			p->m_Last_VoteCall = Now;
+		}
+	}
+	else if(MsgId == NETMSGTYPE_CL_VOTE)
+	{
+		if(!m_VoteCloseTime)
+			return;
+
+		if(p->m_Vote == 0)
+		{
+			CNetMsg_Cl_Vote *pMsg = (CNetMsg_Cl_Vote *)pRawMsg;
+			if(!pMsg->m_Vote)
+				return;
+
+			p->m_Vote = pMsg->m_Vote;
+			p->m_VotePos = ++m_VotePos;
+			m_VoteUpdate = true;
 		}
 	}
+	else if (MsgId == NETMSGTYPE_CL_SETTEAM && !m_World.m_Paused)
+	{
+		CNetMsg_Cl_SetTeam *pMsg = (CNetMsg_Cl_SetTeam *)pRawMsg;
+		
+		if(p->GetTeam() == pMsg->m_Team || (g_Config.m_SvSpamprotection && p->m_Last_SetTeam && p->m_Last_SetTeam+Server()->TickSpeed()*3 > Server()->Tick()))
+			return;
+
+		// Switch team on given client and kill/respawn him
+		if(m_pController->CanJoinTeam(pMsg->m_Team, ClientId))
+		{
+			if(m_pController->CanChangeTeam(p, pMsg->m_Team))
+			{
+				p->m_Last_SetTeam = Server()->Tick();
+				if(p->GetTeam() == -1 || pMsg->m_Team == -1)
+					m_VoteUpdate = true;
+				p->SetTeam(pMsg->m_Team);
+				(void)m_pController->CheckTeamBalance();
+			}
+			else
+				SendBroadcast("Teams must be balanced, please join other team", ClientId);
+		}
+		else
+		{
+			char aBuf[128];
+			str_format(aBuf, sizeof(aBuf), "Only %d active players are allowed", g_Config.m_SvMaxClients-g_Config.m_SvSpectatorSlots);
+			SendBroadcast(aBuf, ClientId);
+		}
+	}
+	else if (MsgId == NETMSGTYPE_CL_CHANGEINFO || MsgId == NETMSGTYPE_CL_STARTINFO)
+	{
+		CNetMsg_Cl_ChangeInfo *pMsg = (CNetMsg_Cl_ChangeInfo *)pRawMsg;
+		
+		if(g_Config.m_SvSpamprotection && p->m_Last_ChangeInfo && p->m_Last_ChangeInfo+Server()->TickSpeed()*5 > Server()->Tick())
+			return;
+			
+		p->m_Last_ChangeInfo = Server()->Tick();
+		
+		p->m_TeeInfos.m_UseCustomColor = pMsg->m_UseCustomColor;
+		p->m_TeeInfos.m_ColorBody = pMsg->m_ColorBody;
+		p->m_TeeInfos.m_ColorFeet = pMsg->m_ColorFeet;
+
+		// check for invalid chars
+		unsigned char *pName = (unsigned char *)pMsg->m_pName;
+		while (*pName)
+		{
+			if(*pName < 32)
+				*pName = ' ';
+			pName++;
+		}
+
+		// copy old name
+		char aOldName[MAX_NAME_LENGTH];
+		str_copy(aOldName, Server()->ClientName(ClientId), MAX_NAME_LENGTH);
+		
+		Server()->SetClientName(ClientId, pMsg->m_pName);
+		if(MsgId == NETMSGTYPE_CL_CHANGEINFO && str_comp(aOldName, Server()->ClientName(ClientId)) != 0)
+		{
+			char aChatText[256];
+			str_format(aChatText, sizeof(aChatText), "%s changed name to %s", aOldName, Server()->ClientName(ClientId));
+			SendChat(-1, CGameContext::CHAT_ALL, aChatText);
+		}
+		
+		// set skin
+		str_copy(p->m_TeeInfos.m_SkinName, pMsg->m_pSkin, sizeof(p->m_TeeInfos.m_SkinName));
+		
+		m_pController->OnPlayerInfoChange(p);
+		
+		if(MsgId == NETMSGTYPE_CL_STARTINFO)
+		{
+			// send vote options
+			CNetMsg_Sv_VoteClearOptions ClearMsg;
+			Server()->SendPackMsg(&ClearMsg, MSGFLAG_VITAL, ClientId);
+			CVoteOption *pCurrent = m_pVoteOptionFirst;
+			while(pCurrent)
+			{
+				CNetMsg_Sv_VoteOption OptionMsg;
+				OptionMsg.m_pCommand = pCurrent->m_aCommand;
+				Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, ClientId);
+				pCurrent = pCurrent->m_pNext;
+			}
+			
+			// send tuning parameters to client
+			SendTuningParams(ClientId);
+
+			//
+			CNetMsg_Sv_ReadyToEnter m;
+			Server()->SendPackMsg(&m, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientId);
+		}
+	}
+	else if (MsgId == NETMSGTYPE_CL_EMOTICON && !m_World.m_Paused)
+	{
+		CNetMsg_Cl_Emoticon *pMsg = (CNetMsg_Cl_Emoticon *)pRawMsg;
+		
+		if(g_Config.m_SvSpamprotection && p->m_Last_Emote && p->m_Last_Emote+Server()->TickSpeed()*3 > Server()->Tick())
+			return;
+			
+		p->m_Last_Emote = Server()->Tick();
+		
+		SendEmoticon(ClientId, pMsg->m_Emoticon);
+	}
+	else if (MsgId == NETMSGTYPE_CL_KILL && !m_World.m_Paused)
+	{
+		if(p->m_Last_Kill && p->m_Last_Kill+Server()->TickSpeed()*3 > Server()->Tick())
+			return;
+		
+		p->m_Last_Kill = Server()->Tick();
+		p->KillCharacter(WEAPON_SELF);
+		p->m_RespawnTick = Server()->Tick()+Server()->TickSpeed()*3;
+	}
+}
+
+void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	const char *pParamName = pResult->GetString(0);
+	float NewValue = pResult->GetFloat(1);
+
+	if(pSelf->Tuning()->Set(pParamName, NewValue))
+	{
+		dbg_msg("tuning", "%s changed to %.2f", pParamName, NewValue);
+		pSelf->SendTuningParams(-1);
+	}
+	else
+		dbg_msg("tuning", "No such tuning parameter");
+}
+
+void CGameContext::ConTuneReset(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	CTuningParams p;
+	*pSelf->Tuning() = p;
+	pSelf->SendTuningParams(-1);
+	dbg_msg("tuning", "Tuning reset");
+}
+
+void CGameContext::ConTuneDump(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	for(int i = 0; i < pSelf->Tuning()->Num(); i++)
+	{
+		float v;
+		pSelf->Tuning()->Get(i, &v);
+		dbg_msg("tuning", "%s %.2f", pSelf->Tuning()->m_apNames[i], v);
+	}
+}
+
+void CGameContext::ConChangeMap(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	pSelf->m_pController->ChangeMap(pResult->GetString(0));
+}
+
+void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	if(pResult->NumArguments())
+		pSelf->m_pController->DoWarmup(pResult->GetInteger(0));
+	else
+		pSelf->m_pController->StartRound();
+}
+
+void CGameContext::ConBroadcast(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	pSelf->SendBroadcast(pResult->GetString(0), -1);
+}
+
+void CGameContext::ConSay(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	pSelf->SendChat(-1, CGameContext::CHAT_ALL, pResult->GetString(0));
+}
+
+void CGameContext::ConSetTeam(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	int ClientId = clamp(pResult->GetInteger(0), 0, (int)MAX_CLIENTS-1);
+	int Team = clamp(pResult->GetInteger(1), -1, 1);
+	
+	dbg_msg("", "%d %d", ClientId, Team);
+	
+	if(!pSelf->m_apPlayers[ClientId])
+		return;
+	
+	pSelf->m_apPlayers[ClientId]->SetTeam(Team);
+	(void)pSelf->m_pController->CheckTeamBalance();
+}
+
+void CGameContext::ConAddVote(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	int Len = str_length(pResult->GetString(0));
+	
+	CGameContext::CVoteOption *pOption = (CGameContext::CVoteOption *)pSelf->m_pVoteOptionHeap->Allocate(sizeof(CGameContext::CVoteOption) + Len);
+	pOption->m_pNext = 0;
+	pOption->m_pPrev = pSelf->m_pVoteOptionLast;
+	if(pOption->m_pPrev)
+		pOption->m_pPrev->m_pNext = pOption;
+	pSelf->m_pVoteOptionLast = pOption;
+	if(!pSelf->m_pVoteOptionFirst)
+		pSelf->m_pVoteOptionFirst = pOption;
+	
+	mem_copy(pOption->m_aCommand, pResult->GetString(0), Len+1);
+	dbg_msg("server", "added option '%s'", pOption->m_aCommand);
+
+	CNetMsg_Sv_VoteOption OptionMsg;
+	OptionMsg.m_pCommand = pOption->m_aCommand;
+	pSelf->Server()->SendPackMsg(&OptionMsg, MSGFLAG_VITAL, -1);
+}
+
+void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData)
+{
+	CGameContext *pSelf = (CGameContext *)pUserData;
+	if(str_comp_nocase(pResult->GetString(0), "yes") == 0)
+		pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_YES;
+	else if(str_comp_nocase(pResult->GetString(0), "no") == 0)
+		pSelf->m_VoteEnforce = CGameContext::VOTE_ENFORCE_NO;
+	dbg_msg("server", "forcing vote %s", pResult->GetString(0));
+}
+
+void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
+{
+	pfnCallback(pResult, pCallbackUserData);
+	if(pResult->NumArguments())
+	{
+		CNetMsg_Sv_Motd Msg;
+		Msg.m_pMessage = g_Config.m_SvMotd;
+		CGameContext *pSelf = (CGameContext *)pUserData;
+		for(int i = 0; i < MAX_CLIENTS; ++i)
+			if(pSelf->m_apPlayers[i])
+				pSelf->Server()->SendPackMsg(&Msg, MSGFLAG_VITAL, i);
+	}
+}
+
+void CGameContext::OnConsoleInit()
+{
+	m_pServer = Kernel()->RequestInterface<IServer>();
+	m_pConsole = Kernel()->RequestInterface<IConsole>();
+
+	Console()->Register("tune", "si", CFGFLAG_SERVER, ConTuneParam, this, "");
+	Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "");
+	Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "");
+
+	Console()->Register("change_map", "r", CFGFLAG_SERVER, ConChangeMap, this, "");
+	Console()->Register("restart", "?i", CFGFLAG_SERVER, ConRestart, this, "");
+	Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, "");
+	Console()->Register("say", "r", CFGFLAG_SERVER, ConSay, this, "");
+	Console()->Register("set_team", "ii", CFGFLAG_SERVER, ConSetTeam, this, "");
+
+	Console()->Register("addvote", "r", CFGFLAG_SERVER, ConAddVote, this, "");
+	Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, "");
+
+	Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this);
+}
+
+void CGameContext::OnInit(/*class IKernel *pKernel*/)
+{
+	m_pServer = Kernel()->RequestInterface<IServer>();
+	m_pConsole = Kernel()->RequestInterface<IConsole>();
+	m_World.SetGameServer(this);
+	m_Events.SetGameServer(this);
+	
+	//if(!data) // only load once
+		//data = load_data_from_memory(internal_data);
+		
+	for(int i = 0; i < NUM_NETOBJTYPES; i++)
+		Server()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i));
+
+	m_Layers.Init(Kernel());
+	m_Collision.Init(&m_Layers);
+
+	// reset everything here
+	//world = new GAMEWORLD;
+	//players = new CPlayer[MAX_CLIENTS];
+
+	// select gametype
+	if(str_comp(g_Config.m_SvGametype, "mod") == 0)
+		m_pController = new CGameControllerMOD(this);
+	else if(str_comp(g_Config.m_SvGametype, "ctf") == 0)
+		m_pController = new CGameControllerCTF(this);
+	else if(str_comp(g_Config.m_SvGametype, "tdm") == 0)
+		m_pController = new CGameControllerTDM(this);
+	else
+		m_pController = new CGameControllerDM(this);
+
+	// setup core world
+	//for(int i = 0; i < MAX_CLIENTS; i++)
+	//	game.players[i].core.world = &game.world.core;
+
+	// create all entities from the game layer
+	CMapItemLayerTilemap *pTileMap = m_Layers.GameLayer();
+	CTile *pTiles = (CTile *)Kernel()->RequestInterface<IMap>()->GetData(pTileMap->m_Data);
+	
+	
+	
+	
+	/*
+	num_spawn_points[0] = 0;
+	num_spawn_points[1] = 0;
+	num_spawn_points[2] = 0;
+	*/
+	
+	for(int y = 0; y < pTileMap->m_Height; y++)
+	{
+		for(int x = 0; x < pTileMap->m_Width; x++)
+		{
+			int Index = pTiles[y*pTileMap->m_Width+x].m_Index;
+			
+			if(Index >= ENTITY_OFFSET)
+			{
+				vec2 Pos(x*32.0f+16.0f, y*32.0f+16.0f);
+				m_pController->OnEntity(Index-ENTITY_OFFSET, Pos);
+			}
+		}
+	}
+
+	//game.world.insert_entity(game.Controller);
+
+#ifdef CONF_DEBUG
+	if(g_Config.m_DbgDummies)
+	{
+		for(int i = 0; i < g_Config.m_DbgDummies ; i++)
+		{
+			OnClientConnected(MAX_CLIENTS-i-1);
+		}
+	}
+#endif
+}
+
+void CGameContext::OnShutdown()
+{
+	delete m_pController;
+	m_pController = 0;
+	Clear();
 }
 
-void GAMECONTEXT::snap(int client_id)
+void CGameContext::OnSnap(int ClientId)
 {
-	world.snap(client_id);
-	controller->snap(client_id);
-	events.snap(client_id);
+	m_World.Snap(ClientId);
+	m_pController->Snap(ClientId);
+	m_Events.Snap(ClientId);
 	
 	for(int i = 0; i < MAX_CLIENTS; i++)
 	{
-		if(players[i])
-			players[i]->snap(client_id);
+		if(m_apPlayers[i])
+			m_apPlayers[i]->Snap(ClientId);
 	}
 }
+void CGameContext::OnPreSnap() {}
+void CGameContext::OnPostSnap()
+{
+	m_Events.Clear();
+}
+
+const char *CGameContext::Version() { return GAME_VERSION; }
+const char *CGameContext::NetVersion() { return GAME_NETVERSION; }
+
+IGameServer *CreateGameServer() { return new CGameContext; }