// copyright (c) 2007 magnus auvinen, see licence.txt for more info #include // qsort #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "client.h" #if defined(CONF_FAMILY_WINDOWS) #define _WIN32_WINNT 0x0500 #define NOGDI #include #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_FrameTime = 0.0001f; m_FrameTimeLow = 1.0f; m_FrameTimeHigh = 0.0f; m_Frames = 0; m_GameTickSpeed = SERVER_TICK_SPEED; m_WindowMustRefocus = 0; m_SnapCrcErrors = 0; 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_RecivedSnapshots = 0; m_VersionInfo.m_State = 0; } // ----- 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_PlayerName, 128); Msg.AddString(g_Config.m_ClanName, 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); } bool CClient::RconAuthed() { return m_RconAuthed; } 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); 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) { 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(); str_copy(aBuf, m_aServerAddressStr, sizeof(aBuf)); for(int k = 0; aBuf[k]; k++) { if(aBuf[k] == ':') { Port = str_toint(aBuf+k+1); aBuf[k] = 0; break; } } // TODO: IPv6 support if(net_host_lookup(aBuf, &m_ServerAddress, NETTYPE_IPV4) != 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, NETTYPE_IPV4); } m_RconAuthed = 0; m_ServerAddress.port = Port; m_NetClient.Connect(&m_ServerAddress); SetState(IClient::STATE_CONNECTING); if(m_DemoRecorder.IsRecording()) m_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(); m_DemoRecorder.Stop(); // m_RconAuthed = 0; 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", 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_FrameTime*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)); 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); if(g_Config.m_GfxClear || g_Config.m_GfxClearFull) Graphics()->Clear(0.3f,0.3f,0.6f); 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) { m_pMap->Unload(); str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc); return aErrorMsg; } // stop demo recording if we loaded a new map m_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); return pError; } int CClient::PlayerScoreComp(const void *a, const void *b) { CServerInfo::CPlayer *p0 = (CServerInfo::CPlayer *)a; CServerInfo::CPlayer *p1 = (CServerInfo::CPlayer *)b; if(p0->m_Score == p1->m_Score) return 0; if(p0->m_Score < p1->m_Score) return 1; return -1; } void CClient::ProcessPacket(CNetChunk *pPacket) { if(pPacket->m_ClientID == -1) { // connectionlesss if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) && mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0) { unsigned char *pVersionData = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION); int VersionMatch = !mem_comp(pVersionData, VERSION_DATA, sizeof(VERSION_DATA)); char aBuf[256]; str_format(aBuf, sizeof(aBuf), "version does %s (%d.%d.%d)", VersionMatch ? "match" : "NOT match", pVersionData[1], pVersionData[2], pVersionData[3]); 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_format(m_aVersionStr, sizeof(m_aVersionStr), "%d.%d.%d", pVersionData[1], pVersionData[2], pVersionData[3]); } } if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) && mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0) { int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST); int Num = Size/sizeof(MASTERSRV_ADDR); MASTERSRV_ADDR *pAddrs = (MASTERSRV_ADDR *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST)); int i; for(i = 0; i < Num; i++) { NETADDR Addr; // convert address mem_zero(&Addr, sizeof(Addr)); Addr.type = NETTYPE_IPV4; Addr.ip[0] = pAddrs[i].m_aIp[0]; Addr.ip[1] = pAddrs[i].m_aIp[1]; Addr.ip[2] = pAddrs[i].m_aIp[2]; Addr.ip[3] = pAddrs[i].m_aIp[3]; Addr.port = (pAddrs[i].m_aPort[1]<<8) | pAddrs[i].m_aPort[0]; m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0); } } { int PacketType = 0; if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0) PacketType = 2; if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_OLD_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_OLD_INFO, sizeof(SERVERBROWSE_OLD_INFO)) == 0) PacketType = 1; if(PacketType) { // we got ze info CUnpacker Up; CServerInfo Info = {0}; int Token = -1; Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO)); if(PacketType >= 2) 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_Progression = str_toint(Up.GetString()); Info.m_NumPlayers = str_toint(Up.GetString()); Info.m_MaxPlayers = str_toint(Up.GetString()); // don't add invalid info to the server browser list if(Info.m_NumPlayers < 0 || Info.m_NumPlayers > MAX_CLIENTS || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > MAX_CLIENTS) return; str_format(Info.m_aAddress, sizeof(Info.m_aAddress), "%d.%d.%d.%d:%d", pPacket->m_Address.ip[0], pPacket->m_Address.ip[1], pPacket->m_Address.ip[2], pPacket->m_Address.ip[3], pPacket->m_Address.port); for(int i = 0; i < Info.m_NumPlayers; i++) { str_copy(Info.m_aPlayers[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aPlayers[i].m_aName)); Info.m_aPlayers[i].m_Score = str_toint(Up.GetString()); } if(!Up.Error()) { // sort players qsort(Info.m_aPlayers, Info.m_NumPlayers, sizeof(*Info.m_aPlayers), 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 { if(PacketType == 2) m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info); else m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_OLD_INTERNET, -1, &Info); } } } } } else { 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(); const char *pError = 0; if(Unpacker.Error()) return; 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(pError) DisconnectWithReason(pError); else { pError = LoadMapSearch(pMap, MapCrc); if(!pError) { m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done"); SendReady(); GameClient()->OnConnected(); } 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)); m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE); m_MapdownloadCrc = MapCrc; m_MapdownloadTotalsize = -1; 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 TotalSize = Unpacker.GetInt(); int Size = Unpacker.GetInt(); const unsigned char *pData = Unpacker.GetRaw(Size); // check fior errors if(Unpacker.Error() || Size <= 0 || TotalSize <= 0 || !m_MapdownloadFile) return; io_write(m_MapdownloadFile, pData, Size); m_MapdownloadTotalsize = TotalSize; m_MapdownloadAmount += Size; if(Last) { const char *pError; m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map"); 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(); GameClient()->OnConnected(); } 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_PING) { CMsgPacker Msg(NETMSG_PING_REPLY); SendMsgEx(&Msg, 0); } else if(Msg == NETMSG_RCON_AUTH_STATUS) { int Result = Unpacker.GetInt(); if(Unpacker.Error() == 0) m_RconAuthed = Result; } 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<= 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); } // adjust game time { int64 Now = m_GameTime.Get(time_get()); int64 TickStart = GameTick*time_freq()/50; int64 TimeLeft = (TickStart-Now)*1000 / time_freq(); //st_update(&game_time, (game_tick-1)*time_freq()/50); 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)) ProcessPacket(&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_OFFLINE && 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; static float LastPredintra = 0; 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) { LastPredintra = m_PredIntraTick; m_PredTick = NewPredTick; Repredict = 1; // send input SendInput(); } LastPredintra = m_PredIntraTick; } // 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; } } } // pump the network PumpNetwork(); // update the maser server registry MasterServer()->Update(); // update the server browser m_ServerBrowser.Update(); } const char *CClient::UserDirectory() { static char saPath[1024] = {0}; fs_storage_path("Teeworlds", saPath, sizeof(saPath)); return saPath; } void CClient::VersionUpdate() { if(m_VersionInfo.m_State == 0) { m_Engine.HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer); m_VersionInfo.m_State++; } else if(m_VersionInfo.m_State == 1) { 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++; } } } void CClient::InitEngine(const char *pAppname) { m_Engine.Init(pAppname); } void CClient::RegisterInterfaces() { Kernel()->RegisterInterface(static_cast(&m_DemoRecorder)); Kernel()->RegisterInterface(static_cast(&m_DemoPlayer)); Kernel()->RegisterInterface(static_cast(&m_ServerBrowser)); } void CClient::InitInterfaces() { // fetch interfaces 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()); } void CClient::Run() { int64 ReportTime = time_get(); int64 ReportInterval = time_freq()*1; m_LocalStartTime = time_get(); m_SnapshotParts = 0; // init graphics if(m_pGraphics->Init() != 0) return; // init font rendering Kernel()->RequestInterface()->Init(); // init the input Input()->Init(); // start refreshing addresses while we load MasterServer()->RefreshAddresses(); // init the editor m_pEditor->Init(); // init sound, allowed to fail Sound()->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); // open socket { NETADDR BindAddr; mem_zero(&BindAddr, sizeof(BindAddr)); m_NetClient.Open(BindAddr, 0); } // 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, -1); while (1) { int64 FrameStartTime = time_get(); m_Frames++; // 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 Input()->Update(); // 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')) 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) { Update(); m_pEditor->UpdateAndRender(); m_pGraphics->Swap(); } else { Update(); if(g_Config.m_DbgStress) { if((m_Frames%10) == 0) { Render(); m_pGraphics->Swap(); } } else { Render(); m_pGraphics->Swap(); } } // 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_FrameTimeHigh, 1.0f/m_FrameTimeLow, m_NetClient.State()); } m_FrameTimeLow = 1; m_FrameTimeHigh = 0; m_Frames = 0; ReportTime += ReportInterval; } // update frametime m_FrameTime = (time_get()-FrameStartTime)/(float)time_freq(); if(m_FrameTime < m_FrameTimeLow) m_FrameTimeLow = m_FrameTime; if(m_FrameTime > m_FrameTimeHigh) m_FrameTimeHigh = m_FrameTime; m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq(); m_FpsGraph.Add(1.0f/m_FrameTime, 1,1,1); } GameClient()->OnShutdown(); Disconnect(); m_pGraphics->Shutdown(); m_pSound->Shutdown(); } void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData, int ClientID) { 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, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->Disconnect(); } void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->Quit(); } void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->Graphics()->Minimize(); } void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; CMsgPacker Msg(NETMSG_PING); pSelf->SendMsgEx(&Msg, 0); pSelf->m_PingStartTime = time_get(); } void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->Graphics()->TakeScreenshot(); } void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->Rcon(pResult->GetString(0)); } void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->RconAuth("", pResult->GetString(0)); } void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; NETADDR Addr; if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0) pSelf->m_ServerBrowser.AddFavorite(Addr); } // DDRace const char* CClient::GetCurrentMap() { return m_aCurrentMap; } 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(); } const char *CClient::DemoPlayer_Play(const char *pFilename) { 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)) return "error loading demo"; // load map Crc = (m_DemoPlayer.Info()->m_Header.m_aCrc[0]<<24)| (m_DemoPlayer.Info()->m_Header.m_aCrc[1]<<16)| (m_DemoPlayer.Info()->m_Header.m_aCrc[2]<<8)| (m_DemoPlayer.Info()->m_Header.m_aCrc[3]); pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMap, 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, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->DemoPlayer_Play(pResult->GetString(0)); } void CClient::DemoRecorder_Start(const char *pFilename) { if(State() != IClient::STATE_ONLINE) m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online"); else { char aFilename[512]; 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::Con_Record(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->DemoRecorder_Start(pResult->GetString(0)); } void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData, int ClientID) { CClient *pSelf = (CClient *)pUserData; pSelf->m_DemoRecorder.Stop(); } void CClient::RegisterCommands() { m_pConsole = Kernel()->RequestInterface(); // register server dummy commands for tab completion m_pConsole->Register("kick", "i", CFGFLAG_SERVER, 0, 0, "Kick player with specified id", 0); m_pConsole->Register("ban", "s?i", CFGFLAG_SERVER, 0, 0, "Ban player with ip/id for x minutes", 0); m_pConsole->Register("unban", "s", CFGFLAG_SERVER, 0, 0, "Unban ip", 0); m_pConsole->Register("bans", "", CFGFLAG_SERVER, 0, 0, "Show banlist", 0); m_pConsole->Register("status", "", CFGFLAG_SERVER, 0, 0, "List players", 0); m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, 0, 0, "Shut down", 0); m_pConsole->Register("record", "s", CFGFLAG_SERVER, 0, 0, "Record to a file", 0); m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording", 0); m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map", 0); m_pConsole->Register("quit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds", 0); m_pConsole->Register("exit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds", 0); m_pConsole->Register("minimize", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Minimize, this, "Minimize Teeworlds", 0); m_pConsole->Register("connect", "s", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip", 0); m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server", 0); m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server", 0); m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot", 0); m_pConsole->Register("rcon", "r", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon", 0); m_pConsole->Register("rcon_auth", "s", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon", 0); m_pConsole->Register("play", "r", CFGFLAG_CLIENT, Con_Play, this, "Play the file specified", 0); m_pConsole->Register("record", "s", CFGFLAG_CLIENT, Con_Record, this, "Record to the file", 0); m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording", 0); m_pConsole->Register("add_favorite", "s", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite", 0); } static CClient m_Client; /* 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, const char **argv) // ignore_convention #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 { ShowWindow(GetConsoleWindow(), SW_HIDE); break; } } #endif // init the engine dbg_msg("client", "starting..."); m_Client.InitEngine("Teeworlds"); IKernel *pKernel = IKernel::Create(); pKernel->RegisterInterface(&m_Client); m_Client.RegisterInterfaces(); // create the components IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT); IStorage *pStorage = CreateStorage("Teeworlds", argc, argv); // ignore_convention IConfig *pConfig = CreateConfig(); IEngineGraphics *pEngineGraphics = CreateEngineGraphics(); IEngineSound *pEngineSound = CreateEngineSound(); IEngineInput *pEngineInput = CreateEngineInput(); IEngineTextRender *pEngineTextRender = CreateEngineTextRender(); IEngineMap *pEngineMap = CreateEngineMap(); IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer(); { bool RegisterFail = false; RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pConsole)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pConfig)); RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineGraphics)); // register graphics as both RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast(pEngineGraphics)); 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; } pConfig->Init(); pEngineMasterServer->Init(m_Client.Engine()); pEngineMasterServer->Load(); // register all console commands m_Client.RegisterCommands(); pKernel->RequestInterface()->OnConsoleInit(); // init client's interfaces m_Client.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(); m_Client.Engine()->InitLogfile(); // run the client m_Client.Run(); // write down the config and quit pConfig->Save(); return 0; }