fixed editor zooming if gui is not active
[twcon.git] / src / engine / client / client.cpp
blobbec7d4d6d980f054abeafb1bff62b8a25e739a8d
1 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2 /* If you are missing that file, acquire a complete release at teeworlds.com. */
3 #include <new>
5 #include <stdlib.h> // qsort
6 #include <stdarg.h>
8 #include <base/math.h>
9 #include <base/system.h>
11 #include <engine/client.h>
12 #include <engine/config.h>
13 #include <engine/console.h>
14 #include <engine/editor.h>
15 #include <engine/engine.h>
16 #include <engine/graphics.h>
17 #include <engine/input.h>
18 #include <engine/keys.h>
19 #include <engine/map.h>
20 #include <engine/masterserver.h>
21 #include <engine/serverbrowser.h>
22 #include <engine/sound.h>
23 #include <engine/storage.h>
24 #include <engine/textrender.h>
26 #include <engine/shared/config.h>
27 #include <engine/shared/compression.h>
28 #include <engine/shared/datafile.h>
29 #include <engine/shared/demo.h>
30 #include <engine/shared/filecollection.h>
31 #include <engine/shared/mapchecker.h>
32 #include <engine/shared/network.h>
33 #include <engine/shared/packer.h>
34 #include <engine/shared/protocol.h>
35 #include <engine/shared/ringbuffer.h>
36 #include <engine/shared/snapshot.h>
38 #include <mastersrv/mastersrv.h>
39 #include <versionsrv/versionsrv.h>
41 #include "friends.h"
42 #include "serverbrowser.h"
43 #include "client.h"
45 #if defined(CONF_FAMILY_WINDOWS)
46 #define _WIN32_WINNT 0x0501
47 #define WIN32_LEAN_AND_MEAN
48 #include <windows.h>
49 #endif
52 void CGraph::Init(float Min, float Max)
54 m_Min = Min;
55 m_Max = Max;
56 m_Index = 0;
59 void CGraph::ScaleMax()
61 int i = 0;
62 m_Max = 0;
63 for(i = 0; i < MAX_VALUES; i++)
65 if(m_aValues[i] > m_Max)
66 m_Max = m_aValues[i];
70 void CGraph::ScaleMin()
72 int i = 0;
73 m_Min = m_Max;
74 for(i = 0; i < MAX_VALUES; i++)
76 if(m_aValues[i] < m_Min)
77 m_Min = m_aValues[i];
81 void CGraph::Add(float v, float r, float g, float b)
83 m_Index = (m_Index+1)&(MAX_VALUES-1);
84 m_aValues[m_Index] = v;
85 m_aColors[m_Index][0] = r;
86 m_aColors[m_Index][1] = g;
87 m_aColors[m_Index][2] = b;
90 void CGraph::Render(IGraphics *pGraphics, int Font, float x, float y, float w, float h, const char *pDescription)
92 //m_pGraphics->BlendNormal();
95 pGraphics->TextureSet(-1);
97 pGraphics->QuadsBegin();
98 pGraphics->SetColor(0, 0, 0, 0.75f);
99 IGraphics::CQuadItem QuadItem(x, y, w, h);
100 pGraphics->QuadsDrawTL(&QuadItem, 1);
101 pGraphics->QuadsEnd();
103 pGraphics->LinesBegin();
104 pGraphics->SetColor(0.95f, 0.95f, 0.95f, 1.00f);
105 IGraphics::CLineItem LineItem(x, y+h/2, x+w, y+h/2);
106 pGraphics->LinesDraw(&LineItem, 1);
107 pGraphics->SetColor(0.5f, 0.5f, 0.5f, 0.75f);
108 IGraphics::CLineItem Array[2] = {
109 IGraphics::CLineItem(x, y+(h*3)/4, x+w, y+(h*3)/4),
110 IGraphics::CLineItem(x, y+h/4, x+w, y+h/4)};
111 pGraphics->LinesDraw(Array, 2);
112 for(int i = 1; i < MAX_VALUES; i++)
114 float a0 = (i-1)/(float)MAX_VALUES;
115 float a1 = i/(float)MAX_VALUES;
116 int i0 = (m_Index+i-1)&(MAX_VALUES-1);
117 int i1 = (m_Index+i)&(MAX_VALUES-1);
119 float v0 = (m_aValues[i0]-m_Min) / (m_Max-m_Min);
120 float v1 = (m_aValues[i1]-m_Min) / (m_Max-m_Min);
122 IGraphics::CColorVertex Array[2] = {
123 IGraphics::CColorVertex(0, m_aColors[i0][0], m_aColors[i0][1], m_aColors[i0][2], 0.75f),
124 IGraphics::CColorVertex(1, m_aColors[i1][0], m_aColors[i1][1], m_aColors[i1][2], 0.75f)};
125 pGraphics->SetColorVertex(Array, 2);
126 IGraphics::CLineItem LineItem(x+a0*w, y+h-v0*h, x+a1*w, y+h-v1*h);
127 pGraphics->LinesDraw(&LineItem, 1);
130 pGraphics->LinesEnd();
132 pGraphics->TextureSet(Font);
133 pGraphics->QuadsText(x+2, y+h-16, 16, 1,1,1,1, pDescription);
135 char aBuf[32];
136 str_format(aBuf, sizeof(aBuf), "%.2f", m_Max);
137 pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+2, 16, 1,1,1,1, aBuf);
139 str_format(aBuf, sizeof(aBuf), "%.2f", m_Min);
140 pGraphics->QuadsText(x+w-8*str_length(aBuf)-8, y+h-16, 16, 1,1,1,1, aBuf);
144 void CSmoothTime::Init(int64 Target)
146 m_Snap = time_get();
147 m_Current = Target;
148 m_Target = Target;
149 m_aAdjustSpeed[0] = 0.3f;
150 m_aAdjustSpeed[1] = 0.3f;
151 m_Graph.Init(0.0f, 0.5f);
154 void CSmoothTime::SetAdjustSpeed(int Direction, float Value)
156 m_aAdjustSpeed[Direction] = Value;
159 int64 CSmoothTime::Get(int64 Now)
161 int64 c = m_Current + (Now - m_Snap);
162 int64 t = m_Target + (Now - m_Snap);
164 // it's faster to adjust upward instead of downward
165 // we might need to adjust these abit
167 float AdjustSpeed = m_aAdjustSpeed[0];
168 if(t > c)
169 AdjustSpeed = m_aAdjustSpeed[1];
171 float a = ((Now-m_Snap)/(float)time_freq()) * AdjustSpeed;
172 if(a > 1.0f)
173 a = 1.0f;
175 int64 r = c + (int64)((t-c)*a);
177 m_Graph.Add(a+0.5f,1,1,1);
179 return r;
182 void CSmoothTime::UpdateInt(int64 Target)
184 int64 Now = time_get();
185 m_Current = Get(Now);
186 m_Snap = Now;
187 m_Target = Target;
190 void CSmoothTime::Update(CGraph *pGraph, int64 Target, int TimeLeft, int AdjustDirection)
192 int UpdateTimer = 1;
194 if(TimeLeft < 0)
196 int IsSpike = 0;
197 if(TimeLeft < -50)
199 IsSpike = 1;
201 m_SpikeCounter += 5;
202 if(m_SpikeCounter > 50)
203 m_SpikeCounter = 50;
206 if(IsSpike && m_SpikeCounter < 15)
208 // ignore this ping spike
209 UpdateTimer = 0;
210 pGraph->Add(TimeLeft, 1,1,0);
212 else
214 pGraph->Add(TimeLeft, 1,0,0);
215 if(m_aAdjustSpeed[AdjustDirection] < 30.0f)
216 m_aAdjustSpeed[AdjustDirection] *= 2.0f;
219 else
221 if(m_SpikeCounter)
222 m_SpikeCounter--;
224 pGraph->Add(TimeLeft, 0,1,0);
226 m_aAdjustSpeed[AdjustDirection] *= 0.95f;
227 if(m_aAdjustSpeed[AdjustDirection] < 2.0f)
228 m_aAdjustSpeed[AdjustDirection] = 2.0f;
231 if(UpdateTimer)
232 UpdateInt(Target);
236 CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta), m_DemoRecorder(&m_SnapshotDelta)
238 m_pEditor = 0;
239 m_pInput = 0;
240 m_pGraphics = 0;
241 m_pSound = 0;
242 m_pGameClient = 0;
243 m_pMap = 0;
244 m_pConsole = 0;
246 m_FrameTime = 0.0001f;
247 m_FrameTimeLow = 1.0f;
248 m_FrameTimeHigh = 0.0f;
249 m_Frames = 0;
251 m_GameTickSpeed = SERVER_TICK_SPEED;
253 m_WindowMustRefocus = 0;
254 m_SnapCrcErrors = 0;
255 m_AutoScreenshotRecycle = false;
256 m_EditorActive = false;
258 m_AckGameTick = -1;
259 m_CurrentRecvTick = 0;
260 m_RconAuthed = 0;
262 // version-checking
263 m_aVersionStr[0] = '0';
264 m_aVersionStr[1] = 0;
266 // pinging
267 m_PingStartTime = 0;
270 m_aCurrentMap[0] = 0;
271 m_CurrentMapCrc = 0;
274 m_aCmdConnect[0] = 0;
276 // map download
277 m_aMapdownloadFilename[0] = 0;
278 m_aMapdownloadName[0] = 0;
279 m_MapdownloadFile = 0;
280 m_MapdownloadChunk = 0;
281 m_MapdownloadCrc = 0;
282 m_MapdownloadAmount = -1;
283 m_MapdownloadTotalsize = -1;
285 m_CurrentServerInfoRequestTime = -1;
287 m_CurrentInput = 0;
289 m_State = IClient::STATE_OFFLINE;
290 m_aServerAddressStr[0] = 0;
292 mem_zero(m_aSnapshots, sizeof(m_aSnapshots));
293 m_SnapshotStorage.Init();
294 m_RecivedSnapshots = 0;
296 m_VersionInfo.m_State = CVersionInfo::STATE_INIT;
299 // ----- send functions -----
300 int CClient::SendMsg(CMsgPacker *pMsg, int Flags)
302 return SendMsgEx(pMsg, Flags, false);
305 int CClient::SendMsgEx(CMsgPacker *pMsg, int Flags, bool System)
307 CNetChunk Packet;
309 if(State() == IClient::STATE_OFFLINE)
310 return 0;
312 mem_zero(&Packet, sizeof(CNetChunk));
314 Packet.m_ClientID = 0;
315 Packet.m_pData = pMsg->Data();
316 Packet.m_DataSize = pMsg->Size();
318 // HACK: modify the message id in the packet and store the system flag
319 if(*((unsigned char*)Packet.m_pData) == 1 && System && Packet.m_DataSize == 1)
320 dbg_break();
322 *((unsigned char*)Packet.m_pData) <<= 1;
323 if(System)
324 *((unsigned char*)Packet.m_pData) |= 1;
326 if(Flags&MSGFLAG_VITAL)
327 Packet.m_Flags |= NETSENDFLAG_VITAL;
328 if(Flags&MSGFLAG_FLUSH)
329 Packet.m_Flags |= NETSENDFLAG_FLUSH;
331 if(Flags&MSGFLAG_RECORD)
333 if(m_DemoRecorder.IsRecording())
334 m_DemoRecorder.RecordMessage(Packet.m_pData, Packet.m_DataSize);
337 if(!(Flags&MSGFLAG_NOSEND))
338 m_NetClient.Send(&Packet);
339 return 0;
342 void CClient::SendInfo()
344 CMsgPacker Msg(NETMSG_INFO);
345 Msg.AddString(GameClient()->NetVersion(), 128);
346 Msg.AddString(g_Config.m_Password, 128);
347 SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
351 void CClient::SendEnterGame()
353 CMsgPacker Msg(NETMSG_ENTERGAME);
354 SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
357 void CClient::SendReady()
359 CMsgPacker Msg(NETMSG_READY);
360 SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
363 void CClient::RconAuth(const char *pName, const char *pPassword)
365 if(RconAuthed())
366 return;
368 CMsgPacker Msg(NETMSG_RCON_AUTH);
369 Msg.AddString(pName, 32);
370 Msg.AddString(pPassword, 32);
371 Msg.AddInt(1);
372 SendMsgEx(&Msg, MSGFLAG_VITAL);
375 void CClient::Rcon(const char *pCmd)
377 CMsgPacker Msg(NETMSG_RCON_CMD);
378 Msg.AddString(pCmd, 256);
379 SendMsgEx(&Msg, MSGFLAG_VITAL);
382 bool CClient::ConnectionProblems()
384 return m_NetClient.GotProblems() != 0;
387 void CClient::DirectInput(int *pInput, int Size)
389 int i;
390 CMsgPacker Msg(NETMSG_INPUT);
391 Msg.AddInt(m_AckGameTick);
392 Msg.AddInt(m_PredTick);
393 Msg.AddInt(Size);
395 for(i = 0; i < Size/4; i++)
396 Msg.AddInt(pInput[i]);
398 SendMsgEx(&Msg, 0);
402 void CClient::SendInput()
404 int64 Now = time_get();
406 if(m_PredTick <= 0)
407 return;
409 // fetch input
410 int Size = GameClient()->OnSnapInput(m_aInputs[m_CurrentInput].m_aData);
412 if(!Size)
413 return;
415 // pack input
416 CMsgPacker Msg(NETMSG_INPUT);
417 Msg.AddInt(m_AckGameTick);
418 Msg.AddInt(m_PredTick);
419 Msg.AddInt(Size);
421 m_aInputs[m_CurrentInput].m_Tick = m_PredTick;
422 m_aInputs[m_CurrentInput].m_PredictedTime = m_PredictedTime.Get(Now);
423 m_aInputs[m_CurrentInput].m_Time = Now;
425 // pack it
426 for(int i = 0; i < Size/4; i++)
427 Msg.AddInt(m_aInputs[m_CurrentInput].m_aData[i]);
429 m_CurrentInput++;
430 m_CurrentInput%=200;
432 SendMsgEx(&Msg, MSGFLAG_FLUSH);
435 const char *CClient::LatestVersion()
437 return m_aVersionStr;
440 // TODO: OPT: do this alot smarter!
441 int *CClient::GetInput(int Tick)
443 int Best = -1;
444 for(int i = 0; i < 200; i++)
446 if(m_aInputs[i].m_Tick <= Tick && (Best == -1 || m_aInputs[Best].m_Tick < m_aInputs[i].m_Tick))
447 Best = i;
450 if(Best != -1)
451 return (int *)m_aInputs[Best].m_aData;
452 return 0;
455 // ------ state handling -----
456 void CClient::SetState(int s)
458 int Old = m_State;
459 if(g_Config.m_Debug)
461 char aBuf[128];
462 str_format(aBuf, sizeof(aBuf), "state change. last=%d current=%d", m_State, s);
463 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
465 m_State = s;
466 if(Old != s)
467 GameClient()->OnStateChange(m_State, Old);
470 // called when the map is loaded and we should init for a new round
471 void CClient::OnEnterGame()
473 // reset input
474 int i;
475 for(i = 0; i < 200; i++)
476 m_aInputs[i].m_Tick = -1;
477 m_CurrentInput = 0;
479 // reset snapshots
480 m_aSnapshots[SNAP_CURRENT] = 0;
481 m_aSnapshots[SNAP_PREV] = 0;
482 m_SnapshotStorage.PurgeAll();
483 m_RecivedSnapshots = 0;
484 m_SnapshotParts = 0;
485 m_PredTick = 0;
486 m_CurrentRecvTick = 0;
487 m_CurGameTick = 0;
488 m_PrevGameTick = 0;
491 void CClient::EnterGame()
493 if(State() == IClient::STATE_DEMOPLAYBACK)
494 return;
496 // now we will wait for two snapshots
497 // to finish the connection
498 SendEnterGame();
499 OnEnterGame();
502 void CClient::Connect(const char *pAddress)
504 char aBuf[512];
505 int Port = 8303;
507 Disconnect();
509 str_copy(m_aServerAddressStr, pAddress, sizeof(m_aServerAddressStr));
511 str_format(aBuf, sizeof(aBuf), "connecting to '%s'", m_aServerAddressStr);
512 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
514 ServerInfoRequest();
516 if(net_host_lookup(m_aServerAddressStr, &m_ServerAddress, m_NetClient.NetType()) != 0)
518 char aBufMsg[256];
519 str_format(aBufMsg, sizeof(aBufMsg), "could not find the address of %s, connecting to localhost", aBuf);
520 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBufMsg);
521 net_host_lookup("localhost", &m_ServerAddress, m_NetClient.NetType());
524 m_RconAuthed = 0;
525 if(m_ServerAddress.port == 0)
526 m_ServerAddress.port = Port;
527 m_NetClient.Connect(&m_ServerAddress);
528 SetState(IClient::STATE_CONNECTING);
530 if(m_DemoRecorder.IsRecording())
531 DemoRecorder_Stop();
533 m_InputtimeMarginGraph.Init(-150.0f, 150.0f);
534 m_GametimeMarginGraph.Init(-150.0f, 150.0f);
537 void CClient::DisconnectWithReason(const char *pReason)
539 char aBuf[512];
540 str_format(aBuf, sizeof(aBuf), "disconnecting. reason='%s'", pReason?pReason:"unknown");
541 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
543 // stop demo playback and recorder
544 m_DemoPlayer.Stop();
545 DemoRecorder_Stop();
548 m_RconAuthed = 0;
549 m_pConsole->DeregisterTempAll();
550 m_NetClient.Disconnect(pReason);
551 SetState(IClient::STATE_OFFLINE);
552 m_pMap->Unload();
554 // disable all downloads
555 m_MapdownloadChunk = 0;
556 if(m_MapdownloadFile)
557 io_close(m_MapdownloadFile);
558 m_MapdownloadFile = 0;
559 m_MapdownloadCrc = 0;
560 m_MapdownloadTotalsize = -1;
561 m_MapdownloadAmount = 0;
563 // clear the current server info
564 mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
565 mem_zero(&m_ServerAddress, sizeof(m_ServerAddress));
567 // clear snapshots
568 m_aSnapshots[SNAP_CURRENT] = 0;
569 m_aSnapshots[SNAP_PREV] = 0;
570 m_RecivedSnapshots = 0;
573 void CClient::Disconnect()
575 DisconnectWithReason(0);
579 void CClient::GetServerInfo(CServerInfo *pServerInfo)
581 mem_copy(pServerInfo, &m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
584 void CClient::ServerInfoRequest()
586 mem_zero(&m_CurrentServerInfo, sizeof(m_CurrentServerInfo));
587 m_CurrentServerInfoRequestTime = 0;
590 int CClient::LoadData()
592 m_DebugFont = Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, IGraphics::TEXLOAD_NORESAMPLE);
593 return 1;
596 // ---
598 void *CClient::SnapGetItem(int SnapID, int Index, CSnapItem *pItem)
600 CSnapshotItem *i;
601 dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
602 i = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(Index);
603 pItem->m_DataSize = m_aSnapshots[SnapID]->m_pAltSnap->GetItemSize(Index);
604 pItem->m_Type = i->Type();
605 pItem->m_ID = i->ID();
606 return (void *)i->Data();
609 void CClient::SnapInvalidateItem(int SnapID, int Index)
611 CSnapshotItem *i;
612 dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
613 i = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(Index);
614 if(i)
616 if((char *)i < (char *)m_aSnapshots[SnapID]->m_pAltSnap || (char *)i > (char *)m_aSnapshots[SnapID]->m_pAltSnap + m_aSnapshots[SnapID]->m_SnapSize)
617 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
618 if((char *)i >= (char *)m_aSnapshots[SnapID]->m_pSnap && (char *)i < (char *)m_aSnapshots[SnapID]->m_pSnap + m_aSnapshots[SnapID]->m_SnapSize)
619 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "snap invalidate problem");
620 i->m_TypeAndID = -1;
624 void *CClient::SnapFindItem(int SnapID, int Type, int ID)
626 // TODO: linear search. should be fixed.
627 int i;
629 if(!m_aSnapshots[SnapID])
630 return 0x0;
632 for(i = 0; i < m_aSnapshots[SnapID]->m_pSnap->NumItems(); i++)
634 CSnapshotItem *pItem = m_aSnapshots[SnapID]->m_pAltSnap->GetItem(i);
635 if(pItem->Type() == Type && pItem->ID() == ID)
636 return (void *)pItem->Data();
638 return 0x0;
641 int CClient::SnapNumItems(int SnapID)
643 dbg_assert(SnapID >= 0 && SnapID < NUM_SNAPSHOT_TYPES, "invalid SnapID");
644 if(!m_aSnapshots[SnapID])
645 return 0;
646 return m_aSnapshots[SnapID]->m_pSnap->NumItems();
649 void CClient::SnapSetStaticsize(int ItemType, int Size)
651 m_SnapshotDelta.SetStaticsize(ItemType, Size);
655 void CClient::DebugRender()
657 static NETSTATS Prev, Current;
658 static int64 LastSnap = 0;
659 static float FrameTimeAvg = 0;
660 int64 Now = time_get();
661 char aBuffer[512];
663 if(!g_Config.m_Debug)
664 return;
666 //m_pGraphics->BlendNormal();
667 Graphics()->TextureSet(m_DebugFont);
668 Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight());
670 if(time_get()-LastSnap > time_freq())
672 LastSnap = time_get();
673 Prev = Current;
674 net_stats(&Current);
678 eth = 14
679 ip = 20
680 udp = 8
681 total = 42
683 FrameTimeAvg = FrameTimeAvg*0.9f + m_FrameTime*0.1f;
684 str_format(aBuffer, sizeof(aBuffer), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d",
685 m_CurGameTick, m_PredTick,
686 mem_stats()->allocated/1024,
687 mem_stats()->total_allocations,
688 Graphics()->MemoryUsage()/1024,
689 (int)(1.0f/FrameTimeAvg));
690 Graphics()->QuadsText(2, 2, 16, 1,1,1,1, aBuffer);
694 int SendPackets = (Current.sent_packets-Prev.sent_packets);
695 int SendBytes = (Current.sent_bytes-Prev.sent_bytes);
696 int SendTotal = SendBytes + SendPackets*42;
697 int RecvPackets = (Current.recv_packets-Prev.recv_packets);
698 int RecvBytes = (Current.recv_bytes-Prev.recv_bytes);
699 int RecvTotal = RecvBytes + RecvPackets*42;
701 if(!SendPackets) SendPackets++;
702 if(!RecvPackets) RecvPackets++;
703 str_format(aBuffer, sizeof(aBuffer), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
704 SendPackets, SendBytes, SendPackets*42, SendTotal, (SendTotal*8)/1024, SendBytes/SendPackets,
705 RecvPackets, RecvBytes, RecvPackets*42, RecvTotal, (RecvTotal*8)/1024, RecvBytes/RecvPackets);
706 Graphics()->QuadsText(2, 14, 16, 1,1,1,1, aBuffer);
709 // render rates
711 int y = 0;
712 int i;
713 for(i = 0; i < 256; i++)
715 if(m_SnapshotDelta.GetDataRate(i))
717 str_format(aBuffer, sizeof(aBuffer), "%4d %20s: %8d %8d %8d", i, GameClient()->GetItemName(i), m_SnapshotDelta.GetDataRate(i)/8, m_SnapshotDelta.GetDataUpdates(i),
718 (m_SnapshotDelta.GetDataRate(i)/m_SnapshotDelta.GetDataUpdates(i))/8);
719 Graphics()->QuadsText(2, 100+y*12, 16, 1,1,1,1, aBuffer);
720 y++;
725 str_format(aBuffer, sizeof(aBuffer), "pred: %d ms",
726 (int)((m_PredictedTime.Get(Now)-m_GameTime.Get(Now))*1000/(float)time_freq()));
727 Graphics()->QuadsText(2, 70, 16, 1,1,1,1, aBuffer);
729 // render graphs
730 if(g_Config.m_DbgGraphs)
732 //Graphics()->MapScreen(0,0,400.0f,300.0f);
733 float w = Graphics()->ScreenWidth()/4.0f;
734 float h = Graphics()->ScreenHeight()/6.0f;
735 float sp = Graphics()->ScreenWidth()/100.0f;
736 float x = Graphics()->ScreenWidth()-w-sp;
738 m_FpsGraph.ScaleMax();
739 m_FpsGraph.ScaleMin();
740 m_FpsGraph.Render(Graphics(), m_DebugFont, x, sp*5, w, h, "FPS");
741 m_InputtimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp, w, h, "Prediction Margin");
742 m_GametimeMarginGraph.Render(Graphics(), m_DebugFont, x, sp*5+h+sp+h+sp, w, h, "Gametime Margin");
746 void CClient::Quit()
748 SetState(IClient::STATE_QUITING);
751 const char *CClient::ErrorString()
753 return m_NetClient.ErrorString();
756 void CClient::Render()
758 if(g_Config.m_GfxClear)
759 Graphics()->Clear(1,1,0);
761 GameClient()->OnRender();
762 DebugRender();
765 const char *CClient::LoadMap(const char *pName, const char *pFilename, unsigned WantedCrc)
767 static char aErrorMsg[128];
769 SetState(IClient::STATE_LOADING);
771 if(!m_pMap->Load(pFilename))
773 str_format(aErrorMsg, sizeof(aErrorMsg), "map '%s' not found", pFilename);
774 return aErrorMsg;
777 // get the crc of the map
778 if(m_pMap->Crc() != WantedCrc)
780 str_format(aErrorMsg, sizeof(aErrorMsg), "map differs from the server. %08x != %08x", m_pMap->Crc(), WantedCrc);
781 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aErrorMsg);
782 m_pMap->Unload();
783 return aErrorMsg;
786 // stop demo recording if we loaded a new map
787 DemoRecorder_Stop();
789 char aBuf[256];
790 str_format(aBuf, sizeof(aBuf), "loaded map '%s'", pFilename);
791 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
792 m_RecivedSnapshots = 0;
794 str_copy(m_aCurrentMap, pName, sizeof(m_aCurrentMap));
795 m_CurrentMapCrc = m_pMap->Crc();
797 return 0x0;
802 const char *CClient::LoadMapSearch(const char *pMapName, int WantedCrc)
804 const char *pError = 0;
805 char aBuf[512];
806 str_format(aBuf, sizeof(aBuf), "loading map, map=%s wanted crc=%08x", pMapName, WantedCrc);
807 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
808 SetState(IClient::STATE_LOADING);
810 // try the normal maps folder
811 str_format(aBuf, sizeof(aBuf), "maps/%s.map", pMapName);
812 pError = LoadMap(pMapName, aBuf, WantedCrc);
813 if(!pError)
814 return pError;
816 // try the downloaded maps
817 str_format(aBuf, sizeof(aBuf), "downloadedmaps/%s_%08x.map", pMapName, WantedCrc);
818 pError = LoadMap(pMapName, aBuf, WantedCrc);
819 if(!pError)
820 return pError;
822 // search for the map within subfolders
823 char aFilename[128];
824 str_format(aFilename, sizeof(aFilename), "%s.map", pMapName);
825 if(Storage()->FindFile(aFilename, "maps", IStorage::TYPE_ALL, aBuf, sizeof(aBuf)))
826 pError = LoadMap(pMapName, aBuf, WantedCrc);
828 return pError;
831 int CClient::PlayerScoreComp(const void *a, const void *b)
833 CServerInfo::CClient *p0 = (CServerInfo::CClient *)a;
834 CServerInfo::CClient *p1 = (CServerInfo::CClient *)b;
835 if(p0->m_Player && !p1->m_Player)
836 return -1;
837 if(!p0->m_Player && p1->m_Player)
838 return 1;
839 if(p0->m_Score == p1->m_Score)
840 return 0;
841 if(p0->m_Score < p1->m_Score)
842 return 1;
843 return -1;
846 void CClient::ProcessConnlessPacket(CNetChunk *pPacket)
848 // version server
849 if(m_VersionInfo.m_State == CVersionInfo::STATE_READY && net_addr_comp(&pPacket->m_Address, &m_VersionInfo.m_VersionServeraddr.m_Addr) == 0)
851 // version info
852 if(pPacket->m_DataSize == (int)(sizeof(VERSIONSRV_VERSION) + sizeof(VERSION_DATA)) &&
853 mem_comp(pPacket->m_pData, VERSIONSRV_VERSION, sizeof(VERSIONSRV_VERSION)) == 0)
856 unsigned char *pVersionData = (unsigned char*)pPacket->m_pData + sizeof(VERSIONSRV_VERSION);
857 int VersionMatch = !mem_comp(pVersionData, VERSION_DATA, sizeof(VERSION_DATA));
859 char aBuf[256];
860 str_format(aBuf, sizeof(aBuf), "version does %s (%d.%d.%d)",
861 VersionMatch ? "match" : "NOT match",
862 pVersionData[1], pVersionData[2], pVersionData[3]);
863 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/version", aBuf);
865 // assume version is out of date when version-data doesn't match
866 if (!VersionMatch)
868 str_format(m_aVersionStr, sizeof(m_aVersionStr), "%d.%d.%d", pVersionData[1], pVersionData[2], pVersionData[3]);
871 // request the map version list now
872 CNetChunk Packet;
873 mem_zero(&Packet, sizeof(Packet));
874 Packet.m_ClientID = -1;
875 Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
876 Packet.m_pData = VERSIONSRV_GETMAPLIST;
877 Packet.m_DataSize = sizeof(VERSIONSRV_GETMAPLIST);
878 Packet.m_Flags = NETSENDFLAG_CONNLESS;
879 m_NetClient.Send(&Packet);
882 // map version list
883 if(pPacket->m_DataSize >= (int)sizeof(VERSIONSRV_MAPLIST) &&
884 mem_comp(pPacket->m_pData, VERSIONSRV_MAPLIST, sizeof(VERSIONSRV_MAPLIST)) == 0)
886 int Size = pPacket->m_DataSize-sizeof(VERSIONSRV_MAPLIST);
887 int Num = Size/sizeof(CMapVersion);
888 m_MapChecker.AddMaplist((CMapVersion *)((char*)pPacket->m_pData+sizeof(VERSIONSRV_MAPLIST)), Num);
892 // server list from master server
893 if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_LIST) &&
894 mem_comp(pPacket->m_pData, SERVERBROWSE_LIST, sizeof(SERVERBROWSE_LIST)) == 0)
896 // check for valid master server address
897 bool Valid = false;
898 for(int i = 0; i < IMasterServer::MAX_MASTERSERVERS; ++i)
900 if(m_pMasterServer->IsValid(i))
902 NETADDR Addr = m_pMasterServer->GetAddr(i);
903 if(net_addr_comp(&pPacket->m_Address, &Addr) == 0)
905 Valid = true;
906 break;
910 if(!Valid)
911 return;
913 int Size = pPacket->m_DataSize-sizeof(SERVERBROWSE_LIST);
914 int Num = Size/sizeof(CMastersrvAddr);
915 CMastersrvAddr *pAddrs = (CMastersrvAddr *)((char*)pPacket->m_pData+sizeof(SERVERBROWSE_LIST));
916 for(int i = 0; i < Num; i++)
918 NETADDR Addr;
920 static char IPV4Mapping[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
922 // copy address
923 if(!mem_comp(IPV4Mapping, pAddrs[i].m_aIp, sizeof(IPV4Mapping)))
925 mem_zero(&Addr, sizeof(Addr));
926 Addr.type = NETTYPE_IPV4;
927 Addr.ip[0] = pAddrs[i].m_aIp[12];
928 Addr.ip[1] = pAddrs[i].m_aIp[13];
929 Addr.ip[2] = pAddrs[i].m_aIp[14];
930 Addr.ip[3] = pAddrs[i].m_aIp[15];
932 else
934 Addr.type = NETTYPE_IPV6;
935 mem_copy(Addr.ip, pAddrs[i].m_aIp, sizeof(Addr.ip));
937 Addr.port = (pAddrs[i].m_aPort[0]<<8) | pAddrs[i].m_aPort[1];
939 m_ServerBrowser.Set(Addr, IServerBrowser::SET_MASTER_ADD, -1, 0x0);
943 // server info
944 if(pPacket->m_DataSize >= (int)sizeof(SERVERBROWSE_INFO) && mem_comp(pPacket->m_pData, SERVERBROWSE_INFO, sizeof(SERVERBROWSE_INFO)) == 0)
946 // we got ze info
947 CUnpacker Up;
948 CServerInfo Info = {0};
950 Up.Reset((unsigned char*)pPacket->m_pData+sizeof(SERVERBROWSE_INFO), pPacket->m_DataSize-sizeof(SERVERBROWSE_INFO));
951 int Token = str_toint(Up.GetString());
952 str_copy(Info.m_aVersion, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aVersion));
953 str_copy(Info.m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aName));
954 str_copy(Info.m_aMap, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aMap));
955 str_copy(Info.m_aGameType, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aGameType));
956 Info.m_Flags = str_toint(Up.GetString());
957 Info.m_NumPlayers = str_toint(Up.GetString());
958 Info.m_MaxPlayers = str_toint(Up.GetString());
959 Info.m_NumClients = str_toint(Up.GetString());
960 Info.m_MaxClients = str_toint(Up.GetString());
962 // don't add invalid info to the server browser list
963 if(Info.m_NumClients < 0 || Info.m_NumClients > MAX_CLIENTS || Info.m_MaxClients < 0 || Info.m_MaxClients > MAX_CLIENTS ||
964 Info.m_NumPlayers < 0 || Info.m_NumPlayers > Info.m_NumClients || Info.m_MaxPlayers < 0 || Info.m_MaxPlayers > Info.m_MaxClients)
965 return;
967 net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress));
969 for(int i = 0; i < Info.m_NumClients; i++)
971 str_copy(Info.m_aClients[i].m_aName, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aName));
972 str_copy(Info.m_aClients[i].m_aClan, Up.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES), sizeof(Info.m_aClients[i].m_aClan));
973 Info.m_aClients[i].m_Country = str_toint(Up.GetString());
974 Info.m_aClients[i].m_Score = str_toint(Up.GetString());
975 Info.m_aClients[i].m_Player = str_toint(Up.GetString()) != 0 ? true : false;
978 if(!Up.Error())
980 // sort players
981 qsort(Info.m_aClients, Info.m_NumClients, sizeof(*Info.m_aClients), PlayerScoreComp);
983 if(net_addr_comp(&m_ServerAddress, &pPacket->m_Address) == 0)
985 mem_copy(&m_CurrentServerInfo, &Info, sizeof(m_CurrentServerInfo));
986 m_CurrentServerInfo.m_NetAddr = m_ServerAddress;
987 m_CurrentServerInfoRequestTime = -1;
989 else
990 m_ServerBrowser.Set(pPacket->m_Address, IServerBrowser::SET_TOKEN, Token, &Info);
995 void CClient::ProcessServerPacket(CNetChunk *pPacket)
997 CUnpacker Unpacker;
998 Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize);
1000 // unpack msgid and system flag
1001 int Msg = Unpacker.GetInt();
1002 int Sys = Msg&1;
1003 Msg >>= 1;
1005 if(Unpacker.Error())
1006 return;
1008 if(Sys)
1010 // system message
1011 if(Msg == NETMSG_MAP_CHANGE)
1013 const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES);
1014 int MapCrc = Unpacker.GetInt();
1015 int MapSize = Unpacker.GetInt();
1016 const char *pError = 0;
1018 if(Unpacker.Error())
1019 return;
1021 // check for valid standard map
1022 if(!m_MapChecker.IsMapValid(pMap, MapCrc, MapSize))
1023 pError = "invalid standard map";
1025 for(int i = 0; pMap[i]; i++) // protect the player from nasty map names
1027 if(pMap[i] == '/' || pMap[i] == '\\')
1028 pError = "strange character in map name";
1031 if(MapSize < 0)
1032 pError = "invalid map size";
1034 if(pError)
1035 DisconnectWithReason(pError);
1036 else
1038 pError = LoadMapSearch(pMap, MapCrc);
1040 if(!pError)
1042 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
1043 SendReady();
1045 else
1047 str_format(m_aMapdownloadFilename, sizeof(m_aMapdownloadFilename), "downloadedmaps/%s_%08x.map", pMap, MapCrc);
1049 char aBuf[256];
1050 str_format(aBuf, sizeof(aBuf), "starting to download map to '%s'", m_aMapdownloadFilename);
1051 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", aBuf);
1053 m_MapdownloadChunk = 0;
1054 str_copy(m_aMapdownloadName, pMap, sizeof(m_aMapdownloadName));
1055 if(m_MapdownloadFile)
1056 io_close(m_MapdownloadFile);
1057 m_MapdownloadFile = Storage()->OpenFile(m_aMapdownloadFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
1058 m_MapdownloadCrc = MapCrc;
1059 m_MapdownloadTotalsize = MapSize;
1060 m_MapdownloadAmount = 0;
1062 CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
1063 Msg.AddInt(m_MapdownloadChunk);
1064 SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
1066 if(g_Config.m_Debug)
1068 str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk);
1069 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf);
1074 else if(Msg == NETMSG_MAP_DATA)
1076 int Last = Unpacker.GetInt();
1077 int MapCRC = Unpacker.GetInt();
1078 int Chunk = Unpacker.GetInt();
1079 int Size = Unpacker.GetInt();
1080 const unsigned char *pData = Unpacker.GetRaw(Size);
1082 // check fior errors
1083 if(Unpacker.Error() || Size <= 0 || MapCRC != m_MapdownloadCrc || Chunk != m_MapdownloadChunk || !m_MapdownloadFile)
1084 return;
1086 io_write(m_MapdownloadFile, pData, Size);
1088 m_MapdownloadAmount += Size;
1090 if(Last)
1092 const char *pError;
1093 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "download complete, loading map");
1095 if(m_MapdownloadFile)
1096 io_close(m_MapdownloadFile);
1097 m_MapdownloadFile = 0;
1098 m_MapdownloadAmount = 0;
1099 m_MapdownloadTotalsize = -1;
1101 // load map
1102 pError = LoadMap(m_aMapdownloadName, m_aMapdownloadFilename, m_MapdownloadCrc);
1103 if(!pError)
1105 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client/network", "loading done");
1106 SendReady();
1108 else
1109 DisconnectWithReason(pError);
1111 else
1113 // request new chunk
1114 m_MapdownloadChunk++;
1116 CMsgPacker Msg(NETMSG_REQUEST_MAP_DATA);
1117 Msg.AddInt(m_MapdownloadChunk);
1118 SendMsgEx(&Msg, MSGFLAG_VITAL|MSGFLAG_FLUSH);
1120 if(g_Config.m_Debug)
1122 char aBuf[256];
1123 str_format(aBuf, sizeof(aBuf), "requested chunk %d", m_MapdownloadChunk);
1124 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", aBuf);
1128 else if(Msg == NETMSG_CON_READY)
1130 GameClient()->OnConnected();
1132 else if(Msg == NETMSG_PING)
1134 CMsgPacker Msg(NETMSG_PING_REPLY);
1135 SendMsgEx(&Msg, 0);
1137 else if(Msg == NETMSG_RCON_CMD_ADD)
1139 const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
1140 const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC);
1141 const char *pParams = Unpacker.GetString(CUnpacker::SANITIZE_CC);
1142 if(Unpacker.Error() == 0)
1143 m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp);
1145 else if(Msg == NETMSG_RCON_CMD_REM)
1147 const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC);
1148 if(Unpacker.Error() == 0)
1149 m_pConsole->DeregisterTemp(pName);
1151 else if(Msg == NETMSG_RCON_AUTH_STATUS)
1153 int Result = Unpacker.GetInt();
1154 if(Unpacker.Error() == 0)
1155 m_RconAuthed = Result;
1156 m_UseTempRconCommands = Unpacker.GetInt();
1157 if(Unpacker.Error() != 0)
1158 m_UseTempRconCommands = 0;
1160 else if(Msg == NETMSG_RCON_LINE)
1162 const char *pLine = Unpacker.GetString();
1163 if(Unpacker.Error() == 0)
1164 GameClient()->OnRconLine(pLine);
1166 else if(Msg == NETMSG_PING_REPLY)
1168 char aBuf[256];
1169 str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq());
1170 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf);
1172 else if(Msg == NETMSG_INPUTTIMING)
1174 int InputPredTick = Unpacker.GetInt();
1175 int TimeLeft = Unpacker.GetInt();
1177 // adjust our prediction time
1178 int64 Target = 0;
1179 for(int k = 0; k < 200; k++)
1181 if(m_aInputs[k].m_Tick == InputPredTick)
1183 Target = m_aInputs[k].m_PredictedTime + (time_get() - m_aInputs[k].m_Time);
1184 Target = Target - (int64)(((TimeLeft-PREDICTION_MARGIN)/1000.0f)*time_freq());
1185 break;
1189 if(Target)
1190 m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1);
1192 else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY)
1194 int NumParts = 1;
1195 int Part = 0;
1196 int GameTick = Unpacker.GetInt();
1197 int DeltaTick = GameTick-Unpacker.GetInt();
1198 int PartSize = 0;
1199 int Crc = 0;
1200 int CompleteSize = 0;
1201 const char *pData = 0;
1203 // we are not allowed to process snapshot yet
1204 if(State() < IClient::STATE_LOADING)
1205 return;
1207 if(Msg == NETMSG_SNAP)
1209 NumParts = Unpacker.GetInt();
1210 Part = Unpacker.GetInt();
1213 if(Msg != NETMSG_SNAPEMPTY)
1215 Crc = Unpacker.GetInt();
1216 PartSize = Unpacker.GetInt();
1219 pData = (const char *)Unpacker.GetRaw(PartSize);
1221 if(Unpacker.Error())
1222 return;
1224 if(GameTick >= m_CurrentRecvTick)
1226 if(GameTick != m_CurrentRecvTick)
1228 m_SnapshotParts = 0;
1229 m_CurrentRecvTick = GameTick;
1232 // TODO: clean this up abit
1233 mem_copy((char*)m_aSnapshotIncommingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize);
1234 m_SnapshotParts |= 1<<Part;
1236 if(m_SnapshotParts == (unsigned)((1<<NumParts)-1))
1238 static CSnapshot Emptysnap;
1239 CSnapshot *pDeltaShot = &Emptysnap;
1240 int PurgeTick;
1241 void *pDeltaData;
1242 int DeltaSize;
1243 unsigned char aTmpBuffer2[CSnapshot::MAX_SIZE];
1244 unsigned char aTmpBuffer3[CSnapshot::MAX_SIZE];
1245 CSnapshot *pTmpBuffer3 = (CSnapshot*)aTmpBuffer3; // Fix compiler warning for strict-aliasing
1246 int SnapSize;
1248 CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize;
1250 // reset snapshoting
1251 m_SnapshotParts = 0;
1253 // find snapshot that we should use as delta
1254 Emptysnap.Clear();
1256 // find delta
1257 if(DeltaTick >= 0)
1259 int DeltashotSize = m_SnapshotStorage.Get(DeltaTick, 0, &pDeltaShot, 0);
1261 if(DeltashotSize < 0)
1263 // couldn't find the delta snapshots that the server used
1264 // to compress this snapshot. force the server to resync
1265 if(g_Config.m_Debug)
1267 char aBuf[256];
1268 str_format(aBuf, sizeof(aBuf), "error, couldn't find the delta snapshot");
1269 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
1272 // ack snapshot
1273 // TODO: combine this with the input message
1274 m_AckGameTick = -1;
1275 return;
1279 // decompress snapshot
1280 pDeltaData = m_SnapshotDelta.EmptyDelta();
1281 DeltaSize = sizeof(int)*3;
1283 if(CompleteSize)
1285 int IntSize = CVariableInt::Decompress(m_aSnapshotIncommingData, CompleteSize, aTmpBuffer2);
1287 if(IntSize < 0) // failure during decompression, bail
1288 return;
1290 pDeltaData = aTmpBuffer2;
1291 DeltaSize = IntSize;
1294 // unpack delta
1295 PurgeTick = DeltaTick;
1296 SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize);
1297 if(SnapSize < 0)
1299 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "delta unpack failed!");
1300 return;
1303 if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc)
1305 if(g_Config.m_Debug)
1307 char aBuf[256];
1308 str_format(aBuf, sizeof(aBuf), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
1309 m_SnapCrcErrors, GameTick, Crc, pTmpBuffer3->Crc(), CompleteSize, DeltaTick);
1310 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
1313 m_SnapCrcErrors++;
1314 if(m_SnapCrcErrors > 10)
1316 // to many errors, send reset
1317 m_AckGameTick = -1;
1318 SendInput();
1319 m_SnapCrcErrors = 0;
1321 return;
1323 else
1325 if(m_SnapCrcErrors)
1326 m_SnapCrcErrors--;
1329 // purge old snapshots
1330 PurgeTick = DeltaTick;
1331 if(m_aSnapshots[SNAP_PREV] && m_aSnapshots[SNAP_PREV]->m_Tick < PurgeTick)
1332 PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
1333 if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_CURRENT]->m_Tick < PurgeTick)
1334 PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick;
1335 m_SnapshotStorage.PurgeUntil(PurgeTick);
1337 // add new
1338 m_SnapshotStorage.Add(GameTick, time_get(), SnapSize, pTmpBuffer3, 1);
1340 // add snapshot to demo
1341 if(m_DemoRecorder.IsRecording())
1343 // write snapshot
1344 m_DemoRecorder.RecordSnapshot(GameTick, pTmpBuffer3, SnapSize);
1347 // apply snapshot, cycle pointers
1348 m_RecivedSnapshots++;
1350 m_CurrentRecvTick = GameTick;
1352 // we got two snapshots until we see us self as connected
1353 if(m_RecivedSnapshots == 2)
1355 // start at 200ms and work from there
1356 m_PredictedTime.Init(GameTick*time_freq()/50);
1357 m_PredictedTime.SetAdjustSpeed(1, 1000.0f);
1358 m_GameTime.Init((GameTick-1)*time_freq()/50);
1359 m_aSnapshots[SNAP_PREV] = m_SnapshotStorage.m_pFirst;
1360 m_aSnapshots[SNAP_CURRENT] = m_SnapshotStorage.m_pLast;
1361 m_LocalStartTime = time_get();
1362 SetState(IClient::STATE_ONLINE);
1363 DemoRecorder_HandleAutoStart();
1366 // adjust game time
1367 if(m_RecivedSnapshots > 2)
1369 int64 Now = m_GameTime.Get(time_get());
1370 int64 TickStart = GameTick*time_freq()/50;
1371 int64 TimeLeft = (TickStart-Now)*1000 / time_freq();
1372 m_GameTime.Update(&m_GametimeMarginGraph, (GameTick-1)*time_freq()/50, TimeLeft, 0);
1375 // ack snapshot
1376 m_AckGameTick = GameTick;
1381 else
1383 // game message
1384 if(m_DemoRecorder.IsRecording())
1385 m_DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize);
1387 GameClient()->OnMessage(Msg, &Unpacker);
1391 void CClient::PumpNetwork()
1393 m_NetClient.Update();
1395 if(State() != IClient::STATE_DEMOPLAYBACK)
1397 // check for errors
1398 if(State() != IClient::STATE_OFFLINE && State() != IClient::STATE_QUITING && m_NetClient.State() == NETSTATE_OFFLINE)
1400 SetState(IClient::STATE_OFFLINE);
1401 Disconnect();
1402 char aBuf[256];
1403 str_format(aBuf, sizeof(aBuf), "offline error='%s'", m_NetClient.ErrorString());
1404 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
1408 if(State() == IClient::STATE_CONNECTING && m_NetClient.State() == NETSTATE_ONLINE)
1410 // we switched to online
1411 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", "connected, sending info");
1412 SetState(IClient::STATE_LOADING);
1413 SendInfo();
1417 // process packets
1418 CNetChunk Packet;
1419 while(m_NetClient.Recv(&Packet))
1421 if(Packet.m_ClientID == -1)
1422 ProcessConnlessPacket(&Packet);
1423 else
1424 ProcessServerPacket(&Packet);
1428 void CClient::OnDemoPlayerSnapshot(void *pData, int Size)
1430 // update ticks, they could have changed
1431 const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
1432 CSnapshotStorage::CHolder *pTemp;
1433 m_CurGameTick = pInfo->m_Info.m_CurrentTick;
1434 m_PrevGameTick = pInfo->m_PreviousTick;
1436 // handle snapshots
1437 pTemp = m_aSnapshots[SNAP_PREV];
1438 m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
1439 m_aSnapshots[SNAP_CURRENT] = pTemp;
1441 mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pSnap, pData, Size);
1442 mem_copy(m_aSnapshots[SNAP_CURRENT]->m_pAltSnap, pData, Size);
1444 GameClient()->OnNewSnapshot();
1447 void CClient::OnDemoPlayerMessage(void *pData, int Size)
1449 CUnpacker Unpacker;
1450 Unpacker.Reset(pData, Size);
1452 // unpack msgid and system flag
1453 int Msg = Unpacker.GetInt();
1454 int Sys = Msg&1;
1455 Msg >>= 1;
1457 if(Unpacker.Error())
1458 return;
1460 if(!Sys)
1461 GameClient()->OnMessage(Msg, &Unpacker);
1464 const IDemoPlayer::CInfo *client_demoplayer_getinfo()
1466 static DEMOPLAYBACK_INFO ret;
1467 const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info();
1468 ret.first_tick = info->first_tick;
1469 ret.last_tick = info->last_tick;
1470 ret.current_tick = info->current_tick;
1471 ret.paused = info->paused;
1472 ret.speed = info->speed;
1473 return &ret;
1477 void DemoPlayer()->SetPos(float percent)
1479 demorec_playback_set(percent);
1482 void DemoPlayer()->SetSpeed(float speed)
1484 demorec_playback_setspeed(speed);
1487 void DemoPlayer()->SetPause(int paused)
1489 if(paused)
1490 demorec_playback_pause();
1491 else
1492 demorec_playback_unpause();
1495 void CClient::Update()
1497 if(State() == IClient::STATE_DEMOPLAYBACK)
1499 m_DemoPlayer.Update();
1500 if(m_DemoPlayer.IsPlaying())
1502 // update timers
1503 const CDemoPlayer::CPlaybackInfo *pInfo = m_DemoPlayer.Info();
1504 m_CurGameTick = pInfo->m_Info.m_CurrentTick;
1505 m_PrevGameTick = pInfo->m_PreviousTick;
1506 m_GameIntraTick = pInfo->m_IntraTick;
1507 m_GameTickTime = pInfo->m_TickTime;
1509 else
1511 // disconnect on error
1512 Disconnect();
1515 else if(State() == IClient::STATE_ONLINE && m_RecivedSnapshots >= 3)
1517 // switch snapshot
1518 int Repredict = 0;
1519 int64 Freq = time_freq();
1520 int64 Now = m_GameTime.Get(time_get());
1521 int64 PredNow = m_PredictedTime.Get(time_get());
1523 while(1)
1525 CSnapshotStorage::CHolder *pCur = m_aSnapshots[SNAP_CURRENT];
1526 int64 TickStart = (pCur->m_Tick)*time_freq()/50;
1528 if(TickStart < Now)
1530 CSnapshotStorage::CHolder *pNext = m_aSnapshots[SNAP_CURRENT]->m_pNext;
1531 if(pNext)
1533 m_aSnapshots[SNAP_PREV] = m_aSnapshots[SNAP_CURRENT];
1534 m_aSnapshots[SNAP_CURRENT] = pNext;
1536 // set ticks
1537 m_CurGameTick = m_aSnapshots[SNAP_CURRENT]->m_Tick;
1538 m_PrevGameTick = m_aSnapshots[SNAP_PREV]->m_Tick;
1540 if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
1542 GameClient()->OnNewSnapshot();
1543 Repredict = 1;
1546 else
1547 break;
1549 else
1550 break;
1553 if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_PREV])
1555 int64 CurtickStart = (m_aSnapshots[SNAP_CURRENT]->m_Tick)*time_freq()/50;
1556 int64 PrevtickStart = (m_aSnapshots[SNAP_PREV]->m_Tick)*time_freq()/50;
1557 int PrevPredTick = (int)(PredNow*50/time_freq());
1558 int NewPredTick = PrevPredTick+1;
1560 m_GameIntraTick = (Now - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
1561 m_GameTickTime = (Now - PrevtickStart) / (float)Freq; //(float)SERVER_TICK_SPEED);
1563 CurtickStart = NewPredTick*time_freq()/50;
1564 PrevtickStart = PrevPredTick*time_freq()/50;
1565 m_PredIntraTick = (PredNow - PrevtickStart) / (float)(CurtickStart-PrevtickStart);
1567 if(NewPredTick < m_aSnapshots[SNAP_PREV]->m_Tick-SERVER_TICK_SPEED || NewPredTick > m_aSnapshots[SNAP_PREV]->m_Tick+SERVER_TICK_SPEED)
1569 m_pConsole->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", "prediction time reset!");
1570 m_PredictedTime.Init(m_aSnapshots[SNAP_CURRENT]->m_Tick*time_freq()/50);
1573 if(NewPredTick > m_PredTick)
1575 m_PredTick = NewPredTick;
1576 Repredict = 1;
1578 // send input
1579 SendInput();
1583 // only do sane predictions
1584 if(Repredict)
1586 if(m_PredTick > m_CurGameTick && m_PredTick < m_CurGameTick+50)
1587 GameClient()->OnPredict();
1590 // fetch server info if we don't have it
1591 if(State() >= IClient::STATE_LOADING &&
1592 m_CurrentServerInfoRequestTime >= 0 &&
1593 time_get() > m_CurrentServerInfoRequestTime)
1595 m_ServerBrowser.Request(m_ServerAddress);
1596 m_CurrentServerInfoRequestTime = time_get()+time_freq()*2;
1600 // STRESS TEST: join the server again
1601 if(g_Config.m_DbgStress)
1603 static int64 ActionTaken = 0;
1604 int64 Now = time_get();
1605 if(State() == IClient::STATE_OFFLINE)
1607 if(Now > ActionTaken+time_freq()*2)
1609 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "reconnecting!");
1610 Connect(g_Config.m_DbgStressServer);
1611 ActionTaken = Now;
1614 else
1616 if(Now > ActionTaken+time_freq()*(10+g_Config.m_DbgStress))
1618 m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "stress", "disconnecting!");
1619 Disconnect();
1620 ActionTaken = Now;
1625 // pump the network
1626 PumpNetwork();
1628 // update the maser server registry
1629 MasterServer()->Update();
1631 // update the server browser
1632 m_ServerBrowser.Update(m_ResortServerBrowser);
1633 m_ResortServerBrowser = false;
1636 void CClient::VersionUpdate()
1638 if(m_VersionInfo.m_State == CVersionInfo::STATE_INIT)
1640 Engine()->HostLookup(&m_VersionInfo.m_VersionServeraddr, g_Config.m_ClVersionServer, m_NetClient.NetType());
1641 m_VersionInfo.m_State = CVersionInfo::STATE_START;
1643 else if(m_VersionInfo.m_State == CVersionInfo::STATE_START)
1645 if(m_VersionInfo.m_VersionServeraddr.m_Job.Status() == CJob::STATE_DONE)
1647 CNetChunk Packet;
1649 mem_zero(&Packet, sizeof(Packet));
1651 m_VersionInfo.m_VersionServeraddr.m_Addr.port = VERSIONSRV_PORT;
1653 Packet.m_ClientID = -1;
1654 Packet.m_Address = m_VersionInfo.m_VersionServeraddr.m_Addr;
1655 Packet.m_pData = VERSIONSRV_GETVERSION;
1656 Packet.m_DataSize = sizeof(VERSIONSRV_GETVERSION);
1657 Packet.m_Flags = NETSENDFLAG_CONNLESS;
1659 m_NetClient.Send(&Packet);
1660 m_VersionInfo.m_State = CVersionInfo::STATE_READY;
1665 void CClient::RegisterInterfaces()
1667 Kernel()->RegisterInterface(static_cast<IDemoRecorder*>(&m_DemoRecorder));
1668 Kernel()->RegisterInterface(static_cast<IDemoPlayer*>(&m_DemoPlayer));
1669 Kernel()->RegisterInterface(static_cast<IServerBrowser*>(&m_ServerBrowser));
1670 Kernel()->RegisterInterface(static_cast<IFriends*>(&m_Friends));
1673 void CClient::InitInterfaces()
1675 // fetch interfaces
1676 m_pEngine = Kernel()->RequestInterface<IEngine>();
1677 m_pEditor = Kernel()->RequestInterface<IEditor>();
1678 m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
1679 m_pSound = Kernel()->RequestInterface<IEngineSound>();
1680 m_pGameClient = Kernel()->RequestInterface<IGameClient>();
1681 m_pInput = Kernel()->RequestInterface<IEngineInput>();
1682 m_pMap = Kernel()->RequestInterface<IEngineMap>();
1683 m_pMasterServer = Kernel()->RequestInterface<IEngineMasterServer>();
1684 m_pStorage = Kernel()->RequestInterface<IStorage>();
1687 m_ServerBrowser.SetBaseInfo(&m_NetClient, m_pGameClient->NetVersion());
1688 m_Friends.Init();
1691 void CClient::Run()
1693 int64 ReportTime = time_get();
1694 int64 ReportInterval = time_freq()*1;
1696 m_LocalStartTime = time_get();
1697 m_SnapshotParts = 0;
1699 // init graphics
1700 if(m_pGraphics->Init() != 0)
1701 return;
1703 // open socket
1705 NETADDR BindAddr;
1706 mem_zero(&BindAddr, sizeof(BindAddr));
1707 BindAddr.type = NETTYPE_ALL;
1708 if(!m_NetClient.Open(BindAddr, 0))
1710 dbg_msg("client", "couldn't start network");
1711 return;
1715 // init font rendering
1716 Kernel()->RequestInterface<IEngineTextRender>()->Init();
1718 // init the input
1719 Input()->Init();
1721 // start refreshing addresses while we load
1722 MasterServer()->RefreshAddresses(m_NetClient.NetType());
1724 // init the editor
1725 m_pEditor->Init();
1727 // init sound, allowed to fail
1728 m_SoundInitFailed = Sound()->Init() != 0;
1730 // load data
1731 if(!LoadData())
1732 return;
1734 GameClient()->OnInit();
1735 char aBuf[256];
1736 str_format(aBuf, sizeof(aBuf), "version %s", GameClient()->NetVersion());
1737 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client", aBuf);
1739 // connect to the server if wanted
1741 if(config.cl_connect[0] != 0)
1742 Connect(config.cl_connect);
1743 config.cl_connect[0] = 0;
1747 m_FpsGraph.Init(0.0f, 200.0f);
1749 // never start with the editor
1750 g_Config.m_ClEditor = 0;
1752 Input()->MouseModeRelative();
1754 // process pending commands
1755 m_pConsole->StoreCommands(false);
1757 while (1)
1759 int64 FrameStartTime = time_get();
1760 m_Frames++;
1763 VersionUpdate();
1765 // handle pending connects
1766 if(m_aCmdConnect[0])
1768 str_copy(g_Config.m_UiServerAddress, m_aCmdConnect, sizeof(g_Config.m_UiServerAddress));
1769 Connect(m_aCmdConnect);
1770 m_aCmdConnect[0] = 0;
1773 // update input
1774 if(Input()->Update())
1775 break; // SDL_QUIT
1777 // update sound
1778 Sound()->Update();
1780 // release focus
1781 if(!m_pGraphics->WindowActive())
1783 if(m_WindowMustRefocus == 0)
1784 Input()->MouseModeAbsolute();
1785 m_WindowMustRefocus = 1;
1787 else if (g_Config.m_DbgFocus && Input()->KeyPressed(KEY_ESCAPE))
1789 Input()->MouseModeAbsolute();
1790 m_WindowMustRefocus = 1;
1793 // refocus
1794 if(m_WindowMustRefocus && m_pGraphics->WindowActive())
1796 if(m_WindowMustRefocus < 3)
1798 Input()->MouseModeAbsolute();
1799 m_WindowMustRefocus++;
1802 if(m_WindowMustRefocus >= 3 || Input()->KeyPressed(KEY_MOUSE_1))
1804 Input()->MouseModeRelative();
1805 m_WindowMustRefocus = 0;
1809 // panic quit button
1810 if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyPressed('q'))
1811 break;
1813 if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('d'))
1814 g_Config.m_Debug ^= 1;
1816 if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('g'))
1817 g_Config.m_DbgGraphs ^= 1;
1819 if(Input()->KeyPressed(KEY_LCTRL) && Input()->KeyPressed(KEY_LSHIFT) && Input()->KeyDown('e'))
1821 g_Config.m_ClEditor = g_Config.m_ClEditor^1;
1822 Input()->MouseModeRelative();
1826 if(!gfx_window_open())
1827 break;
1830 // render
1831 if(g_Config.m_ClEditor)
1833 if(!m_EditorActive)
1835 GameClient()->OnActivateEditor();
1836 m_EditorActive = true;
1839 Update();
1840 m_pEditor->UpdateAndRender();
1841 DebugRender();
1842 m_pGraphics->Swap();
1844 else
1846 if(m_EditorActive)
1847 m_EditorActive = false;
1849 Update();
1851 if(g_Config.m_DbgStress)
1853 if((m_Frames%10) == 0)
1855 Render();
1856 m_pGraphics->Swap();
1859 else
1861 Render();
1862 m_pGraphics->Swap();
1866 AutoScreenshot_Cleanup();
1868 // check conditions
1869 if(State() == IClient::STATE_QUITING)
1870 break;
1872 // beNice
1873 if(g_Config.m_DbgStress)
1874 thread_sleep(5);
1875 else if(g_Config.m_ClCpuThrottle || !m_pGraphics->WindowActive())
1876 thread_sleep(1);
1878 if(g_Config.m_DbgHitch)
1880 thread_sleep(g_Config.m_DbgHitch);
1881 g_Config.m_DbgHitch = 0;
1884 if(ReportTime < time_get())
1886 if(0 && g_Config.m_Debug)
1888 dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
1889 m_Frames/(float)(ReportInterval/time_freq()),
1890 1.0f/m_FrameTimeHigh,
1891 1.0f/m_FrameTimeLow,
1892 m_NetClient.State());
1894 m_FrameTimeLow = 1;
1895 m_FrameTimeHigh = 0;
1896 m_Frames = 0;
1897 ReportTime += ReportInterval;
1900 // update frametime
1901 m_FrameTime = (time_get()-FrameStartTime)/(float)time_freq();
1902 if(m_FrameTime < m_FrameTimeLow)
1903 m_FrameTimeLow = m_FrameTime;
1904 if(m_FrameTime > m_FrameTimeHigh)
1905 m_FrameTimeHigh = m_FrameTime;
1907 m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
1909 m_FpsGraph.Add(1.0f/m_FrameTime, 1,1,1);
1912 GameClient()->OnShutdown();
1913 Disconnect();
1915 m_pGraphics->Shutdown();
1916 m_pSound->Shutdown();
1920 void CClient::Con_Connect(IConsole::IResult *pResult, void *pUserData)
1922 CClient *pSelf = (CClient *)pUserData;
1923 str_copy(pSelf->m_aCmdConnect, pResult->GetString(0), sizeof(pSelf->m_aCmdConnect));
1926 void CClient::Con_Disconnect(IConsole::IResult *pResult, void *pUserData)
1928 CClient *pSelf = (CClient *)pUserData;
1929 pSelf->Disconnect();
1932 void CClient::Con_Quit(IConsole::IResult *pResult, void *pUserData)
1934 CClient *pSelf = (CClient *)pUserData;
1935 pSelf->Quit();
1938 void CClient::Con_Minimize(IConsole::IResult *pResult, void *pUserData)
1940 CClient *pSelf = (CClient *)pUserData;
1941 pSelf->Graphics()->Minimize();
1944 void CClient::Con_Ping(IConsole::IResult *pResult, void *pUserData)
1946 CClient *pSelf = (CClient *)pUserData;
1948 CMsgPacker Msg(NETMSG_PING);
1949 pSelf->SendMsgEx(&Msg, 0);
1950 pSelf->m_PingStartTime = time_get();
1953 void CClient::AutoScreenshot_Start()
1955 if(g_Config.m_ClAutoScreenshot)
1957 Graphics()->TakeScreenshot("auto/autoscreen");
1958 m_AutoScreenshotRecycle = true;
1962 void CClient::AutoScreenshot_Cleanup()
1964 if(m_AutoScreenshotRecycle)
1966 if(g_Config.m_ClAutoScreenshotMax)
1968 // clean up auto taken screens
1969 CFileCollection AutoScreens;
1970 AutoScreens.Init(Storage(), "screenshots/auto", "autoscreen", ".png", g_Config.m_ClAutoScreenshotMax);
1972 m_AutoScreenshotRecycle = false;
1976 void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData)
1978 CClient *pSelf = (CClient *)pUserData;
1979 pSelf->Graphics()->TakeScreenshot(0);
1982 void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData)
1984 CClient *pSelf = (CClient *)pUserData;
1985 pSelf->Rcon(pResult->GetString(0));
1988 void CClient::Con_RconAuth(IConsole::IResult *pResult, void *pUserData)
1990 CClient *pSelf = (CClient *)pUserData;
1991 pSelf->RconAuth("", pResult->GetString(0));
1994 void CClient::Con_AddFavorite(IConsole::IResult *pResult, void *pUserData)
1996 CClient *pSelf = (CClient *)pUserData;
1997 NETADDR Addr;
1998 if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
1999 pSelf->m_ServerBrowser.AddFavorite(Addr);
2002 void CClient::Con_RemoveFavorite(IConsole::IResult *pResult, void *pUserData)
2004 CClient *pSelf = (CClient *)pUserData;
2005 NETADDR Addr;
2006 if(net_addr_from_str(&Addr, pResult->GetString(0)) == 0)
2007 pSelf->m_ServerBrowser.RemoveFavorite(Addr);
2010 const char *CClient::DemoPlayer_Play(const char *pFilename, int StorageType)
2012 int Crc;
2013 const char *pError;
2014 Disconnect();
2015 m_NetClient.ResetErrorString();
2017 // try to start playback
2018 m_DemoPlayer.SetListner(this);
2020 if(m_DemoPlayer.Load(Storage(), m_pConsole, pFilename, StorageType))
2021 return "error loading demo";
2023 // load map
2024 Crc = (m_DemoPlayer.Info()->m_Header.m_aMapCrc[0]<<24)|
2025 (m_DemoPlayer.Info()->m_Header.m_aMapCrc[1]<<16)|
2026 (m_DemoPlayer.Info()->m_Header.m_aMapCrc[2]<<8)|
2027 (m_DemoPlayer.Info()->m_Header.m_aMapCrc[3]);
2028 pError = LoadMapSearch(m_DemoPlayer.Info()->m_Header.m_aMapName, Crc);
2029 if(pError)
2031 DisconnectWithReason(pError);
2032 return pError;
2035 GameClient()->OnConnected();
2037 // setup buffers
2038 mem_zero(m_aDemorecSnapshotData, sizeof(m_aDemorecSnapshotData));
2040 m_aSnapshots[SNAP_CURRENT] = &m_aDemorecSnapshotHolders[SNAP_CURRENT];
2041 m_aSnapshots[SNAP_PREV] = &m_aDemorecSnapshotHolders[SNAP_PREV];
2043 m_aSnapshots[SNAP_CURRENT]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][0];
2044 m_aSnapshots[SNAP_CURRENT]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_CURRENT][1];
2045 m_aSnapshots[SNAP_CURRENT]->m_SnapSize = 0;
2046 m_aSnapshots[SNAP_CURRENT]->m_Tick = -1;
2048 m_aSnapshots[SNAP_PREV]->m_pSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][0];
2049 m_aSnapshots[SNAP_PREV]->m_pAltSnap = (CSnapshot *)m_aDemorecSnapshotData[SNAP_PREV][1];
2050 m_aSnapshots[SNAP_PREV]->m_SnapSize = 0;
2051 m_aSnapshots[SNAP_PREV]->m_Tick = -1;
2053 // enter demo playback state
2054 SetState(IClient::STATE_DEMOPLAYBACK);
2056 m_DemoPlayer.Play();
2057 GameClient()->OnEnterGame();
2059 return 0;
2062 void CClient::Con_Play(IConsole::IResult *pResult, void *pUserData)
2064 CClient *pSelf = (CClient *)pUserData;
2065 pSelf->DemoPlayer_Play(pResult->GetString(0), IStorage::TYPE_ALL);
2068 void CClient::DemoRecorder_Start(const char *pFilename, bool WithTimestamp)
2070 if(State() != IClient::STATE_ONLINE)
2071 m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "demorec/record", "client is not online");
2072 else
2074 char aFilename[128];
2075 if(WithTimestamp)
2077 char aDate[20];
2078 str_timestamp(aDate, sizeof(aDate));
2079 str_format(aFilename, sizeof(aFilename), "demos/%s_%s.demo", pFilename, aDate);
2081 else
2082 str_format(aFilename, sizeof(aFilename), "demos/%s.demo", pFilename);
2083 m_DemoRecorder.Start(Storage(), m_pConsole, aFilename, GameClient()->NetVersion(), m_aCurrentMap, m_CurrentMapCrc, "client");
2087 void CClient::DemoRecorder_HandleAutoStart()
2089 if(g_Config.m_ClAutoDemoRecord)
2091 DemoRecorder_Stop();
2092 DemoRecorder_Start("auto/autorecord", true);
2093 if(g_Config.m_ClAutoDemoMax)
2095 // clean up auto recorded demos
2096 CFileCollection AutoDemos;
2097 AutoDemos.Init(Storage(), "demos/auto", "autorecord", ".demo", g_Config.m_ClAutoDemoMax);
2102 void CClient::DemoRecorder_Stop()
2104 m_DemoRecorder.Stop();
2107 void CClient::Con_Record(IConsole::IResult *pResult, void *pUserData)
2109 CClient *pSelf = (CClient *)pUserData;
2110 if(pResult->NumArguments())
2111 pSelf->DemoRecorder_Start(pResult->GetString(0), false);
2112 else
2113 pSelf->DemoRecorder_Start("demo", true);
2116 void CClient::Con_StopRecord(IConsole::IResult *pResult, void *pUserData)
2118 CClient *pSelf = (CClient *)pUserData;
2119 pSelf->DemoRecorder_Stop();
2122 void CClient::ServerBrowserUpdate()
2124 m_ResortServerBrowser = true;
2127 void CClient::ConchainServerBrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
2129 pfnCallback(pResult, pCallbackUserData);
2130 if(pResult->NumArguments())
2131 ((CClient *)pUserData)->ServerBrowserUpdate();
2134 void CClient::RegisterCommands()
2136 m_pConsole = Kernel()->RequestInterface<IConsole>();
2137 // register server dummy commands for tab completion
2138 m_pConsole->Register("kick", "i?r", CFGFLAG_SERVER, 0, 0, "Kick player with specified id for any reason");
2139 m_pConsole->Register("ban", "s?ir", CFGFLAG_SERVER, 0, 0, "Ban player with ip/id for x minutes for any reason");
2140 m_pConsole->Register("unban", "s", CFGFLAG_SERVER, 0, 0, "Unban ip");
2141 m_pConsole->Register("bans", "", CFGFLAG_SERVER, 0, 0, "Show banlist");
2142 m_pConsole->Register("status", "", CFGFLAG_SERVER, 0, 0, "List players");
2143 m_pConsole->Register("shutdown", "", CFGFLAG_SERVER, 0, 0, "Shut down");
2144 m_pConsole->Register("record", "?s", CFGFLAG_SERVER, 0, 0, "Record to a file");
2145 m_pConsole->Register("stoprecord", "", CFGFLAG_SERVER, 0, 0, "Stop recording");
2146 m_pConsole->Register("reload", "", CFGFLAG_SERVER, 0, 0, "Reload the map");
2148 m_pConsole->Register("quit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds");
2149 m_pConsole->Register("exit", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Quit, this, "Quit Teeworlds");
2150 m_pConsole->Register("minimize", "", CFGFLAG_CLIENT|CFGFLAG_STORE, Con_Minimize, this, "Minimize Teeworlds");
2151 m_pConsole->Register("connect", "s", CFGFLAG_CLIENT, Con_Connect, this, "Connect to the specified host/ip");
2152 m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server");
2153 m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server");
2154 m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot");
2155 m_pConsole->Register("rcon", "r", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
2156 m_pConsole->Register("rcon_auth", "s", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
2157 m_pConsole->Register("play", "r", CFGFLAG_CLIENT, Con_Play, this, "Play the file specified");
2158 m_pConsole->Register("record", "?s", CFGFLAG_CLIENT, Con_Record, this, "Record to the file");
2159 m_pConsole->Register("stoprecord", "", CFGFLAG_CLIENT, Con_StopRecord, this, "Stop recording");
2160 m_pConsole->Register("add_favorite", "s", CFGFLAG_CLIENT, Con_AddFavorite, this, "Add a server as a favorite");
2161 m_pConsole->Register("remove_favorite", "s", CFGFLAG_CLIENT, Con_RemoveFavorite, this, "Remove a server from favorites");
2163 // used for server browser update
2164 m_pConsole->Chain("br_filter_string", ConchainServerBrowserUpdate, this);
2165 m_pConsole->Chain("br_filter_gametype", ConchainServerBrowserUpdate, this);
2166 m_pConsole->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate, this);
2169 static CClient *CreateClient()
2171 CClient *pClient = static_cast<CClient *>(mem_alloc(sizeof(CClient), 1));
2172 mem_zero(pClient, sizeof(CClient));
2173 return new(pClient) CClient;
2177 Server Time
2178 Client Mirror Time
2179 Client Predicted Time
2181 Snapshot Latency
2182 Downstream latency
2184 Prediction Latency
2185 Upstream latency
2188 #if defined(CONF_PLATFORM_MACOSX)
2189 extern "C" int SDL_main(int argc, const char **argv) // ignore_convention
2190 #else
2191 int main(int argc, const char **argv) // ignore_convention
2192 #endif
2194 #if defined(CONF_FAMILY_WINDOWS)
2195 for(int i = 1; i < argc; i++) // ignore_convention
2197 if(str_comp("-s", argv[i]) == 0 || str_comp("--silent", argv[i]) == 0) // ignore_convention
2199 FreeConsole();
2200 break;
2203 #endif
2205 CClient *pClient = CreateClient();
2206 IKernel *pKernel = IKernel::Create();
2207 pKernel->RegisterInterface(pClient);
2208 pClient->RegisterInterfaces();
2210 // create the components
2211 IEngine *pEngine = CreateEngine("Teeworlds");
2212 IConsole *pConsole = CreateConsole(CFGFLAG_CLIENT);
2213 IStorage *pStorage = CreateStorage("Teeworlds", argc, argv); // ignore_convention
2214 IConfig *pConfig = CreateConfig();
2215 IEngineGraphics *pEngineGraphics = CreateEngineGraphics();
2216 IEngineSound *pEngineSound = CreateEngineSound();
2217 IEngineInput *pEngineInput = CreateEngineInput();
2218 IEngineTextRender *pEngineTextRender = CreateEngineTextRender();
2219 IEngineMap *pEngineMap = CreateEngineMap();
2220 IEngineMasterServer *pEngineMasterServer = CreateEngineMasterServer();
2223 bool RegisterFail = false;
2225 RegisterFail = RegisterFail || !pKernel->RegisterInterface(pEngine);
2226 RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConsole);
2227 RegisterFail = RegisterFail || !pKernel->RegisterInterface(pConfig);
2229 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineGraphics*>(pEngineGraphics)); // register graphics as both
2230 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IGraphics*>(pEngineGraphics));
2232 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineSound*>(pEngineSound)); // register as both
2233 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ISound*>(pEngineSound));
2235 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineInput*>(pEngineInput)); // register as both
2236 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IInput*>(pEngineInput));
2238 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineTextRender*>(pEngineTextRender)); // register as both
2239 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<ITextRender*>(pEngineTextRender));
2241 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMap*>(pEngineMap)); // register as both
2242 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMap*>(pEngineMap));
2244 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IEngineMasterServer*>(pEngineMasterServer)); // register as both
2245 RegisterFail = RegisterFail || !pKernel->RegisterInterface(static_cast<IMasterServer*>(pEngineMasterServer));
2247 RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateEditor());
2248 RegisterFail = RegisterFail || !pKernel->RegisterInterface(CreateGameClient());
2249 RegisterFail = RegisterFail || !pKernel->RegisterInterface(pStorage);
2251 if(RegisterFail)
2252 return -1;
2255 pEngine->Init();
2256 pConfig->Init();
2257 pEngineMasterServer->Init();
2258 pEngineMasterServer->Load();
2260 // register all console commands
2261 pClient->RegisterCommands();
2263 pKernel->RequestInterface<IGameClient>()->OnConsoleInit();
2265 // init client's interfaces
2266 pClient->InitInterfaces();
2268 // execute config file
2269 pConsole->ExecuteFile("settings.cfg");
2271 // execute autoexec file
2272 pConsole->ExecuteFile("autoexec.cfg");
2274 // parse the command line arguments
2275 if(argc > 1) // ignore_convention
2276 pConsole->ParseArguments(argc-1, &argv[1]); // ignore_convention
2278 // restore empty config strings to their defaults
2279 pConfig->RestoreStrings();
2281 pClient->Engine()->InitLogfile();
2283 // run the client
2284 dbg_msg("client", "starting...");
2285 pClient->Run();
2287 // write down the config and quit
2288 pConfig->Save();
2290 return 0;