text: /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
  /* If you are missing that file, acquire a complete release at teeworlds.com.                */
  #include <new>
  
  #include <stdlib.h> // qsort
  #include <stdarg.h>
  
  #include <base/math.h>
  #include <base/system.h>
  #include <base/tl/threading.h>
  
  #include <engine/client.h>
  #include <engine/config.h>
  #include <engine/console.h>
  #include <engine/editor.h>
  #include <engine/engine.h>
  #include <engine/graphics.h>
  #include <engine/input.h>
  #include <engine/keys.h>
  #include <engine/map.h>
  #include <engine/masterserver.h>
  #include <engine/serverbrowser.h>
  #include <engine/sound.h>
  #include <engine/storage.h>
  #include <engine/textrender.h>
  
  #include <engine/shared/config.h>
  #include <engine/shared/compression.h>
  #include <engine/shared/datafile.h>
  #include <engine/shared/demo.h>
  #include <engine/shared/filecollection.h>
  #include <engine/shared/mapchecker.h>
  #include <engine/shared/network.h>
  #include <engine/shared/packer.h>
  #include <engine/shared/protocol.h>
  #include <engine/shared/ringbuffer.h>
  #include <engine/shared/snapshot.h>
  
  #include <game/version.h>
  
  #include <mastersrv/mastersrv.h>
  #include <versionsrv/versionsrv.h>
  
  #include "friends.h"
  #include "serverbrowser.h"
  #include "client.h"
  
  #if defined(CONF_FAMILY_WINDOWS)
  	#define _WIN32_WINNT 0x0501
  	#define WIN32_LEAN_AND_MEAN
  	#include <windows.h>
  #endif
  
  #include "SDL.h"
  #ifdef main
  #undef main
  #endif
  
  void CGraph::Init(float Min, float Max)
  {
  	m_Min = Min;
  	m_Max = Max;
  	m_Index = 0;
  }
  
  void CGraph::ScaleMax()
  {
  	int i = 0;
  	m_Max = 0;
  	for(i = 0; i < MAX_VALUES; i++)
  	{
  		if(m_aValues[i] > m_Max)
  			m_Max = m_aValues[i];
  	}
  }
  
  void CGraph::ScaleMin()
  {
  	int i = 0;
  	m_Min = m_Max;
  	for(i = 0; i < MAX_VALUES; i++)
  	{
  		if(m_aValues[i] < m_Min)
  			m_Min = m_aValues[i];
  	}
  }
  
  void CGraph::Add(float v, float r, float g, float b)
  {
  	m_Index = (m_Index+1)&(MAX_VALUES-1);
  	m_aValues[m_Index] = v;
  	m_aColors[m_Index][0] = r;
  	m_aColors[m_Index][1] = g;
  	m_aColors[m_Index][2] = b;
  }
  
  void CGraph::Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription)
  {
  	//m_pGraphics->BlendNormal();
  
  
  	pGraphics->TextureSet(-1);
  
  	pGraphics->QuadsBegin();
  	pGraphics->SetColor(0, 0, 0, 0.75f);
  	IGraphics::CQuadItem QuadItem(x, y, w, h);
  	pGraphics->QuadsDrawTL(&QuadItem, 1);
  	pGraphics->QuadsEnd();
  
  	pGraphics->LinesBegin();
  	pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.00f);
  	IGraphics::CLineItem LineItem(x, y+h/2, x+w, y+h/2);
  	pGraphics->LinesDraw(&LineItem, 1);
  	pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f);
  	IGraphics::CLineItem Array[2] = {
  		IGraphics::CLineItem(x, y+(h*3)/4, x+w, y+(h*3)/4),
  		IGraphics::CLineItem(x, y+h/4, x+w, y+h/4)};
  	pGraphics->LinesDraw(Array, 2);
  	for(int i = 1; i < MAX_VALUES; i++)
  	{
  		float a0 = (i-1)/(float)MAX_VALUES;
  		float a1 = i/(float)MAX_VALUES;
  		int i0 = (m_Index+i-1)&(MAX_VALUES-1);
  		int i1 = (m_Index+i)&(MAX_VALUES-1);
  
  		float v0 = (m_aValues[i0]-m_Min) / (m_Max-m_Min);
  		float v1 = (m_aValues[i1]-m_Min) / (m_Max-m_Min);
  
  		IGraphics::CColorVertex Array[2] = {
  			IGraphics::CColorVertex(0, m_aColors[i0][0], m_aColors[i0][1], m_aColors[i0][2], 0.75f),
  			IGraphics::CColorVertex(1, m_aColors[i1][0], m_aColors[i1][1], m_aColors[i1][2], 0.75f)};
  		pGraphics->SetColorVertex(Array, 2);
  		IGraphics::CLineItem LineItem(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h);
  		pGraphics->LinesDraw(&LineItem, 1);
  
  	}
  	pGraphics->LinesEnd();
  
  	pGraphics->TextureSet(Font);
  	pGraphics->QuadsText(x+2, y+h-16, 16, 1,1,1,1, pDescription);
  
  	char aBuf[32];
  	str_format(aBuf, sizeof(aBuf), "%.2f", m_Max);
  	pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, 1,1,1,1, aBuf);
  
  	str_format(aBuf, sizeof(aBuf), "%.2f", m_Min);
  	pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, 1,1,1,1, aBuf);
  }
  
  
  void CSmoothTime::Init(int64 Target)
  {
  	m_Snap = time_get();
  	m_Current = Target;
  	m_Target = Target;
  	m_aAdjustSpeed[0] = 0.3f;
  	m_aAdjustSpeed[1] = 0.3f;
  	m_Graph.Init(0.0f, 0.5f);
  }
  
  void CSmoothTime::SetAdjustSpeed(int Direction, float Value)
  {
  	m_aAdjustSpeed[Direction] = Value;
  }
  
  int64 CSmoothTime::Get(int64 Now)
  {
  	int64 c = m_Current + (Now - m_Snap);
  	int64 t = m_Target + (Now - m_Snap);
  
  	// it's faster to adjust upward instead of downward
  	// we might need to adjust these abit
  
  	float AdjustSpeed = m_aAdjustSpeed[0];
  	if(t > c)
  		AdjustSpeed = m_aAdjustSpeed[1];
  
  	float a = ((Now-m_Snap)/(float)time_freq()) * AdjustSpeed;
  	if(a > 1.0f)
  		a = 1.0f;
  
  	int64 r = c + (int64)((t-c)*a);
  
  	m_Graph.Add(a+0.5f,1,1,1);
  
  	return r;
  }
  
  void CSmoothTime::UpdateInt(int64 Target)
  {
  	int64 Now = time_get();
  	m_Current = Get(Now);
  	m_Snap = Now;
  	m_Target = Target;
  }
  
  void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection)
  {
  	int UpdateTimer = 1;
  
  	if(TimeLeft < 0)
  	{
  		int IsSpike = 0;
  		if(TimeLeft < -50)
  		{
  			IsSpike = 1;
  
  			m_SpikeCounter += 5;
  			if(m_SpikeCounter > 50)
  				m_SpikeCounter = 50;
  		}
  
  		if(IsSpike && m_SpikeCounter < 15)
  		{
  			// ignore this ping spike
  			UpdateTimer = 0;
  			pGraph->Add(TimeLeft, 1,1,0);
  		}
  		else
  		{
  			pGraph->Add(TimeLeft, 1,0,0);
  			if(m_aAdjustSpeed[AdjustDirection] < 30.0f)
  				m_aAdjustSpeed[AdjustDirection] *= 2.0f;
  		}
  	}
  	else
  	{
  		if(m_SpikeCounter)
  			m_SpikeCounter--;
  
  		pGraph->Add(TimeLeft, 0,1,0);
  
  		m_aAdjustSpeed[AdjustDirection] *= 0.95f;
  		if(m_aAdjustSpeed[AdjustDirection] < 2.0f)
  			m_aAdjustSpeed[AdjustDirection] = 2.0f;
  	}
  
  	if(UpdateTimer)
  		UpdateInt(Target);
  }
  
  
  CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta)
  {
  	m_pEditor = 0;
  	m_pInput = 0;
  	m_pGraphics = 0;
  	m_pSound = 0;
  	m_pGameClient = 0;
  	m_pMap = 0;
  	m_pConsole = 0;
  
  	m_RenderFrameTime = 0.0001f;
  	m_RenderFrameTimeLow = 1.0f;
  	m_RenderFrameTimeHigh = 0.0f;
  	m_RenderFrames = 0;
  	m_LastRenderTime = time_get();
  
  	m_GameTickSpeed = SERVER_TICK_SPEED;
  
  	m_WindowMustRefocus = 0;
  	m_SnapCrcErrors = 0;
  	m_AutoScreenshotRecycle = false;
  	m_EditorActive = false;
  
  	m_AckGameTick = -1;
  	m_CurrentRecvTick = 0;
  	m_RconAuthed = 0;
  
  	// version-checking
  	m_aVersionStr[0] = '0';
  	m_aVersionStr[1] = 0;
  
  	// pinging
  	m_PingStartTime = 0;
  
  	//
  	m_aCurrentMap[0] = 0;
  	m_CurrentMapCrc = 0;
  
  	//
  	m_aCmdConnect[0] = 0;
  
  	// map download
  	m_aMapdownloadFilename[0] = 0;
  	m_aMapdownloadName[0] = 0;
  	m_MapdownloadFile = 0;
  	m_MapdownloadChunk = 0;
  	m_MapdownloadCrc = 0;
  	m_MapdownloadAmount = -1;
  	m_MapdownloadTotalsize = -1;
  
  	m_CurrentServerInfoRequestTime = -1;
  
  	m_CurrentInput = 0;
  
  	m_State = IClient::STATE_OFFLINE;
  	m_aServerAddressStr[0] = 0;
  
  	mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
  	m_SnapshotStorage.Init();
  	m_RecivedSnapshots = 0;
  
  	m_VersionInfo.m_State = CVersionInfo::STATE_INIT;
  }
  
  // ----- send functions -----
  int CClient::SendMsg(CMsgPacker *pMsg, int Flags)
  {
  	return SendMsgEx(pMsg, Flags, false);
  }
  
  int CClient::SendMsgEx(CMsgPacker *pMsg, int Flags, bool System)
  {
  	CNetChunk Packet;
  
  	if(State() == IClient::STATE_OFFLINE)
  		return 0;
  
  	mem_zero(&Packet, sizeof(CNetChunk));
  
  	Packet.m_ClientID = 0;
  	Packet.m_pData = pMsg->Data();
  	Packet.m_DataSize = pMsg->Size();
  
  	// HACK: modify the message id in the packet and store the system flag
  	if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1)
  		dbg_break();
  
  	*((unsigned char*)Packet.m_pData) <<= 1;
  	if(System)
  		*((unsigned char*)Packet.m_pData) |= 1;
  
  	if(Flags&MSGFLAG_VITAL)
  		Packet.m_Flags |= NETSENDFLAG_VITAL;
  	if(Flags&MSGFLAG_FLUSH)
  		Packet.m_Flags |= NETSENDFLAG_FLUSH;
  
  	if(Flags&MSGFLAG_RECORD)
  	{
  		if(m_DemoRecorder.IsRecording())
  			m_DemoRecorder.RecordMessage(Packet.m_pData, Packet.m_DataSize);
  	}
  
  	if(!(Flags&MSGFLAG_NOSEND))
  		m_NetClient.Send(&Packet);
  	return 0;
  }
  
  void CClient::SendInfo()
  {
  	CMsgPacker Msg(NETMSG_INFO);
  	Msg.AddString(GameClient()->NetVersion(), 128);
  	Msg.AddString(g_Config.m_Password, 128);
  	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
  }
  
  
  void CClient::SendEnterGame()
  {
  	CMsgPacker Msg(NETMSG_ENTERGAME);
  	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
  }
  
  void CClient::SendReady()
  {
  	CMsgPacker Msg(NETMSG_READY);
  	SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
  }
  
  void CClient::RconAuth(const char *pName, const char *pPassword)
  {
  	if(RconAuthed())
  		return;
  
  	CMsgPacker Msg(NETMSG_RCON_AUTH);
  	Msg.AddString(pName, 32);
  	Msg.AddString(pPassword, 32);
  	Msg.AddInt(1);
  	SendMsgEx(&Msg, MSGFLAG_VITAL);
  }
  
  void CClient::Rcon(const char *pCmd)
  {
  	CMsgPacker Msg(NETMSG_RCON_CMD);
  	Msg.AddString(pCmd, 256);
  	SendMsgEx(&Msg, MSGFLAG_VITAL);
  }
  
  bool CClient::ConnectionProblems()
  {
  	return m_NetClient.GotProblems() != 0;
  }
  
  void CClient::DirectInput(int *pInput, int Size)
  {
  	int i;
  	CMsgPacker Msg(NETMSG_INPUT);
  	Msg.AddInt(m_AckGameTick);
  	Msg.AddInt(m_PredTick);
  	Msg.AddInt(Size);
  
  	for(i = 0; i < Size/4; i++)
  		Msg.AddInt(pInput[i]);
  
  	SendMsgEx(&Msg, 0);
  }
  
  
  void CClient::SendInput()
  {
  	int64 Now = time_get();
  
  	if(m_PredTick <= 0)
  		return;
  
  	// fetch input
  	int Size = GameClient()->OnSnapInput(m_aInputs[m_CurrentInput].m_aData);
  
  	if(!Size)
  		return;
  
  	// pack input
  	CMsgPacker Msg(NETMSG_INPUT);
  	Msg.AddInt(m_AckGameTick);
  	Msg.AddInt(m_PredTick);
  	Msg.AddInt(Size);
  
  	m_aInputs[m_CurrentInput].m_Tick = m_PredTick;
  	m_aInputs[m_CurrentInput].m_PredictedTime = m_PredictedTime.Get(Now);
  	m_aInputs[m_CurrentInput].m_Time = Now;
  
  	// pack it
  	for(int i = 0; i < Size/4; i++)
  		Msg.AddInt(m_aInputs[m_CurrentInput].m_aData[i]);
  
  	m_CurrentInput++;
  	m_CurrentInput%=200;
  
  	SendMsgEx(&Msg, MSGFLAG_FLUSH);
  }
  
  const char *CClient::LatestVersion()
  {
  	return m_aVersionStr;
  }
  
  // TODO: OPT: do this alot smarter!
  int *CClient::GetInput(int Tick)
  {
  	int Best = -1;
  	for(int i = 0; i < 200; i++)
  	{
  		if(m_aInputs[i].m_Tick <= Tick && (Best == -1 || m_aInputs[Best].m_Tick < m_aInputs[i].m_Tick))
  			Best = i;
  	}
  
  	if(Best != -1)
  		return (int *)m_aInputs[Best].m_aData;
  	return 0;
  }
  
  // ------ state handling -----
  void CClient::SetState(int s)
  {
  	if(m_State == IClient::STATE_QUITING)
  		return;
  
  	int Old = m_State;
  	if(g_Config.m_Debug)
  	{
  		char aBuf[128];
  		str_format(aBuf, sizeof(aBuf), "state change. last=%d current=%d", m_State, s);
  		m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
  	}
  	m_State = s;
  	if(Old != s)
  		GameClient()->OnStateChange(m_State, Old);
  }
  
  // called when the map is loaded and we should init for a new round
  void CClient::OnEnterGame()
  {
  	// reset input
  	int i;
  	for(i = 0; i < 200; i++)
  		m_aInputs[i].m_Tick = -1;
  	m_CurrentInput = 0;
  
  	// reset snapshots
  	m_aSnapshots[SNAP_CURRENT] = 0;
  	m_aSnapshots[SNAP_PREV] = 0;
  	m_SnapshotStorage.PurgeAll();
  	m_RecivedSnapshots = 0;
  	m_SnapshotParts = 0;
  	m_PredTick = 0;
  	m_CurrentRecvTick = 0;
  	m_CurGameTick = 0;
  	m_PrevGameTick = 0;
  }
  
  void CClient::EnterGame()
  {
  	if(State() == IClient::STATE_DEMOPLAYBACK)
  		return;
  
  	// now we will wait for two snapshots
  	// to finish the connection
  	SendEnterGame();
  	OnEnterGame();
  }
  
  void CClient::Connect(const char *pAddress)
  {
  	char aBuf[512];
  	int Port = 8303;
  
  	Disconnect();
  
  	str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr));
  
  	str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr);
  	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
  
  	ServerInfoRequest();
  
  	if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient.NetType()) != 0)
  	{
  		char aBufMsg[256];
  		str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf);
  		m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg);
  		net_host_lookup("localhost", &m_ServerAddress, m_NetClient.NetType());
  	}
  
  	m_RconAuthed = 0;
  	if(m_ServerAddress.port == 0)
  		m_ServerAddress.port = Port;
  	m_NetClient.Connect(&m_ServerAddress);
  	SetState(IClient::STATE_CONNECTING);
  
  	if(m_DemoRecorder.IsRecording())
  		DemoRecorder_Stop();
  
  	m_InputtimeMarginGraph.Init(-150.0f, 150.0f);
  	m_GametimeMarginGraph.Init(-150.0f, 150.0f);
  }
  
  void CClient::DisconnectWithReason(const char *pReason)
  {
  	char aBuf[512];
  	str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason?pReason:"unknown");
  	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
  
  	// stop demo playback and recorder
  	m_DemoPlayer.Stop();
  	DemoRecorder_Stop();
  
  	//
  	m_RconAuthed = 0;
  	m_UseTempRconCommands = 0;
  	m_pConsole->DeregisterTempAll();
  	m_NetClient.Disconnect(pReason);
  	SetState(IClient::STATE_OFFLINE);
  	m_pMap->Unload();
  
  	// disable all downloads
  	m_MapdownloadChunk = 0;
  	if(m_MapdownloadFile)
  		io_close(m_MapdownloadFile);
  	m_MapdownloadFile = 0;
  	m_MapdownloadCrc = 0;
  	m_MapdownloadTotalsize = -1;
  	m_MapdownloadAmount = 0;
  
  	// clear the current server info
  	mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
  	mem_zero(&m_ServerAddress, sizeof(m_ServerAddress));
  
  	// clear snapshots
  	m_aSnapshots[SNAP_CURRENT] = 0;
  	m_aSnapshots[SNAP_PREV] = 0;
  	m_RecivedSnapshots = 0;
  }
  
  void CClient::Disconnect()
  {
  	DisconnectWithReason(0);
  }
  
  
  void CClient::GetServerInfo(CServerInfo *pServerInfo)
  {
  	mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
  }
  
  void CClient::ServerInfoRequest()
  {
  	mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
  	m_CurrentServerInfoRequestTime = 0;
  }
  
  int CClient::LoadData()
  {
  	m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE);
  	return 1;
  }
  
  // ---
  
  void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem)
  {
  	CSnapshotItem *i;
  	dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
  	i = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(Index);
  	pItem->m_DataSize = m_aSnapshots[SnapID]->m_pAltSnap->GetItemSize(Index);
  	pItem->m_Type = i->Type();
  	pItem->m_ID = i->ID();
  	return (void *)i->Data();
  }
  
  void CClient::SnapInvalidateItem(int SnapID, int Index)
  {
  	CSnapshotItem *i;
  	dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
  	i = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(Index);
  	if(i)
  	{
  		if((char *)i < (char *)m_aSnapshots[SnapID]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[SnapID]->m_pAltSnap + m_aSnapshots[SnapID]->m_SnapSize)
  			m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
  		if((char *)i >= (char *)m_aSnapshots[SnapID]->m_pSnap && (char *)i < (char *)m_aSnapshots[SnapID]->m_pSnap + m_aSnapshots[SnapID]->m_SnapSize)
  			m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
  		i->m_TypeAndID = -1;
  	}
  }
  
  void *CClient::SnapFindItem(int SnapID, int Type, int ID)
  {
  	// TODO: linear search. should be fixed.
  	int i;
  
  	if(!m_aSnapshots[SnapID])
  		return 0x0;
  
  	for(i = 0; i < m_aSnapshots[SnapID]->m_pSnap->NumItems(); i++)
  	{
  		CSnapshotItem *pItem = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(i);
  		if(pItem->Type() == Type && pItem->ID() == ID)
  			return (void *)pItem->Data();
  	}
  	return 0x0;
  }
  
  int CClient::SnapNumItems(int SnapID)
  {
  	dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
  	if(!m_aSnapshots[SnapID])
  		return 0;
  	return m_aSnapshots[SnapID]->m_pSnap->NumItems();
  }
  
  void CClient::SnapSetStaticsize(int ItemType, int Size)
  {
  	m_SnapshotDelta.SetStaticsize(ItemType, Size);
  }
  
  
  void CClient::DebugRender()
  {
  	static NETSTATS Prev, Current;
  	static int64 LastSnap = 0;
  	static float FrameTimeAvg = 0;
  	int64 Now = time_get();
  	char aBuffer[512];
  
  	if(!g_Config.m_Debug)
  		return;
  
  	//m_pGraphics->BlendNormal();
  	Graphics()->TextureSet(m_DebugFont);
  	Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight());
  
  	if(time_get()-LastSnap > time_freq())
  	{
  		LastSnap = time_get();
  		Prev = Current;
  		net_stats(&Current);
  	}
  
  	/*
  		eth = 14
  		ip = 20
  		udp = 8
  		total = 42
  	*/
  	FrameTimeAvg = FrameTimeAvg*0.9f + m_RenderFrameTime*0.1f;
  	str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d",
  		m_CurGameTick, m_PredTick,
  		mem_stats()->allocated/1024,
  		mem_stats()->total_allocations,
  		Graphics()->MemoryUsage()/1024,
  		(int)(1.0f/FrameTimeAvg + 0.5f));
  	Graphics()->QuadsText(2, 2, 16, 1,1,1,1, aBuffer);
  
  
  	{
  		int SendPackets = (Current.sent_packets-Prev.sent_packets);
  		int SendBytes = (Current.sent_bytes-Prev.sent_bytes);
  		int SendTotal = SendBytes + SendPackets*42;
  		int RecvPackets = (Current.recv_packets-Prev.recv_packets);
  		int RecvBytes = (Current.recv_bytes-Prev.recv_bytes);
  		int RecvTotal = RecvBytes + RecvPackets*42;
  
  		if(!SendPackets) SendPackets++;
  		if(!RecvPackets) RecvPackets++;
  		str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
  			SendPackets, SendBytes, SendPackets*42, SendTotal, (SendTotal*8)/1024, SendBytes/SendPackets,
  			RecvPackets, RecvBytes, RecvPackets*42, RecvTotal, (RecvTotal*8)/1024, RecvBytes/RecvPackets);
  		Graphics()->QuadsText(2, 14, 16, 1,1,1,1, aBuffer);
  	}
  
  	// render rates
  	{
  		int y = 0;
  		int i;
  		for(i = 0; i < 256; i++)
  		{
  			if(m_SnapshotDelta.GetDataRate(i))
  			{
  				str_format(aBuffer, sizeof(aBuffer), "%4d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i)/8, m_SnapshotDelta.GetDataUpdates(i),
  					(m_SnapshotDelta.GetDataRate(i)/m_SnapshotDelta.GetDataUpdates(i))/8);
  				Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, aBuffer);
  				y++;
  			}
  		}
  	}
  
  	str_format(aBuffer, sizeof(aBuffer), "pred: %d ms",
  		(int)((m_PredictedTime.Get(Now)-m_GameTime.Get(Now))*1000/(float)time_freq()));
  	Graphics()->QuadsText(2, 70, 16, 1,1,1,1, aBuffer);
  
  	// render graphs
  	if(g_Config.m_DbgGraphs)
  	{
  		//Graphics()->MapScreen(0,0,400.0f,300.0f);
  		float w = Graphics()->ScreenWidth()/4.0f;
  		float h = Graphics()->ScreenHeight()/6.0f;
  		float sp = Graphics()->ScreenWidth()/100.0f;
  		float x = Graphics()->ScreenWidth()-w-sp;
  
  		m_FpsGraph.ScaleMax();
  		m_FpsGraph.ScaleMin();
  		m_FpsGraph.Render(Graphics(), m_DebugFont, x, sp*5, w, h, "FPS");
  		m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin");
  		m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin");
  	}
  }
  
  void CClient::Quit()
  {
  	SetState(IClient::STATE_QUITING);
  }
  
  const char *CClient::ErrorString()
  {
  	return m_NetClient.ErrorString();
  }
  
  void CClient::Render()
  {
  	if(g_Config.m_GfxClear)
  		Graphics()->Clear(1,1,0);
  
  	GameClient()->OnRender();
  	DebugRender();
  }
  
  const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc)
  {
  	static char aErrorMsg[128];
  
  	SetState(IClient::STATE_LOADING);
  
  	if(!m_pMap->Load(pFilename))
  	{
  		str_format(aErrorMsg, sizeof(aErrorMsg), "map '%s' not found", pFilename);
  		return aErrorMsg;
  	}
  
  	// get the crc of the map
  	if(m_pMap->Crc() != WantedCrc)
  	{
  		str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc);
  		m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aErrorMsg);
  		m_pMap->Unload();
  		return aErrorMsg;
  	}
  
  	// stop demo recording if we loaded a new map
  	DemoRecorder_Stop();
  
  	char aBuf[256];
  	str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename);
  	m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
  	m_RecivedSnapshots = 0;
  
  	str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap));
  	m_CurrentMapCrc = m_pMap->Crc();
  
  	return 0x0;
  }
  
  
  
  const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc)
  {
  	const char *pError = 0;
  	char aBuf[512];
  	str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc);
  	m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
  	SetState(IClient::STATE_LOADING);
  
  	// try the normal maps folder
  	str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
  	pError = LoadMap(pMapName, aBuf, WantedCrc);
  	if(!pError)
  		return pError;
  
  	// try the downloaded maps
  	str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
  	pError = LoadMap(pMapName, aBuf, WantedCrc);
  	if(!pError)
  		return pError;
  
  	// search for the map within subfolders
  	char aFilename[128];
  	str_format(aFilename, sizeof(aFilename), "%s.map", pMapName);
  	if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf)))
  		pError = LoadMap(pMapName, aBuf, WantedCrc);
  
  	return pError;
  }
  
  int CClient::PlayerScoreComp(const void *a, const void *b)
  {
  	CServerInfo::CClient *p0 = (CServerInfo::CClient *)a;
  	CServerInfo::CClient *p1 = (CServerInfo::CClient *)b;
  	if(p0->m_Player && !p1->m_Player)
  		return -1;
  	if(!p0->m_Player && p1->m_Player)
  		return 1;
  	if(p0->m_Score == p1->m_Score)
  		return 0;
  	if(p0->m_Score < p1->m_Score)
  		return 1;
  	return -1;
  }
  
  void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
  {
  	// version server
  	if(m_VersionInfo.m_State == CVersionInfo::STATE_READY && net_addr_comp(&pPacket->m_Address, &m_VersionInfo.m_VersionServeraddr.m_Addr) == 0)
  	{
  		// version info
  		if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(GAME_RELEASE_VERSION)) &&
  			mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0)
  
  		{
  			char *pVersionData = (char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
  			int VersionMatch = !mem_comp(pVersionData, GAME_RELEASE_VERSION, sizeof(GAME_RELEASE_VERSION));
  
  			char aVersion[sizeof(GAME_RELEASE_VERSION)];
  			str_copy(aVersion, pVersionData, sizeof(aVersion));
  
  			char aBuf[256];
  			str_format(aBuf, sizeof(aBuf), "version does %s (%s)",
  				VersionMatch ? "match" : "NOT match",
  				aVersion);
  			m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/version", aBuf);
  
  			// assume version is out of date when version-data doesn't match
  			if(!VersionMatch)
  			{
  				str_copy(m_aVersionStr, aVersion, sizeof(m_aVersionStr));
  			}
  
  			// request the map version list now
  			CNetChunk Packet;
  			mem_zero(&Packet, sizeof(Packet));
  			Packet.m_ClientID = -1;
  			Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
  			Packet.m_pData = VERSIONSRV_GETMAPLIST;
  			Packet.m_DataSize = sizeof(VERSIONSRV_GETMAPLIST);
  			Packet.m_Flags = NETSENDFLAG_CONNLESS;
  			m_NetClient.Send(&Packet);
  		}
  
  		// map version list
  		if(pPacket->m_DataSize >= (int)sizeof(VERSIONSRV_MAPLIST) &&
  			mem_comp(pPacket->m_pData, VERSIONSRV_MAPLIST, sizeof(VERSIONSRV_MAPLIST)) == 0)
  		{
  			int Size = pPacket->m_DataSize-sizeof(VERSIONSRV_MAPLIST);
  			int Num = Size/sizeof(CMapVersion);
  			m_MapChecker.AddMaplist((CMapVersion *)((char*)pPacket->m_pData+sizeof(VERSIONSRV_MAPLIST)), Num);
  		}
  	}
  
  	// server list from master server
  	if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) &&
  		mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0)
  	{
  		// check for valid master server address
  		bool Valid = false;
  		for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; ++i)
  		{
  			if(m_pMasterServer->IsValid(i))
  			{
  				NETADDR Addr = m_pMasterServer->GetAddr(i);
  				if(net_addr_comp(&pPacket->m_Address, &Addr) == 0)
  				{
  					Valid = true;
  					break;
  				}
  			}
  		}
  		if(!Valid)
  			return;
  
  		int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST);
  		int Num = Size/sizeof(CMastersrvAddr);
  		CMastersrvAddr *pAddrs = (CMastersrvAddr *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST));
  		for(int i = 0; i < Num; i++)
  		{
  			NETADDR Addr;
  
  			static char IPV4Mapping[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
  
  			// copy address
  			if(!mem_comp(IPV4Mapping, pAddrs[i].m_aIp, sizeof(IPV4Mapping)))
  			{
  				mem_zero(&Addr, sizeof(Addr));
  				Addr.type = NETTYPE_IPV4;
  				Addr.ip[0] = pAddrs[i].m_aIp[12];
  				Addr.ip[1] = pAddrs[i].m_aIp[13];
  				Addr.ip[2] = pAddrs[i].m_aIp[14];
  				Addr.ip[3] = pAddrs[i].m_aIp[15];
  			}
  			else
  			{
  				Addr.type = NETTYPE_IPV6;
  				mem_copy(Addr.ip, pAddrs[i].m_aIp, sizeof(Addr.ip));
  			}
  			Addr.port = (pAddrs[i].m_aPort[0]<<8) | pAddrs[i].m_aPort[1];
  
  			m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0);
  		}
  	}
  
  	// server info
  	if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
  	{
  		// we got ze info
  		CUnpacker Up;
  		CServerInfo Info = {0};
  
  		Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO));
  		int Token = str_toint(Up.GetString());
  		str_copy(Info.m_aVersion, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aVersion));
  		str_copy(Info.m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aName));
  		str_copy(Info.m_aMap, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aMap));
  		str_copy(Info.m_aGameType, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aGameType));
  		Info.m_Flags = str_toint(Up.GetString());
  		Info.m_NumPlayers = str_toint(Up.GetString());
  		Info.m_MaxPlayers = str_toint(Up.GetString());
  		Info.m_NumClients = str_toint(Up.GetString());
  		Info.m_MaxClients = str_toint(Up.GetString());
  
  		// don't add invalid info to the server browser list
  		if(Info.m_NumClients < 0 || Info.m_NumClients > MAX_CLIENTS || Info.m_MaxClients < 0 || Info.m_MaxClients > MAX_CLIENTS ||
  			Info.m_NumPlayers < 0 || Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > Info.m_MaxClients)
  			return;
  
  		net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true);
  
  		for(int i = 0; i < Info.m_NumClients; i++)
  		{
  			str_copy(Info.m_aClients[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aName));
  			str_copy(Info.m_aClients[i].m_aClan, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aClan));
  			Info.m_aClients[i].m_Country = str_toint(Up.GetString());
  			Info.m_aClients[i].m_Score = str_toint(Up.GetString());
  			Info.m_aClients[i].m_Player = str_toint(Up.GetString()) != 0 ? true : false;
  		}
  
  		if(!Up.Error())
  		{
  			// sort players
  			qsort(Info.m_aClients, Info.m_NumClients, sizeof(*Info.m_aClients), PlayerScoreComp);
  
  			if(net_addr_comp(&m_ServerAddress, &pPacket->m_Address) == 0)
  			{
  				mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo));
  				m_CurrentServerInfo.m_NetAddr = m_ServerAddress;
  				m_CurrentServerInfoRequestTime = -1;
  			}
  			else
  				m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info);
  		}
  	}
  }
  
  void CClient::ProcessServerPacket(CNetChunk *pPacket)
  {
  	CUnpacker Unpacker;
  	Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize);
  
  	// unpack msgid and system flag
  	int Msg = Unpacker.GetInt();
  	int Sys = Msg&1;
  	Msg >>= 1;
  
  	if(Unpacker.Error())
  		return;
  
  	if(Sys)
  	{
  		// system message
  		if(Msg == NETMSG_MAP_CHANGE)
  		{
  			const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES);
  			int MapCrc = Unpacker.GetInt();
  			int MapSize = Unpacker.GetInt();
  			int MapChunkNum = Unpacker.GetInt();
  			int MapChunkSize = Unpacker.GetInt();
  			const char *pError = 0;
  
  			if(Unpacker.Error())
  				return;
  
  			// check for valid standard map
  			if(!m_MapChecker.IsMapValid(pMap, MapCrc, MapSize))
  				pError = "invalid standard map";
  
  			// protect the player from nasty map names
  			for(int i = 0; pMap[i]; i++)
  			{
  				if(pMap[i] == '/' || pMap[i] == '\\')
  					pError = "strange character in map name";
  			}
  
  			if(MapSize <= 0)
  				pError = "invalid map size";
  
  			if(pError)
  				DisconnectWithReason(pError);
  			else
  			{
  				pError = LoadMapSearch(pMap, MapCrc);
  
  				if(!pError)
  				{
  					m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
  					SendReady();
  				}
  				else
  				{
  					// start map download
  					str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc);
  
  					char aBuf[256];
  					str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename);
  					m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf);
  
  					str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName));
  					if(m_MapdownloadFile)
  						io_close(m_MapdownloadFile);
  					m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
  					m_MapdownloadChunk = 0;
  					m_MapdownloadChunkNum = MapChunkNum;
  					m_MapDownloadChunkSize = MapChunkSize;
  					m_MapdownloadCrc = MapCrc;
  					m_MapdownloadTotalsize = MapSize;
  					m_MapdownloadAmount = 0;
  
  					// request first chunk package of map data
  					CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
  					SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
  
  					if(g_Config.m_Debug)
  						m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", "requested first chunk package");
  				}
  			}
  		}
  		else if(Msg == NETMSG_MAP_DATA)
  		{
  			if(!m_MapdownloadFile)
  				return;
  
  			int Size = min(m_MapDownloadChunkSize, m_MapdownloadTotalsize-m_MapdownloadAmount);
  			const unsigned char *pData = Unpacker.GetRaw(Size);
  			if(Unpacker.Error())
  				return;
   
  			io_write(m_MapdownloadFile, pData, Size);
  			++m_MapdownloadChunk;
  			m_MapdownloadAmount += Size;
  
  			if(m_MapdownloadAmount == m_MapdownloadTotalsize)
  			{
  				// map download complete
  				m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map");
  
  				if(m_MapdownloadFile)
  					io_close(m_MapdownloadFile);
  				m_MapdownloadFile = 0;
  				m_MapdownloadAmount = 0;
  				m_MapdownloadTotalsize = -1;
  
  				// load map
  				const char *pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc);
  				if(!pError)
  				{
  					m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
  					SendReady();
  				}
  				else
  					DisconnectWithReason(pError);
  			}
  			else if(m_MapdownloadChunk%m_MapdownloadChunkNum == 0)
  			{
  				// request next chunk package of map data
  				CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
  				SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
  
  				if(g_Config.m_Debug)
  					m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", "requested next chunk package");
  			}
  		}
  		else if(Msg == NETMSG_CON_READY)
  		{
  			GameClient()->OnConnected();
  		}
  		else if(Msg == NETMSG_PING)
  		{
  			CMsgPacker Msg(NETMSG_PING_REPLY);
  			SendMsgEx(&Msg, 0);
  		}
  		else if(Msg == NETMSG_RCON_CMD_ADD)
  		{
  			const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
  			const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC);
  			const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC);
  			if(Unpacker.Error() == 0)
  				m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp);
  		}
  		else if(Msg == NETMSG_RCON_CMD_REM)
  		{
  			const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
  			if(Unpacker.Error() == 0)
  				m_pConsole->DeregisterTemp(pName);
  		}
  		else if(Msg == NETMSG_RCON_AUTH_STATUS)
  		{
  			int Result = Unpacker.GetInt();
  			if(Unpacker.Error() == 0)
  				m_RconAuthed = Result;
  			int Old = m_UseTempRconCommands;
  			m_UseTempRconCommands = Unpacker.GetInt();
  			if(Unpacker.Error() != 0)
  				m_UseTempRconCommands = 0;
  			if(Old != 0 && m_UseTempRconCommands == 0)
  				m_pConsole->DeregisterTempAll();
  		}
  		else if(Msg == NETMSG_RCON_LINE)
  		{
  			const char *pLine = Unpacker.GetString();
  			if(Unpacker.Error() == 0)
  				GameClient()->OnRconLine(pLine);
  		}
  		else if(Msg == NETMSG_PING_REPLY)
  		{
  			char aBuf[256];
  			str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq());
  			m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf);
  		}
  		else if(Msg == NETMSG_INPUTTIMING)
  		{
  			int InputPredTick = Unpacker.GetInt();
  			int TimeLeft = Unpacker.GetInt();
  
  			// adjust our prediction time
  			int64 Target = 0;
  			for(int k = 0; k < 200; k++)
  			{
  				if(m_aInputs[k].m_Tick == InputPredTick)
  				{
  					Target = m_aInputs[k].m_PredictedTime + (time_get() - m_aInputs[k].m_Time);
  					Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq());
  					break;
  				}
  			}
  
  			if(Target)
  				m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1);
  		}
  		else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY)
  		{
  			int NumParts = 1;
  			int Part = 0;
  			int GameTick = Unpacker.GetInt();
  			int DeltaTick = GameTick-Unpacker.GetInt();
  			int PartSize = 0;
  			int Crc = 0;
  			int CompleteSize = 0;
  			const char *pData = 0;
  
  			// we are not allowed to process snapshot yet
  			if(State() < IClient::STATE_LOADING)
  				return;
  
  			if(Msg == NETMSG_SNAP)
  			{
  				NumParts = Unpacker.GetInt();
  				Part = Unpacker.GetInt();
  			}
  
  			if(Msg != NETMSG_SNAPEMPTY)
  			{
  				Crc = Unpacker.GetInt();
  				PartSize = Unpacker.GetInt();
  			}
  
  			pData = (const char *)Unpacker.GetRaw(PartSize);
  
  			if(Unpacker.Error())
  				return;
  
  			if(GameTick >= m_CurrentRecvTick)
  			{
  				if(GameTick != m_CurrentRecvTick)
  				{
  					m_SnapshotParts = 0;
  					m_CurrentRecvTick = GameTick;
  				}
  
  				// TODO: clean this up abit
  				mem_copy((char*)m_aSnapshotIncommingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize);
  				m_SnapshotParts |= 1<<Part;
  
  				if(m_SnapshotParts == (unsigned)((1<<NumParts)-1))
  				{
  					static CSnapshot Emptysnap;
  					CSnapshot *pDeltaShot = &Emptysnap;
  					int PurgeTick;
  					void *pDeltaData;
  					int DeltaSize;
  					unsigned char aTmpBuffer2[CSnapshot::MAX_SIZE];
  					unsigned char aTmpBuffer3[CSnapshot::MAX_SIZE];
  					CSnapshot *pTmpBuffer3 = (CSnapshot*)aTmpBuffer3;	// Fix compiler warning for strict-aliasing
  					int SnapSize;
  
  					CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize;
  
  					// reset snapshoting
  					m_SnapshotParts = 0;
  
  					// find snapshot that we should use as delta
  					Emptysnap.Clear();
  
  					// find delta
  					if(DeltaTick >= 0)
  					{
  						int DeltashotSize = m_SnapshotStorage.Get(DeltaTick, 0, &pDeltaShot, 0);
  
  						if(DeltashotSize < 0)
  						{
  							// couldn't find the delta snapshots that the server used
  							// to compress this snapshot. force the server to resync
  							if(g_Config.m_Debug)
  							{
  								char aBuf[256];
  								str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot");
  								m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
  							}
  
  							// ack snapshot
  							// TODO: combine this with the input message
  							m_AckGameTick = -1;
  							return;
  						}
  					}
  
  					// decompress snapshot
  					pDeltaData = m_SnapshotDelta.EmptyDelta();
  					DeltaSize = sizeof(int)*3;
  
  					if(CompleteSize)
  					{
  						int IntSize = CVariableInt::Decompress(m_aSnapshotIncommingData, CompleteSize, aTmpBuffer2);
  
  						if(IntSize < 0) // failure during decompression, bail
  							return;
  
  						pDeltaData = aTmpBuffer2;
  						DeltaSize = IntSize;
  					}
  
  					// unpack delta
  					PurgeTick = DeltaTick;
  					SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize);
  					if(SnapSize < 0)
  					{
  						m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "delta unpack failed!");
  						return;
  					}
  
  					if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc)
  					{
  						if(g_Config.m_Debug)
  						{
  							char aBuf[256];
  							str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
  								m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick);
  							m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
  						}
  
  						m_SnapCrcErrors++;
  						if(m_SnapCrcErrors > 10)
  						{
  							// to many errors, send reset
  							m_AckGameTick = -1;
  							SendInput();
  							m_SnapCrcErrors = 0;
  						}
  						return;
  					}
  					else
  					{
  						if(m_SnapCrcErrors)
  							m_SnapCrcErrors--;
  					}
  
  					// purge old snapshots
  					PurgeTick = DeltaTick;
  					if(m_aSnapshots[SNAP_PREV] && m_aSnapshots[SNAP_PREV]->m_Tick < PurgeTick)
  						PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
  					if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_CURRENT]->m_Tick < PurgeTick)
  						PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
  					m_SnapshotStorage.PurgeUntil(PurgeTick);
  
  					// add new
  					m_SnapshotStorage.Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1);
  
  					// add snapshot to demo
  					if(m_DemoRecorder.IsRecording())
  					{
  						// write snapshot
  						m_DemoRecorder.RecordSnapshot(GameTick, pTmpBuffer3, SnapSize);
  					}
  
  					// apply snapshot, cycle pointers
  					m_RecivedSnapshots++;
  
  					m_CurrentRecvTick = GameTick;
  
  					// we got two snapshots until we see us self as connected
  					if(m_RecivedSnapshots == 2)
  					{
  						// start at 200ms and work from there
  						m_PredictedTime.Init(GameTick*time_freq()/50);
  						m_PredictedTime.SetAdjustSpeed(1, 1000.0f);
  						m_GameTime.Init((GameTick-1)*time_freq()/50);
  						m_aSnapshots[SNAP_PREV] = m_SnapshotStorage.m_pFirst;
  						m_aSnapshots[SNAP_CURRENT] = m_SnapshotStorage.m_pLast;
  						m_LocalStartTime = time_get();
  						SetState(IClient::STATE_ONLINE);
  						DemoRecorder_HandleAutoStart();
  					}
  
  					// adjust game time
  					if(m_RecivedSnapshots > 2)
  					{
  						int64 Now = m_GameTime.Get(time_get());
  						int64 TickStart = GameTick*time_freq()/50;
  						int64 TimeLeft = (TickStart-Now)*1000 / time_freq();
  						m_GameTime.Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0);
  					}
  
  					// ack snapshot
  					m_AckGameTick = GameTick;
  				}
  			}
  		}
  	}
  	else
  	{
  		// game message
  		if(m_DemoRecorder.IsRecording())
  			m_DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
  
  		GameClient()->OnMessage(Msg, &Unpacker);
  	}
  }
  
  void CClient::PumpNetwork()
  {
  	m_NetClient.Update();
  
  	if(State() != IClient::STATE_DEMOPLAYBACK)
  	{
  		// check for errors
  		if(State() != IClient::STATE_OFFLINE && State() != IClient::STATE_QUITING && m_NetClient.State() == NETSTATE_OFFLINE)
  		{
  			SetState(IClient::STATE_OFFLINE);
  			Disconnect();
  			char aBuf[256];
  			str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient.ErrorString());
  			m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
  		}
  
  		//
  		if(State() == IClient::STATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE)
  		{
  			// we switched to online
  			m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info");
  			SetState(IClient::STATE_LOADING);
  			SendInfo();
  		}
  	}
  
  	// process packets
  	CNetChunk Packet;
  	while(m_NetClient.Recv(&Packet))
  	{
  		if(Packet.m_ClientID == -1)
  			ProcessConnlessPacket(&Packet);
  		else
  			ProcessServerPacket(&Packet);
  	}
  }
  
  void CClient::OnDemoPlayerSnapshot(void *pData, int Size)
  {
  	// update ticks, they could have changed
  	const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
  	CSnapshotStorage::CHolder *pTemp;
  	m_CurGameTick = pInfo->m_Info.m_CurrentTick;
  	m_PrevGameTick = pInfo->m_PreviousTick;
  
  	// handle snapshots
  	pTemp = m_aSnapshots[SNAP_PREV];
  	m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
  	m_aSnapshots[SNAP_CURRENT] = pTemp;
  
  	mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pSnap, pData, Size);
  	mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size);
  
  	GameClient()->OnNewSnapshot();
  }
  
  void CClient::OnDemoPlayerMessage(void *pData, int Size)
  {
  	CUnpacker Unpacker;
  	Unpacker.Reset(pData, Size);
  
  	// unpack msgid and system flag
  	int Msg = Unpacker.GetInt();
  	int Sys = Msg&1;
  	Msg >>= 1;
  
  	if(Unpacker.Error())
  		return;
  
  	if(!Sys)
  		GameClient()->OnMessage(Msg, &Unpacker);
  }
  /*
  const IDemoPlayer::CInfo *client_demoplayer_getinfo()
  {
  	static DEMOPLAYBACK_INFO ret;
  	const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info();
  	ret.first_tick = info->first_tick;
  	ret.last_tick = info->last_tick;
  	ret.current_tick = info->current_tick;
  	ret.paused = info->paused;
  	ret.speed = info->speed;
  	return &ret;
  }*/
  
  /*
  void DemoPlayer()->SetPos(float percent)
  {
  	demorec_playback_set(percent);
  }
  
  void DemoPlayer()->SetSpeed(float speed)
  {
  	demorec_playback_setspeed(speed);
  }
  
  void DemoPlayer()->SetPause(int paused)
  {
  	if(paused)
  		demorec_playback_pause();
  	else
  		demorec_playback_unpause();
  }*/
  
  void CClient::Update()
  {
  	if(State() == IClient::STATE_DEMOPLAYBACK)
  	{
  		m_DemoPlayer.Update();
  		if(m_DemoPlayer.IsPlaying())
  		{
  			// update timers
  			const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
  			m_CurGameTick = pInfo->m_Info.m_CurrentTick;
  			m_PrevGameTick = pInfo->m_PreviousTick;
  			m_GameIntraTick = pInfo->m_IntraTick;
  			m_GameTickTime = pInfo->m_TickTime;
  		}
  		else
  		{
  			// disconnect on error
  			Disconnect();
  		}
  	}
  	else if(State() == IClient::STATE_ONLINE && m_RecivedSnapshots >= 3)
  	{
  		// switch snapshot
  		int Repredict = 0;
  		int64 Freq = time_freq();
  		int64 Now = m_GameTime.Get(time_get());
  		int64 PredNow = m_PredictedTime.Get(time_get());
  
  		while(1)
  		{
  			CSnapshotStorage::CHolder *pCur = m_aSnapshots[SNAP_CURRENT];
  			int64 TickStart = (pCur->m_Tick)*time_freq()/50;
  
  			if(TickStart < Now)
  			{
  				CSnapshotStorage::CHolder *pNext = m_aSnapshots[SNAP_CURRENT]->m_pNext;
  				if(pNext)
  				{
  					m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
  					m_aSnapshots[SNAP_CURRENT] = pNext;
  
  					// set ticks
  					m_CurGameTick = m_aSnapshots[SNAP_CURRENT]->m_Tick;
  					m_PrevGameTick = m_aSnapshots[SNAP_PREV]->m_Tick;
  
  					if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
  					{
  						GameClient()->OnNewSnapshot();
  						Repredict = 1;
  					}
  				}
  				else
  					break;
  			}
  			else
  				break;
  		}
  
  		if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
  		{
  			int64 CurtickStart = (m_aSnapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50;
  			int64 PrevtickStart = (m_aSnapshots[SNAP_PREV]->m_Tick)*time_freq()/50;
  			int PrevPredTick = (int)(PredNow*50/time_freq());
  			int NewPredTick = PrevPredTick+1;
  
  			m_GameIntraTick = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
  			m_GameTickTime = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
  
  			CurtickStart = NewPredTick*time_freq()/50;
  			PrevtickStart = PrevPredTick*time_freq()/50;
  			m_PredIntraTick = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
  
  			if(NewPredTick < m_aSnapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
  			{
  				m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!");
  				m_PredictedTime.Init(m_aSnapshots[SNAP_CURRENT]->m_Tick*time_freq()/50);
  			}
  
  			if(NewPredTick > m_PredTick)
  			{
  				m_PredTick = NewPredTick;
  				Repredict = 1;
  
  				// send input
  				SendInput();
  			}
  		}
  
  		// only do sane predictions
  		if(Repredict)
  		{
  			if(m_PredTick > m_CurGameTick && m_PredTick < m_CurGameTick+50)
  				GameClient()->OnPredict();
  		}
  
  		// fetch server info if we don't have it
  		if(State() >= IClient::STATE_LOADING &&
  			m_CurrentServerInfoRequestTime >= 0 &&
  			time_get() > m_CurrentServerInfoRequestTime)
  		{
  			m_ServerBrowser.Request(m_ServerAddress);
  			m_CurrentServerInfoRequestTime = time_get()+time_freq()*2;
  		}
  	}
  
  	// STRESS TEST: join the server again
  	if(g_Config.m_DbgStress)
  	{
  		static int64 ActionTaken = 0;
  		int64 Now = time_get();
  		if(State() == IClient::STATE_OFFLINE)
  		{
  			if(Now > ActionTaken+time_freq()*2)
  			{
  				m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "reconnecting!");
  				Connect(g_Config.m_DbgStressServer);
  				ActionTaken = Now;
  			}
  		}
  		else
  		{
  			if(Now > ActionTaken+time_freq()*(10+g_Config.m_DbgStress))
  			{
  				m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "disconnecting!");
  				Disconnect();
  				ActionTaken = Now;
  			}
  		}
  	}
  
  	// pump the network
  	PumpNetwork();
  
  	// update the maser server registry
  	MasterServer()->Update();
  
  	// update the server browser
  	m_ServerBrowser.Update(m_ResortServerBrowser);
  	m_ResortServerBrowser = false;
  }
  
  void CClient::VersionUpdate()
  {
  	if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT)
  	{
  		Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_NetClient.NetType());
  		m_VersionInfo.m_State = CVersionInfo::STATE_START;
  	}
  	else if(m_VersionInfo.m_State == CVersionInfo::STATE_START)
  	{
  		if(m_VersionInfo.m_VersionServeraddr.m_Job.Status() == CJob::STATE_DONE)
  		{
  			CNetChunk Packet;
  
  			mem_zero(&Packet, sizeof(Packet));
  
  			m_VersionInfo.m_VersionServeraddr.m_Addr.port = VERSIONSRV_PORT;
  
  			Packet.m_ClientID = -1;
  			Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
  			Packet.m_pData = VERSIONSRV_GETVERSION;
  			Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION);
  			Packet.m_Flags = NETSENDFLAG_CONNLESS;
  
  			m_NetClient.Send(&Packet);
  			m_VersionInfo.m_State = CVersionInfo::STATE_READY;
  		}
  	}
  }
  
  void CClient::RegisterInterfaces()
  {
  	Kernel()->RegisterInterface(static_cast<IDemoRecorder*>(&m_DemoRecorder));
  	Kernel()->RegisterInterface(static_cast<IDemoPlayer*>(&m_DemoPlayer));
  	Kernel()->RegisterInterface(static_cast<IServerBrowser*>(&m_ServerBrowser));
  	Kernel()->RegisterInterface(static_cast<IFriends*>(&m_Friends));
  }
  
  void CClient::InitInterfaces()
  {
  	// fetch interfaces
  	m_pEngine = Kernel()->RequestInterface<IEngine>();
  	m_pEditor = Kernel()->RequestInterface<IEditor>();
  	//m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
  	m_pSound = Kernel()->RequestInterface<IEngineSound>();
  	m_pGameClient = Kernel()->RequestInterface<IGameClient>();
  	m_pInput = Kernel()->RequestInterface<IEngineInput>();
  	m_pMap = Kernel()->RequestInterface<IEngineMap>();
  	m_pMasterServer = Kernel()->RequestInterface<IEngineMasterServer>();
  	m_pStorage = Kernel()->RequestInterface<IStorage>();
  
  	//
  	m_ServerBrowser.SetBaseInfo(&m_NetClient, m_pGameClient->NetVersion());
  	m_Friends.Init();
  }
  
  void CClient::Run()
  {
  	m_LocalStartTime = time_get();
  	m_SnapshotParts = 0;
  
  	// init SDL
  	{
  		if(SDL_Init(0) < 0)
  		{
  			dbg_msg("client", "unable to init SDL base: %s", SDL_GetError());
  			return;
  		}
  
  		atexit(SDL_Quit); // ignore_convention
  	}
  
  	// init graphics
  	{
  		if(g_Config.m_GfxThreaded)
  			m_pGraphics = CreateEngineGraphicsThreaded();
  		else
  			m_pGraphics = CreateEngineGraphics();
  
  		bool RegisterFail = false;
  		RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IEngineGraphics*>(m_pGraphics)); // register graphics as both
  		RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast<IGraphics*>(m_pGraphics));
  
  		if(RegisterFail || m_pGraphics->Init() != 0)
  		{
  			dbg_msg("client", "couldn't init graphics");
  			return;
  		}
  	}
  
  	// init sound, allowed to fail
  	m_SoundInitFailed = Sound()->Init() != 0;
  
  	// open socket
  	{
  		NETADDR BindAddr;
  		if(g_Config.m_Bindaddr[0] && net_host_lookup(g_Config.m_Bindaddr, &BindAddr, NETTYPE_ALL) == 0)
  		{
  			// got bindaddr
  			BindAddr.type = NETTYPE_ALL;
  		}
  		else
  		{
  			mem_zero(&BindAddr, sizeof(BindAddr));
  			BindAddr.type = NETTYPE_ALL;
  		}
  		if(!m_NetClient.Open(BindAddr, 0))
  		{
  			dbg_msg("client", "couldn't open socket");
  			return;
  		}
  	}
  
  	// init font rendering
  	Kernel()->RequestInterface<IEngineTextRender>()->Init();
  
  	// init the input
  	Input()->Init();
  
  	// start refreshing addresses while we load
  	MasterServer()->RefreshAddresses(m_NetClient.NetType());
  
  	// init the editor
  	m_pEditor->Init();
  
  
  	// load data
  	if(!LoadData())
  		return;
  
  	GameClient()->OnInit();
  
  	char aBuf[256];
  	str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion());
  	m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
  
  	// connect to the server if wanted
  	/*
  	if(config.cl_connect[0] != 0)
  		Connect(config.cl_connect);
  	config.cl_connect[0] = 0;
  	*/
  
  	//
  	m_FpsGraph.Init(0.0f, 200.0f);
  
  	// never start with the editor
  	g_Config.m_ClEditor = 0;
  
  	Input()->MouseModeRelative();
  
  	// process pending commands
  	m_pConsole->StoreCommands(false);
  
  	while (1)
  	{
  		//
  		VersionUpdate();
  
  		// handle pending connects
  		if(m_aCmdConnect[0])
  		{
  			str_copy(g_Config.m_UiServerAddress, m_aCmdConnect, sizeof(g_Config.m_UiServerAddress));
  			Connect(m_aCmdConnect);
  			m_aCmdConnect[0] = 0;
  		}
  
  		// update input
  		if(Input()->Update())
  			break;	// SDL_QUIT
  
  		// update sound
  		Sound()->Update();
  
  		// release focus
  		if(!m_pGraphics->WindowActive())
  		{
  			if(m_WindowMustRefocus == 0)
  				Input()->MouseModeAbsolute();
  			m_WindowMustRefocus = 1;
  		}
  		else if (g_Config.m_DbgFocus && Input()->KeyPressed(KEY_ESCAPE))
  		{
  			Input()->MouseModeAbsolute();
  			m_WindowMustRefocus = 1;
  		}
  
  		// refocus
  		if(m_WindowMustRefocus && m_pGraphics->WindowActive())
  		{
  			if(m_WindowMustRefocus < 3)
  			{
  				Input()->MouseModeAbsolute();
  				m_WindowMustRefocus++;
  			}
  
  			if(m_WindowMustRefocus >= 3 || Input()->KeyPressed(KEY_MOUSE_1))
  			{
  				Input()->MouseModeRelative();
  				m_WindowMustRefocus = 0;
  			}
  		}
  
  		// panic quit button
  		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyPressed('q'))
  		{
  			Quit();
  			break;
  		}
  
  		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('d'))
  			g_Config.m_Debug ^= 1;
  
  		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('g'))
  			g_Config.m_DbgGraphs ^= 1;
  
  		if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('e'))
  		{
  			g_Config.m_ClEditor = g_Config.m_ClEditor^1;
  			Input()->MouseModeRelative();
  		}
  
  		/*
  		if(!gfx_window_open())
  			break;
  		*/
  
  		// render
  		{
  			if(g_Config.m_ClEditor)
  			{
  				if(!m_EditorActive)
  				{
  					GameClient()->OnActivateEditor();
  					m_EditorActive = true;
  				}
  			}
  			else if(m_EditorActive)
  				m_EditorActive = false;
  
  			Update();
  			
  			if(!g_Config.m_GfxAsyncRender || m_pGraphics->IsIdle())
  			{
  				m_RenderFrames++;
  
  				// update frametime
  				int64 Now = time_get();
  				m_RenderFrameTime = (Now - m_LastRenderTime) / (float)time_freq();
  				if(m_RenderFrameTime < m_RenderFrameTimeLow)
  					m_RenderFrameTimeLow = m_RenderFrameTime;
  				if(m_RenderFrameTime > m_RenderFrameTimeHigh)
  					m_RenderFrameTimeHigh = m_RenderFrameTime;
  				m_FpsGraph.Add(1.0f/m_RenderFrameTime, 1,1,1);
  
  				m_LastRenderTime = Now;
  
  				if(g_Config.m_DbgStress)
  				{
  					if((m_RenderFrames%10) == 0)
  					{
  						if(!m_EditorActive)
  							Render();
  						else
  						{
  							m_pEditor->UpdateAndRender();
  							DebugRender();
  						}
  						m_pGraphics->Swap();
  					}
  				}
  				else
  				{
  					if(!m_EditorActive)
  						Render();
  					else
  					{
  						m_pEditor->UpdateAndRender();
  						DebugRender();
  					}
  					m_pGraphics->Swap();
  				}
  			}
  		}
  
  		AutoScreenshot_Cleanup();
  
  		// check conditions
  		if(State() == IClient::STATE_QUITING)
  			break;
  
  		// beNice
  		if(g_Config.m_DbgStress)
  			thread_sleep(5);
  		else if(g_Config.m_ClCpuThrottle || !m_pGraphics->WindowActive())
  			thread_sleep(1);
  
  		if(g_Config.m_DbgHitch)
  		{
  			thread_sleep(g_Config.m_DbgHitch);
  			g_Config.m_DbgHitch = 0;
  		}
  
  		/*
  		if(ReportTime < time_get())
  		{
  			if(0 && g_Config.m_Debug)
  			{
  				dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
  					m_Frames/(float)(ReportInterval/time_freq()),
  					1.0f/m_RenderFrameTimeHigh,
  					1.0f/m_RenderFrameTimeLow,
  					m_NetClient.State());
  			}
  			m_RenderFrameTimeLow = 1;
  			m_RenderFrameTimeHigh = 0;
  			m_RenderFrames = 0;
  			ReportTime += ReportInterval;
  		}*/
  
  		// update local time
  		m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
  	}
  
  	GameClient()->OnShutdown();
  	Disconnect();
  
  	m_pGraphics->Shutdown();
  	m_pSound->Shutdown();
  
  	// shutdown SDL
  	{
  		SDL_Quit();
  	}
  }
  
  
  void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	str_copy(pSelf->m_aCmdConnect, pResult->GetString(0), sizeof(pSelf->m_aCmdConnect));
  }
  
  void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->Disconnect();
  }
  
  void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->Quit();
  }
  
  void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->Graphics()->Minimize();
  }
  
  void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  
  	CMsgPacker Msg(NETMSG_PING);
  	pSelf->SendMsgEx(&Msg, 0);
  	pSelf->m_PingStartTime = time_get();
  }
  
  void CClient::AutoScreenshot_Start()
  {
  	if(g_Config.m_ClAutoScreenshot)
  	{
  		Graphics()->TakeScreenshot("auto/autoscreen");
  		m_AutoScreenshotRecycle = true;
  	}
  }
  
  void CClient::AutoScreenshot_Cleanup()
  {
  	if(m_AutoScreenshotRecycle)
  	{
  		if(g_Config.m_ClAutoScreenshotMax)
  		{
  			// clean up auto taken screens
  			CFileCollection AutoScreens;
  			AutoScreens.Init(Storage(), "screenshots/auto", "autoscreen", ".png", g_Config.m_ClAutoScreenshotMax);
  		}
  		m_AutoScreenshotRecycle = false;
  	}
  }
  
  void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->Graphics()->TakeScreenshot(0);
  }
  
  void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->Rcon(pResult->GetString(0));
  }
  
  void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->RconAuth("", pResult->GetString(0));
  }
  
  void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	NETADDR Addr;
  	if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
  		pSelf->m_ServerBrowser.AddFavorite(Addr);
  }
  
  void CClient::Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	NETADDR Addr;
  	if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
  		pSelf->m_ServerBrowser.RemoveFavorite(Addr);
  }
  
  const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
  {
  	int Crc;
  	const char *pError;
  	Disconnect();
  	m_NetClient.ResetErrorString();
  
  	// try to start playback
  	m_DemoPlayer.SetListner(this);
  
  	if(m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType))
  		return "error loading demo";
  
  	// load map
  	Crc = (m_DemoPlayer.Info()->m_Header.m_aMapCrc[0]<<24)|
  		(m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)|
  		(m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)|
  		(m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]);
  	pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, Crc);
  	if(pError)
  	{
  		DisconnectWithReason(pError);
  		return pError;
  	}
  
  	GameClient()->OnConnected();
  
  	// setup buffers
  	mem_zero(m_aDemorecSnapshotData, sizeof(m_aDemorecSnapshotData));
  
  	m_aSnapshots[SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT];
  	m_aSnapshots[SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV];
  
  	m_aSnapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0];
  	m_aSnapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1];
  	m_aSnapshots[SNAP_CURRENT]->m_SnapSize = 0;
  	m_aSnapshots[SNAP_CURRENT]->m_Tick = -1;
  
  	m_aSnapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0];
  	m_aSnapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1];
  	m_aSnapshots[SNAP_PREV]->m_SnapSize = 0;
  	m_aSnapshots[SNAP_PREV]->m_Tick = -1;
  
  	// enter demo playback state
  	SetState(IClient::STATE_DEMOPLAYBACK);
  
  	m_DemoPlayer.Play();
  	GameClient()->OnEnterGame();
  
  	return 0;
  }
  
  void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL);
  }
  
  void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp)
  {
  	if(State() != IClient::STATE_ONLINE)
  		m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
  	else
  	{
  		char aFilename[128];
  		if(WithTimestamp)
  		{
  			char aDate[20];
  			str_timestamp(aDate, sizeof(aDate));
  			str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aDate);
  		}
  		else
  			str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename);
  		m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client");
  	}
  }
  
  void CClient::DemoRecorder_HandleAutoStart()
  {
  	if(g_Config.m_ClAutoDemoRecord)
  	{
  		DemoRecorder_Stop();
  		DemoRecorder_Start("auto/autorecord", true);
  		if(g_Config.m_ClAutoDemoMax)
  		{
  			// clean up auto recorded demos
  			CFileCollection AutoDemos;
  			AutoDemos.Init(Storage(), "demos/auto", "autorecord", ".demo", g_Config.m_ClAutoDemoMax);
  		}
  	}
  }
  
  void CClient::DemoRecorder_Stop()
  {
  	m_DemoRecorder.Stop();
  }
  
  void CClient::DemoRecorder_AddDemoMarker()
  {
  	m_DemoRecorder.AddDemoMarker();
  }
  
  void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	if(pResult->NumArguments())
  		pSelf->DemoRecorder_Start(pResult->GetString(0), false);
  	else
  		pSelf->DemoRecorder_Start("demo", true);
  }
  
  void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->DemoRecorder_Stop();
  }
  
  void CClient::Con_AddDemoMarker(IConsole::IResult *pResult, void *pUserData)
  {
  	CClient *pSelf = (CClient *)pUserData;
  	pSelf->DemoRecorder_AddDemoMarker();
  }
  
  void CClient::ServerBrowserUpdate()
  {
  	m_ResortServerBrowser = true;
  }
  
  void CClient::ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
  {
  	pfnCallback(pResult, pCallbackUserData);
  	if(pResult->NumArguments())
  		((CClient *)pUserData)->ServerBrowserUpdate();
  }
  
  void CClient::RegisterCommands()
  {
  	m_pConsole = Kernel()->RequestInterface<IConsole>();
  	// register server dummy commands for tab completion
  	m_pConsole->Register("kick", "i?r", CFGFLAG_SERVER, 0, 0, "Kick player with specified id for any reason");
  	m_pConsole->Register("ban", "s?ir", CFGFLAG_SERVER, 0, 0, "Ban player with ip/id for x minutes for any reason");
  	m_pConsole->Register("unban", "s", CFGFLAG_SERVER, 0, 0, "Unban ip");
  	m_pConsole->Register("bans", "", CFGFLAG_SERVER, 0, 0, "Show banlist");
  	m_pConsole->Register("status", "", CFGFLAG_SERVER, 0, 0, "List players");
  	m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, 0, 0, "Shut down");
  	m_pConsole->Register("record", "?s", CFGFLAG_SERVER, 0, 0, "Record to a file");
  	m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording");
  	m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map");
  
  	m_pConsole->Register("quit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds");
  	m_pConsole->Register("exit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds");
  	m_pConsole->Register("minimize", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Minimize, this, "Minimize Teeworlds");
  	m_pConsole->Register("connect", "s", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip");
  	m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server");
  	m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server");
  	m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot");
  	m_pConsole->Register("rcon", "r", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
  	m_pConsole->Register("rcon_auth", "s", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
  	m_pConsole->Register("play", "r", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Play, this, "Play the file specified");
  	m_pConsole->Register("record", "?s", CFGFLAG_CLIENT, Con_Record, this, "Record to the file");
  	m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording");
  	m_pConsole->Register("add_demomarker", "", CFGFLAG_CLIENT, Con_AddDemoMarker, this, "Add demo timeline marker");
  	m_pConsole->Register("add_favorite", "s", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite");
  	m_pConsole->Register("remove_favorite", "s", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites");
  
  	// used for server browser update
  	m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this);
  	m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this);
  	m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this);
  }
  
  static CClient *CreateClient()
  {
  	CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1));
  	mem_zero(pClient, sizeof(CClient));
  	return new(pClient) CClient;
  }
  
  /*
  	Server Time
  	Client Mirror Time
  	Client Predicted Time
  
  	Snapshot Latency
  		Downstream latency
  
  	Prediction Latency
  		Upstream latency
  */
  
  #if defined(CONF_PLATFORM_MACOSX)
  extern "C" int SDL_main(int argc, char **argv_) // ignore_convention
  {
  	const char **argv = const_cast<const char **>(argv_);
  #else
  int main(int argc, const char **argv) // ignore_convention
  {
  #endif
  #if defined(CONF_FAMILY_WINDOWS)
  	for(int i = 1; i < argc; i++) // ignore_convention
  	{
  		if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention
  		{
  			FreeConsole();
  			break;
  		}
  	}
  #endif
  
  	CClient *pClient = CreateClient();
  	IKernel *pKernel = IKernel::Create();
  	pKernel->RegisterInterface(pClient);
  	pClient->RegisterInterfaces();
  
  	// create the components
  	IEngine *pEngine = CreateEngine("Teeworlds");
  	IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT);
  	IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_CLIENT, argc, argv); // ignore_convention
  	IConfig *pConfig = CreateConfig();
  	IEngineSound *pEngineSound = CreateEngineSound();
  	IEngineInput *pEngineInput = CreateEngineInput();
  	IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
  	IEngineMap *pEngineMap = CreateEngineMap();
  	IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer();
  
  	{
  		bool RegisterFail = false;
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine);
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig);
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineSound*>(pEngineSound)); // register as both
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ISound*>(pEngineSound));
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineInput*>(pEngineInput)); // register as both
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IInput*>(pEngineInput));
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineTextRender*>(pEngineTextRender)); // register as both
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ITextRender*>(pEngineTextRender));
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap));
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer));
  
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor());
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient());
  		RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
  
  		if(RegisterFail)
  			return -1;
  	}
  
  	pEngine->Init();
  	pConfig->Init();
  	pEngineMasterServer->Init();
  	pEngineMasterServer->Load();
  
  	// register all console commands
  	pClient->RegisterCommands();
  
  	pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
  
  	// init client's interfaces
  	pClient->InitInterfaces();
  
  	// execute config file
  	pConsole->ExecuteFile("settings.cfg");
  
  	// execute autoexec file
  	pConsole->ExecuteFile("autoexec.cfg");
  
  	// parse the command line arguments
  	if(argc > 1) // ignore_convention
  		pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention
  
  	// restore empty config strings to their defaults
  	pConfig->RestoreStrings();
  
  	pClient->Engine()->InitLogfile();
  
  	// run the client
  	dbg_msg("client", "starting...");
  	pClient->Run();
  
  	// write down the config and quit
  	pConfig->Save();
  
  	return 0;
  }
  