/* (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 #include // qsort #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "friends.h" #include "serverbrowser.h" #include "client.h" #if defined(CONF_FAMILY_WINDOWS) #define _WIN32_WINNT 0x0501 #define WIN32_LEAN_AND_MEAN #include #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->QuadsBegin(); pGraphics->QuadsText(x+2, y+h-16, 16, pDescription); char aBuf[32]; str_format(aBuf, sizeof(aBuf), "%.2f", m_Max); pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, aBuf); str_format(aBuf, sizeof(aBuf), "%.2f", m_Min); pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, aBuf); pGraphics->QuadsEnd(); } 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()); Graphics()->QuadsBegin(); 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, 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, 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, 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, aBuffer); Graphics()->QuadsEnd(); // 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_ClShowEntities) { vec3 bg = HslToRgb(vec3(g_Config.m_ClBackgroundEntitiesHue/255.0f, g_Config.m_ClBackgroundEntitiesSat/255.0f, g_Config.m_ClBackgroundEntitiesLht/255.0f)); Graphics()->Clear(bg.r, bg.g, bg.b); } else { vec3 bg = HslToRgb(vec3(g_Config.m_ClBackgroundHue/255.0f, g_Config.m_ClBackgroundSat/255.0f, g_Config.m_ClBackgroundLht/255.0f)); Graphics()->Clear(bg.r, bg.g, bg.b); } GameClient()->OnRender(); DebugRender(); } vec3 CClient::GetColorV3(int v) { return HslToRgb(vec3(((v>>16)&0xff)/255.0f, ((v>>8)&0xff)/255.0f, 0.5f+(v&0xff)/255.0f*0.5f)); } 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 count from master server if(pPacket->m_DataSize == (int) sizeof(SERVERBROWSE_COUNT) + 2 && mem_comp(pPacket->m_pData, SERVERBROWSE_COUNT, sizeof(SERVERBROWSE_COUNT)) == 0) { unsigned char *pP = (unsigned char*) pPacket->m_pData; pP += sizeof(SERVERBROWSE_COUNT); int ServerCount = ((*pP)<<8) | *(pP+1); int ServerID = -1; for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; i++) { if(!m_pMasterServer->IsValid(i)) continue; NETADDR tmp = m_pMasterServer->GetAddr(i); if(net_addr_comp(&pPacket->m_Address, &tmp) == 0) { ServerID = i; break; } } if(ServerCount > -1 && ServerID != -1) { m_pMasterServer->SetCount(ServerID, ServerCount); if(g_Config.m_Debug) dbg_msg("MasterCount", "Server %d got %d servers", ServerID, ServerCount); } } // 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 unsigned 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}; CServerBrowser::CServerEntry *pEntry = m_ServerBrowser.Find(pPacket->m_Address); // Don't add info if we already got info64 if(pEntry && pEntry->m_Info.m_MaxClients > VANILLA_MAX_CLIENTS) return; 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) { if(m_CurrentServerInfo.m_MaxClients > VANILLA_MAX_CLIENTS) return; 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); if (strstr(Info.m_aGameType, "64") || strstr(Info.m_aName, "64")) { pEntry = m_ServerBrowser.Find(pPacket->m_Address); if (pEntry) { pEntry->m_Is64 = true; m_ServerBrowser.RequestImpl64(pEntry->m_Addr, pEntry); // Force a quick update m_ServerBrowser.QueueRequest(pEntry); // If it fails this will cause a retry } } } } // server info 64 if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO64) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO64, sizeof(SERVERBROWSE_INFO64)) == 0) { // we got ze info CUnpacker Up; CServerInfo NewInfo = {0}; CServerBrowser::CServerEntry *pEntry = m_ServerBrowser.Find(pPacket->m_Address); CServerInfo &Info = NewInfo; if (pEntry) Info = pEntry->m_Info; Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO64), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO64)); 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); int Offset = Up.GetInt(); for(int i = max(Offset, 0); i < max(Offset, 0) + 24 && i < MAX_CLIENTS; 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 if (Offset + 24 >= Info.m_NumClients) 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.SetInfo(pEntry, Info); 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(); const char *pError = 0; if(Unpacker.Error()) return; // check for valid standard map if(!m_MapChecker.IsMapValid(pMap, MapCrc, MapSize)) pError = "invalid standard map"; for(int i = 0; pMap[i]; i++) // protect the player from nasty map names { 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 { 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); m_MapdownloadChunk = 0; 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_MapdownloadCrc = MapCrc; m_MapdownloadTotalsize = MapSize; m_MapdownloadAmount = 0; CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); Msg.AddInt(m_MapdownloadChunk); SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); if(g_Config.m_Debug) { str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk); m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf); } } } } else if(Msg == NETMSG_MAP_DATA) { int Last = Unpacker.GetInt(); int MapCRC = Unpacker.GetInt(); int Chunk = Unpacker.GetInt(); int Size = Unpacker.GetInt(); const unsigned char *pData = Unpacker.GetRaw(Size); // check fior errors if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFile) return; io_write(m_MapdownloadFile, pData, Size); m_MapdownloadAmount += Size; if(Last) { const char *pError; 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 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 { // request new chunk m_MapdownloadChunk++; CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA); Msg.AddInt(m_MapdownloadChunk); SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH); if(g_Config.m_Debug) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk); m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf); } } } 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(); int64 Now = time_get(); // 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 + (Now - 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<= 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 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_CURRENT]->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_ClDDNetVersionServer, 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(&m_DemoRecorder)); Kernel()->RegisterInterface(static_cast(&m_DemoPlayer)); Kernel()->RegisterInterface(static_cast(&m_ServerBrowser)); Kernel()->RegisterInterface(static_cast(&m_Friends)); } void CClient::InitInterfaces() { // fetch interfaces m_pEngine = Kernel()->RequestInterface(); m_pEditor = Kernel()->RequestInterface(); //m_pGraphics = Kernel()->RequestInterface(); m_pSound = Kernel()->RequestInterface(); m_pGameClient = Kernel()->RequestInterface(); m_pInput = Kernel()->RequestInterface(); m_pMap = Kernel()->RequestInterface(); m_pMasterServer = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); // 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(m_pGraphics)); // register graphics as both RegisterFail = RegisterFail || !Kernel()->RegisterInterface(static_cast(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()->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); bool LastD = false; bool LastQ = false; bool LastE = false; bool LastG = 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(CtrlShiftKey('q', LastQ)) { Quit(); break; } if(CtrlShiftKey('d', LastD)) g_Config.m_Debug ^= 1; if(CtrlShiftKey('g', LastG)) g_Config.m_DbgGraphs ^= 1; if(CtrlShiftKey('e', LastE)) { 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_ClCpuThrottle) net_socket_read_wait(m_NetClient.m_Socket, g_Config.m_ClCpuThrottle); //thread_sleep(g_Config.m_ClCpuThrottle); else if(g_Config.m_DbgStress || !m_pGraphics->WindowActive()) thread_sleep(5); 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(); } } bool CClient::CtrlShiftKey(int Key, bool &Last) { if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && !Last && Input()->KeyPressed(Key)) { Last = true; return true; } else if (Last && !Input()->KeyPressed(Key)) Last = false; return false; } 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(); // 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); // DDRace #define CONSOLE_COMMAND(name, params, flags, callback, userdata, help) m_pConsole->Register(name, params, flags, 0, 0, help); #include } static CClient *CreateClient() { CClient *pClient = static_cast(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(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(pEngineSound)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineSound)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineInput)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineTextRender)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMap)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineMasterServer)); // register as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(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()->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; } // DDRace const char* CClient::GetCurrentMap() { return m_aCurrentMap; } int CClient::GetCurrentMapCrc() { return m_CurrentMapCrc; } const char* CClient::RaceRecordStart(const char *pFilename) { char aFilename[128]; str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", m_aCurrentMap, pFilename); if(State() != STATE_ONLINE) dbg_msg("demorec/record", "client is not online"); else m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client"); return m_aCurrentMap; } void CClient::RaceRecordStop() { if(m_DemoRecorder.IsRecording()) m_DemoRecorder.Stop(); } bool CClient::DemoIsRecording() { return m_DemoRecorder.IsRecording(); }