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. */
4 #include <stdlib.h> // qsort
8 #include <base/system.h>
10 #include <engine/client.h>
11 #include <engine/config.h>
12 #include <engine/console.h>
13 #include <engine/editor.h>
14 #include <engine/engine.h>
15 #include <engine/graphics.h>
16 #include <engine/input.h>
17 #include <engine/keys.h>
18 #include <engine/map.h>
19 #include <engine/masterserver.h>
20 #include <engine/serverbrowser.h>
21 #include <engine/sound.h>
22 #include <engine/storage.h>
23 #include <engine/textrender.h>
25 #include <engine/shared/config.h>
26 #include <engine/shared/compression.h>
27 #include <engine/shared/datafile.h>
28 #include <engine/shared/demo.h>
29 #include <engine/shared/filecollection.h>
30 #include <engine/shared/mapchecker.h>
31 #include <engine/shared/network.h>
32 #include <engine/shared/packer.h>
33 #include <engine/shared/protocol.h>
34 #include <engine/shared/ringbuffer.h>
35 #include <engine/shared/snapshot.h>
37 #include <mastersrv/mastersrv.h>
38 #include <versionsrv/versionsrv.h>
41 #include "serverbrowser.h"
44 #if defined(CONF_FAMILY_WINDOWS)
45 #define _WIN32_WINNT 0x0501
46 #define WIN32_LEAN_AND_MEAN
51 void CGraph::Init(float Min
, float Max
)
58 void CGraph::ScaleMax()
62 for(i
= 0; i
< MAX_VALUES
; i
++)
64 if(m_aValues
[i
] > m_Max
)
69 void CGraph::ScaleMin()
73 for(i
= 0; i
< MAX_VALUES
; i
++)
75 if(m_aValues
[i
] < m_Min
)
80 void CGraph::Add(float v
, float r
, float g
, float b
)
82 m_Index
= (m_Index
+1)&(MAX_VALUES
-1);
83 m_aValues
[m_Index
] = v
;
84 m_aColors
[m_Index
][0] = r
;
85 m_aColors
[m_Index
][1] = g
;
86 m_aColors
[m_Index
][2] = b
;
89 void CGraph::Render(IGraphics
*pGraphics
, int Font
, float x
, float y
, float w
, float h
, const char *pDescription
)
91 //m_pGraphics->BlendNormal();
94 pGraphics
->TextureSet(-1);
96 pGraphics
->QuadsBegin();
97 pGraphics
->SetColor(0, 0, 0, 0.75f
);
98 IGraphics::CQuadItem
QuadItem(x
, y
, w
, h
);
99 pGraphics
->QuadsDrawTL(&QuadItem
, 1);
100 pGraphics
->QuadsEnd();
102 pGraphics
->LinesBegin();
103 pGraphics
->SetColor(0.95f
, 0.95f
, 0.95f
, 1.00f
);
104 IGraphics::CLineItem
LineItem(x
, y
+h
/2, x
+w
, y
+h
/2);
105 pGraphics
->LinesDraw(&LineItem
, 1);
106 pGraphics
->SetColor(0.5f
, 0.5f
, 0.5f
, 0.75f
);
107 IGraphics::CLineItem Array
[2] = {
108 IGraphics::CLineItem(x
, y
+(h
*3)/4, x
+w
, y
+(h
*3)/4),
109 IGraphics::CLineItem(x
, y
+h
/4, x
+w
, y
+h
/4)};
110 pGraphics
->LinesDraw(Array
, 2);
111 for(int i
= 1; i
< MAX_VALUES
; i
++)
113 float a0
= (i
-1)/(float)MAX_VALUES
;
114 float a1
= i
/(float)MAX_VALUES
;
115 int i0
= (m_Index
+i
-1)&(MAX_VALUES
-1);
116 int i1
= (m_Index
+i
)&(MAX_VALUES
-1);
118 float v0
= (m_aValues
[i0
]-m_Min
) / (m_Max
-m_Min
);
119 float v1
= (m_aValues
[i1
]-m_Min
) / (m_Max
-m_Min
);
121 IGraphics::CColorVertex Array
[2] = {
122 IGraphics::CColorVertex(0, m_aColors
[i0
][0], m_aColors
[i0
][1], m_aColors
[i0
][2], 0.75f
),
123 IGraphics::CColorVertex(1, m_aColors
[i1
][0], m_aColors
[i1
][1], m_aColors
[i1
][2], 0.75f
)};
124 pGraphics
->SetColorVertex(Array
, 2);
125 IGraphics::CLineItem
LineItem(x
+a0
*w
, y
+h
-v0
*h
, x
+a1
*w
, y
+h
-v1
*h
);
126 pGraphics
->LinesDraw(&LineItem
, 1);
129 pGraphics
->LinesEnd();
131 pGraphics
->TextureSet(Font
);
132 pGraphics
->QuadsText(x
+2, y
+h
-16, 16, 1,1,1,1, pDescription
);
135 str_format(aBuf
, sizeof(aBuf
), "%.2f", m_Max
);
136 pGraphics
->QuadsText(x
+w
-8*str_length(aBuf
)-8, y
+2, 16, 1,1,1,1, aBuf
);
138 str_format(aBuf
, sizeof(aBuf
), "%.2f", m_Min
);
139 pGraphics
->QuadsText(x
+w
-8*str_length(aBuf
)-8, y
+h
-16, 16, 1,1,1,1, aBuf
);
143 void CSmoothTime::Init(int64 Target
)
148 m_aAdjustSpeed
[0] = 0.3f
;
149 m_aAdjustSpeed
[1] = 0.3f
;
150 m_Graph
.Init(0.0f
, 0.5f
);
153 void CSmoothTime::SetAdjustSpeed(int Direction
, float Value
)
155 m_aAdjustSpeed
[Direction
] = Value
;
158 int64
CSmoothTime::Get(int64 Now
)
160 int64 c
= m_Current
+ (Now
- m_Snap
);
161 int64 t
= m_Target
+ (Now
- m_Snap
);
163 // it's faster to adjust upward instead of downward
164 // we might need to adjust these abit
166 float AdjustSpeed
= m_aAdjustSpeed
[0];
168 AdjustSpeed
= m_aAdjustSpeed
[1];
170 float a
= ((Now
-m_Snap
)/(float)time_freq()) * AdjustSpeed
;
174 int64 r
= c
+ (int64
)((t
-c
)*a
);
176 m_Graph
.Add(a
+0.5f
,1,1,1);
181 void CSmoothTime::UpdateInt(int64 Target
)
183 int64 Now
= time_get();
184 m_Current
= Get(Now
);
189 void CSmoothTime::Update(CGraph
*pGraph
, int64 Target
, int TimeLeft
, int AdjustDirection
)
201 if(m_SpikeCounter
> 50)
205 if(IsSpike
&& m_SpikeCounter
< 15)
207 // ignore this ping spike
209 pGraph
->Add(TimeLeft
, 1,1,0);
213 pGraph
->Add(TimeLeft
, 1,0,0);
214 if(m_aAdjustSpeed
[AdjustDirection
] < 30.0f
)
215 m_aAdjustSpeed
[AdjustDirection
] *= 2.0f
;
223 pGraph
->Add(TimeLeft
, 0,1,0);
225 m_aAdjustSpeed
[AdjustDirection
] *= 0.95f
;
226 if(m_aAdjustSpeed
[AdjustDirection
] < 2.0f
)
227 m_aAdjustSpeed
[AdjustDirection
] = 2.0f
;
235 CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta
), m_DemoRecorder(&m_SnapshotDelta
)
245 m_FrameTime
= 0.0001f
;
246 m_FrameTimeLow
= 1.0f
;
247 m_FrameTimeHigh
= 0.0f
;
250 m_GameTickSpeed
= SERVER_TICK_SPEED
;
252 m_WindowMustRefocus
= 0;
254 m_AutoScreenshotRecycle
= false;
255 m_EditorActive
= false;
258 m_CurrentRecvTick
= 0;
262 m_aVersionStr
[0] = '0';
263 m_aVersionStr
[1] = 0;
269 m_aCurrentMap
[0] = 0;
273 m_aCmdConnect
[0] = 0;
276 m_aMapdownloadFilename
[0] = 0;
277 m_aMapdownloadName
[0] = 0;
278 m_MapdownloadFile
= 0;
279 m_MapdownloadChunk
= 0;
280 m_MapdownloadCrc
= 0;
281 m_MapdownloadAmount
= -1;
282 m_MapdownloadTotalsize
= -1;
284 m_CurrentServerInfoRequestTime
= -1;
288 m_State
= IClient::STATE_OFFLINE
;
289 m_aServerAddressStr
[0] = 0;
291 mem_zero(m_aSnapshots
, sizeof(m_aSnapshots
));
292 m_SnapshotStorage
.Init();
293 m_RecivedSnapshots
= 0;
295 m_VersionInfo
.m_State
= CVersionInfo::STATE_INIT
;
298 // ----- send functions -----
299 int CClient::SendMsg(CMsgPacker
*pMsg
, int Flags
)
301 return SendMsgEx(pMsg
, Flags
, false);
304 int CClient::SendMsgEx(CMsgPacker
*pMsg
, int Flags
, bool System
)
308 if(State() == IClient::STATE_OFFLINE
)
311 mem_zero(&Packet
, sizeof(CNetChunk
));
313 Packet
.m_ClientID
= 0;
314 Packet
.m_pData
= pMsg
->Data();
315 Packet
.m_DataSize
= pMsg
->Size();
317 // HACK: modify the message id in the packet and store the system flag
318 if(*((unsigned char*)Packet
.m_pData
) == 1 && System
&& Packet
.m_DataSize
== 1)
321 *((unsigned char*)Packet
.m_pData
) <<= 1;
323 *((unsigned char*)Packet
.m_pData
) |= 1;
325 if(Flags
&MSGFLAG_VITAL
)
326 Packet
.m_Flags
|= NETSENDFLAG_VITAL
;
327 if(Flags
&MSGFLAG_FLUSH
)
328 Packet
.m_Flags
|= NETSENDFLAG_FLUSH
;
330 if(Flags
&MSGFLAG_RECORD
)
332 if(m_DemoRecorder
.IsRecording())
333 m_DemoRecorder
.RecordMessage(Packet
.m_pData
, Packet
.m_DataSize
);
336 if(!(Flags
&MSGFLAG_NOSEND
))
337 m_NetClient
.Send(&Packet
);
341 void CClient::SendInfo()
343 CMsgPacker
Msg(NETMSG_INFO
);
344 Msg
.AddString(GameClient()->NetVersion(), 128);
345 Msg
.AddString(g_Config
.m_Password
, 128);
346 SendMsgEx(&Msg
, MSGFLAG_VITAL
|MSGFLAG_FLUSH
);
350 void CClient::SendEnterGame()
352 CMsgPacker
Msg(NETMSG_ENTERGAME
);
353 SendMsgEx(&Msg
, MSGFLAG_VITAL
|MSGFLAG_FLUSH
);
356 void CClient::SendReady()
358 CMsgPacker
Msg(NETMSG_READY
);
359 SendMsgEx(&Msg
, MSGFLAG_VITAL
|MSGFLAG_FLUSH
);
362 void CClient::RconAuth(const char *pName
, const char *pPassword
)
367 CMsgPacker
Msg(NETMSG_RCON_AUTH
);
368 Msg
.AddString(pName
, 32);
369 Msg
.AddString(pPassword
, 32);
371 SendMsgEx(&Msg
, MSGFLAG_VITAL
);
374 void CClient::Rcon(const char *pCmd
)
376 CMsgPacker
Msg(NETMSG_RCON_CMD
);
377 Msg
.AddString(pCmd
, 256);
378 SendMsgEx(&Msg
, MSGFLAG_VITAL
);
381 bool CClient::ConnectionProblems()
383 return m_NetClient
.GotProblems() != 0;
386 void CClient::DirectInput(int *pInput
, int Size
)
389 CMsgPacker
Msg(NETMSG_INPUT
);
390 Msg
.AddInt(m_AckGameTick
);
391 Msg
.AddInt(m_PredTick
);
394 for(i
= 0; i
< Size
/4; i
++)
395 Msg
.AddInt(pInput
[i
]);
401 void CClient::SendInput()
403 int64 Now
= time_get();
409 int Size
= GameClient()->OnSnapInput(m_aInputs
[m_CurrentInput
].m_aData
);
415 CMsgPacker
Msg(NETMSG_INPUT
);
416 Msg
.AddInt(m_AckGameTick
);
417 Msg
.AddInt(m_PredTick
);
420 m_aInputs
[m_CurrentInput
].m_Tick
= m_PredTick
;
421 m_aInputs
[m_CurrentInput
].m_PredictedTime
= m_PredictedTime
.Get(Now
);
422 m_aInputs
[m_CurrentInput
].m_Time
= Now
;
425 for(int i
= 0; i
< Size
/4; i
++)
426 Msg
.AddInt(m_aInputs
[m_CurrentInput
].m_aData
[i
]);
431 SendMsgEx(&Msg
, MSGFLAG_FLUSH
);
434 const char *CClient::LatestVersion()
436 return m_aVersionStr
;
439 // TODO: OPT: do this alot smarter!
440 int *CClient::GetInput(int Tick
)
443 for(int i
= 0; i
< 200; i
++)
445 if(m_aInputs
[i
].m_Tick
<= Tick
&& (Best
== -1 || m_aInputs
[Best
].m_Tick
< m_aInputs
[i
].m_Tick
))
450 return (int *)m_aInputs
[Best
].m_aData
;
454 // ------ state handling -----
455 void CClient::SetState(int s
)
461 str_format(aBuf
, sizeof(aBuf
), "state change. last=%d current=%d", m_State
, s
);
462 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", aBuf
);
466 GameClient()->OnStateChange(m_State
, Old
);
469 // called when the map is loaded and we should init for a new round
470 void CClient::OnEnterGame()
474 for(i
= 0; i
< 200; i
++)
475 m_aInputs
[i
].m_Tick
= -1;
479 m_aSnapshots
[SNAP_CURRENT
] = 0;
480 m_aSnapshots
[SNAP_PREV
] = 0;
481 m_SnapshotStorage
.PurgeAll();
482 m_RecivedSnapshots
= 0;
485 m_CurrentRecvTick
= 0;
490 void CClient::EnterGame()
492 if(State() == IClient::STATE_DEMOPLAYBACK
)
495 // now we will wait for two snapshots
496 // to finish the connection
501 void CClient::Connect(const char *pAddress
)
508 str_copy(m_aServerAddressStr
, pAddress
, sizeof(m_aServerAddressStr
));
510 str_format(aBuf
, sizeof(aBuf
), "connecting to '%s'", m_aServerAddressStr
);
511 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client", aBuf
);
515 if(net_host_lookup(m_aServerAddressStr
, &m_ServerAddress
, m_NetClient
.NetType()) != 0)
518 str_format(aBufMsg
, sizeof(aBufMsg
), "could not find the address of %s, connecting to localhost", aBuf
);
519 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client", aBufMsg
);
520 net_host_lookup("localhost", &m_ServerAddress
, m_NetClient
.NetType());
524 if(m_ServerAddress
.port
== 0)
525 m_ServerAddress
.port
= Port
;
526 m_NetClient
.Connect(&m_ServerAddress
);
527 SetState(IClient::STATE_CONNECTING
);
529 if(m_DemoRecorder
.IsRecording())
532 m_InputtimeMarginGraph
.Init(-150.0f
, 150.0f
);
533 m_GametimeMarginGraph
.Init(-150.0f
, 150.0f
);
536 void CClient::DisconnectWithReason(const char *pReason
)
539 str_format(aBuf
, sizeof(aBuf
), "disconnecting. reason='%s'", pReason
?pReason
:"unknown");
540 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client", aBuf
);
542 // stop demo playback and recorder
548 m_pConsole
->DeregisterTempAll();
549 m_NetClient
.Disconnect(pReason
);
550 SetState(IClient::STATE_OFFLINE
);
553 // disable all downloads
554 m_MapdownloadChunk
= 0;
555 if(m_MapdownloadFile
)
556 io_close(m_MapdownloadFile
);
557 m_MapdownloadFile
= 0;
558 m_MapdownloadCrc
= 0;
559 m_MapdownloadTotalsize
= -1;
560 m_MapdownloadAmount
= 0;
562 // clear the current server info
563 mem_zero(&m_CurrentServerInfo
, sizeof(m_CurrentServerInfo
));
564 mem_zero(&m_ServerAddress
, sizeof(m_ServerAddress
));
567 m_aSnapshots
[SNAP_CURRENT
] = 0;
568 m_aSnapshots
[SNAP_PREV
] = 0;
569 m_RecivedSnapshots
= 0;
572 void CClient::Disconnect()
574 DisconnectWithReason(0);
578 void CClient::GetServerInfo(CServerInfo
*pServerInfo
)
580 mem_copy(pServerInfo
, &m_CurrentServerInfo
, sizeof(m_CurrentServerInfo
));
583 void CClient::ServerInfoRequest()
585 mem_zero(&m_CurrentServerInfo
, sizeof(m_CurrentServerInfo
));
586 m_CurrentServerInfoRequestTime
= 0;
589 int CClient::LoadData()
591 m_DebugFont
= Graphics()->LoadTexture("debug_font.png", IStorage::TYPE_ALL
, CImageInfo::FORMAT_AUTO
, IGraphics::TEXLOAD_NORESAMPLE
);
597 void *CClient::SnapGetItem(int SnapID
, int Index
, CSnapItem
*pItem
)
600 dbg_assert(SnapID
>= 0 && SnapID
< NUM_SNAPSHOT_TYPES
, "invalid SnapID");
601 i
= m_aSnapshots
[SnapID
]->m_pAltSnap
->GetItem(Index
);
602 pItem
->m_DataSize
= m_aSnapshots
[SnapID
]->m_pAltSnap
->GetItemSize(Index
);
603 pItem
->m_Type
= i
->Type();
604 pItem
->m_ID
= i
->ID();
605 return (void *)i
->Data();
608 void CClient::SnapInvalidateItem(int SnapID
, int Index
)
611 dbg_assert(SnapID
>= 0 && SnapID
< NUM_SNAPSHOT_TYPES
, "invalid SnapID");
612 i
= m_aSnapshots
[SnapID
]->m_pAltSnap
->GetItem(Index
);
615 if((char *)i
< (char *)m_aSnapshots
[SnapID
]->m_pAltSnap
|| (char *)i
> (char *)m_aSnapshots
[SnapID
]->m_pAltSnap
+ m_aSnapshots
[SnapID
]->m_SnapSize
)
616 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", "snap invalidate problem");
617 if((char *)i
>= (char *)m_aSnapshots
[SnapID
]->m_pSnap
&& (char *)i
< (char *)m_aSnapshots
[SnapID
]->m_pSnap
+ m_aSnapshots
[SnapID
]->m_SnapSize
)
618 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", "snap invalidate problem");
623 void *CClient::SnapFindItem(int SnapID
, int Type
, int ID
)
625 // TODO: linear search. should be fixed.
628 if(!m_aSnapshots
[SnapID
])
631 for(i
= 0; i
< m_aSnapshots
[SnapID
]->m_pSnap
->NumItems(); i
++)
633 CSnapshotItem
*pItem
= m_aSnapshots
[SnapID
]->m_pAltSnap
->GetItem(i
);
634 if(pItem
->Type() == Type
&& pItem
->ID() == ID
)
635 return (void *)pItem
->Data();
640 int CClient::SnapNumItems(int SnapID
)
642 dbg_assert(SnapID
>= 0 && SnapID
< NUM_SNAPSHOT_TYPES
, "invalid SnapID");
643 if(!m_aSnapshots
[SnapID
])
645 return m_aSnapshots
[SnapID
]->m_pSnap
->NumItems();
648 void CClient::SnapSetStaticsize(int ItemType
, int Size
)
650 m_SnapshotDelta
.SetStaticsize(ItemType
, Size
);
654 void CClient::DebugRender()
656 static NETSTATS Prev
, Current
;
657 static int64 LastSnap
= 0;
658 static float FrameTimeAvg
= 0;
659 int64 Now
= time_get();
662 if(!g_Config
.m_Debug
)
665 //m_pGraphics->BlendNormal();
666 Graphics()->TextureSet(m_DebugFont
);
667 Graphics()->MapScreen(0,0,Graphics()->ScreenWidth(),Graphics()->ScreenHeight());
669 if(time_get()-LastSnap
> time_freq())
671 LastSnap
= time_get();
682 FrameTimeAvg
= FrameTimeAvg
*0.9f
+ m_FrameTime
*0.1f
;
683 str_format(aBuffer
, sizeof(aBuffer
), "ticks: %8d %8d mem %dk %d gfxmem: %dk fps: %3d",
684 m_CurGameTick
, m_PredTick
,
685 mem_stats()->allocated
/1024,
686 mem_stats()->total_allocations
,
687 Graphics()->MemoryUsage()/1024,
688 (int)(1.0f
/FrameTimeAvg
));
689 Graphics()->QuadsText(2, 2, 16, 1,1,1,1, aBuffer
);
693 int SendPackets
= (Current
.sent_packets
-Prev
.sent_packets
);
694 int SendBytes
= (Current
.sent_bytes
-Prev
.sent_bytes
);
695 int SendTotal
= SendBytes
+ SendPackets
*42;
696 int RecvPackets
= (Current
.recv_packets
-Prev
.recv_packets
);
697 int RecvBytes
= (Current
.recv_bytes
-Prev
.recv_bytes
);
698 int RecvTotal
= RecvBytes
+ RecvPackets
*42;
700 if(!SendPackets
) SendPackets
++;
701 if(!RecvPackets
) RecvPackets
++;
702 str_format(aBuffer
, sizeof(aBuffer
), "send: %3d %5d+%4d=%5d (%3d kbps) avg: %5d\nrecv: %3d %5d+%4d=%5d (%3d kbps) avg: %5d",
703 SendPackets
, SendBytes
, SendPackets
*42, SendTotal
, (SendTotal
*8)/1024, SendBytes
/SendPackets
,
704 RecvPackets
, RecvBytes
, RecvPackets
*42, RecvTotal
, (RecvTotal
*8)/1024, RecvBytes
/RecvPackets
);
705 Graphics()->QuadsText(2, 14, 16, 1,1,1,1, aBuffer
);
712 for(i
= 0; i
< 256; i
++)
714 if(m_SnapshotDelta
.GetDataRate(i
))
716 str_format(aBuffer
, sizeof(aBuffer
), "%4d %20s: %8d %8d %8d", i
, GameClient()->GetItemName(i
), m_SnapshotDelta
.GetDataRate(i
)/8, m_SnapshotDelta
.GetDataUpdates(i
),
717 (m_SnapshotDelta
.GetDataRate(i
)/m_SnapshotDelta
.GetDataUpdates(i
))/8);
718 Graphics()->QuadsText(2, 100+y
*12, 16, 1,1,1,1, aBuffer
);
724 str_format(aBuffer
, sizeof(aBuffer
), "pred: %d ms",
725 (int)((m_PredictedTime
.Get(Now
)-m_GameTime
.Get(Now
))*1000/(float)time_freq()));
726 Graphics()->QuadsText(2, 70, 16, 1,1,1,1, aBuffer
);
729 if(g_Config
.m_DbgGraphs
)
731 //Graphics()->MapScreen(0,0,400.0f,300.0f);
732 float w
= Graphics()->ScreenWidth()/4.0f
;
733 float h
= Graphics()->ScreenHeight()/6.0f
;
734 float sp
= Graphics()->ScreenWidth()/100.0f
;
735 float x
= Graphics()->ScreenWidth()-w
-sp
;
737 m_FpsGraph
.ScaleMax();
738 m_FpsGraph
.ScaleMin();
739 m_FpsGraph
.Render(Graphics(), m_DebugFont
, x
, sp
*5, w
, h
, "FPS");
740 m_InputtimeMarginGraph
.Render(Graphics(), m_DebugFont
, x
, sp
*5+h
+sp
, w
, h
, "Prediction Margin");
741 m_GametimeMarginGraph
.Render(Graphics(), m_DebugFont
, x
, sp
*5+h
+sp
+h
+sp
, w
, h
, "Gametime Margin");
747 SetState(IClient::STATE_QUITING
);
750 const char *CClient::ErrorString()
752 return m_NetClient
.ErrorString();
755 void CClient::Render()
757 if(g_Config
.m_GfxClear
)
758 Graphics()->Clear(1,1,0);
760 GameClient()->OnRender();
764 const char *CClient::LoadMap(const char *pName
, const char *pFilename
, unsigned WantedCrc
)
766 static char aErrorMsg
[128];
768 SetState(IClient::STATE_LOADING
);
770 if(!m_pMap
->Load(pFilename
))
772 str_format(aErrorMsg
, sizeof(aErrorMsg
), "map '%s' not found", pFilename
);
776 // get the crc of the map
777 if(m_pMap
->Crc() != WantedCrc
)
779 str_format(aErrorMsg
, sizeof(aErrorMsg
), "map differs from the server. %08x != %08x", m_pMap
->Crc(), WantedCrc
);
780 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client", aErrorMsg
);
785 // stop demo recording if we loaded a new map
789 str_format(aBuf
, sizeof(aBuf
), "loaded map '%s'", pFilename
);
790 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client", aBuf
);
791 m_RecivedSnapshots
= 0;
793 str_copy(m_aCurrentMap
, pName
, sizeof(m_aCurrentMap
));
794 m_CurrentMapCrc
= m_pMap
->Crc();
801 const char *CClient::LoadMapSearch(const char *pMapName
, int WantedCrc
)
803 const char *pError
= 0;
805 str_format(aBuf
, sizeof(aBuf
), "loading map, map=%s wanted crc=%08x", pMapName
, WantedCrc
);
806 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client", aBuf
);
807 SetState(IClient::STATE_LOADING
);
809 // try the normal maps folder
810 str_format(aBuf
, sizeof(aBuf
), "maps/%s.map", pMapName
);
811 pError
= LoadMap(pMapName
, aBuf
, WantedCrc
);
815 // try the downloaded maps
816 str_format(aBuf
, sizeof(aBuf
), "downloadedmaps/%s_%08x.map", pMapName
, WantedCrc
);
817 pError
= LoadMap(pMapName
, aBuf
, WantedCrc
);
821 // search for the map within subfolders
823 str_format(aFilename
, sizeof(aFilename
), "%s.map", pMapName
);
824 if(Storage()->FindFile(aFilename
, "maps", IStorage::TYPE_ALL
, aBuf
, sizeof(aBuf
)))
825 pError
= LoadMap(pMapName
, aBuf
, WantedCrc
);
830 int CClient::PlayerScoreComp(const void *a
, const void *b
)
832 CServerInfo::CClient
*p0
= (CServerInfo::CClient
*)a
;
833 CServerInfo::CClient
*p1
= (CServerInfo::CClient
*)b
;
834 if(p0
->m_Player
&& !p1
->m_Player
)
836 if(!p0
->m_Player
&& p1
->m_Player
)
838 if(p0
->m_Score
== p1
->m_Score
)
840 if(p0
->m_Score
< p1
->m_Score
)
845 void CClient::ProcessConnlessPacket(CNetChunk
*pPacket
)
848 if(m_VersionInfo
.m_State
== CVersionInfo::STATE_READY
&& net_addr_comp(&pPacket
->m_Address
, &m_VersionInfo
.m_VersionServeraddr
.m_Addr
) == 0)
851 if(pPacket
->m_DataSize
== (int)(sizeof(VERSIONSRV_VERSION
) + sizeof(VERSION_DATA
)) &&
852 mem_comp(pPacket
->m_pData
, VERSIONSRV_VERSION
, sizeof(VERSIONSRV_VERSION
)) == 0)
855 unsigned char *pVersionData
= (unsigned char*)pPacket
->m_pData
+ sizeof(VERSIONSRV_VERSION
);
856 int VersionMatch
= !mem_comp(pVersionData
, VERSION_DATA
, sizeof(VERSION_DATA
));
859 str_format(aBuf
, sizeof(aBuf
), "version does %s (%d.%d.%d)",
860 VersionMatch
? "match" : "NOT match",
861 pVersionData
[1], pVersionData
[2], pVersionData
[3]);
862 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/version", aBuf
);
864 // assume version is out of date when version-data doesn't match
867 str_format(m_aVersionStr
, sizeof(m_aVersionStr
), "%d.%d.%d", pVersionData
[1], pVersionData
[2], pVersionData
[3]);
870 // request the map version list now
872 mem_zero(&Packet
, sizeof(Packet
));
873 Packet
.m_ClientID
= -1;
874 Packet
.m_Address
= m_VersionInfo
.m_VersionServeraddr
.m_Addr
;
875 Packet
.m_pData
= VERSIONSRV_GETMAPLIST
;
876 Packet
.m_DataSize
= sizeof(VERSIONSRV_GETMAPLIST
);
877 Packet
.m_Flags
= NETSENDFLAG_CONNLESS
;
878 m_NetClient
.Send(&Packet
);
882 if(pPacket
->m_DataSize
>= (int)sizeof(VERSIONSRV_MAPLIST
) &&
883 mem_comp(pPacket
->m_pData
, VERSIONSRV_MAPLIST
, sizeof(VERSIONSRV_MAPLIST
)) == 0)
885 int Size
= pPacket
->m_DataSize
-sizeof(VERSIONSRV_MAPLIST
);
886 int Num
= Size
/sizeof(CMapVersion
);
887 m_MapChecker
.AddMaplist((CMapVersion
*)((char*)pPacket
->m_pData
+sizeof(VERSIONSRV_MAPLIST
)), Num
);
891 // server list from master server
892 if(pPacket
->m_DataSize
>= (int)sizeof(SERVERBROWSE_LIST
) &&
893 mem_comp(pPacket
->m_pData
, SERVERBROWSE_LIST
, sizeof(SERVERBROWSE_LIST
)) == 0)
895 // check for valid master server address
897 for(int i
= 0; i
< IMasterServer::MAX_MASTERSERVERS
; ++i
)
899 if(m_pMasterServer
->IsValid(i
))
901 NETADDR Addr
= m_pMasterServer
->GetAddr(i
);
902 if(net_addr_comp(&pPacket
->m_Address
, &Addr
) == 0)
912 int Size
= pPacket
->m_DataSize
-sizeof(SERVERBROWSE_LIST
);
913 int Num
= Size
/sizeof(CMastersrvAddr
);
914 CMastersrvAddr
*pAddrs
= (CMastersrvAddr
*)((char*)pPacket
->m_pData
+sizeof(SERVERBROWSE_LIST
));
915 for(int i
= 0; i
< Num
; i
++)
919 static char IPV4Mapping
[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
922 if(!mem_comp(IPV4Mapping
, pAddrs
[i
].m_aIp
, sizeof(IPV4Mapping
)))
924 mem_zero(&Addr
, sizeof(Addr
));
925 Addr
.type
= NETTYPE_IPV4
;
926 Addr
.ip
[0] = pAddrs
[i
].m_aIp
[12];
927 Addr
.ip
[1] = pAddrs
[i
].m_aIp
[13];
928 Addr
.ip
[2] = pAddrs
[i
].m_aIp
[14];
929 Addr
.ip
[3] = pAddrs
[i
].m_aIp
[15];
933 Addr
.type
= NETTYPE_IPV6
;
934 mem_copy(Addr
.ip
, pAddrs
[i
].m_aIp
, sizeof(Addr
.ip
));
936 Addr
.port
= (pAddrs
[i
].m_aPort
[0]<<8) | pAddrs
[i
].m_aPort
[1];
938 m_ServerBrowser
.Set(Addr
, IServerBrowser::SET_MASTER_ADD
, -1, 0x0);
943 if(pPacket
->m_DataSize
>= (int)sizeof(SERVERBROWSE_INFO
) && mem_comp(pPacket
->m_pData
, SERVERBROWSE_INFO
, sizeof(SERVERBROWSE_INFO
)) == 0)
947 CServerInfo Info
= {0};
949 Up
.Reset((unsigned char*)pPacket
->m_pData
+sizeof(SERVERBROWSE_INFO
), pPacket
->m_DataSize
-sizeof(SERVERBROWSE_INFO
));
950 int Token
= str_toint(Up
.GetString());
951 str_copy(Info
.m_aVersion
, Up
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
), sizeof(Info
.m_aVersion
));
952 str_copy(Info
.m_aName
, Up
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
), sizeof(Info
.m_aName
));
953 str_copy(Info
.m_aMap
, Up
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
), sizeof(Info
.m_aMap
));
954 str_copy(Info
.m_aGameType
, Up
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
), sizeof(Info
.m_aGameType
));
955 Info
.m_Flags
= str_toint(Up
.GetString());
956 Info
.m_NumPlayers
= str_toint(Up
.GetString());
957 Info
.m_MaxPlayers
= str_toint(Up
.GetString());
958 Info
.m_NumClients
= str_toint(Up
.GetString());
959 Info
.m_MaxClients
= str_toint(Up
.GetString());
961 // don't add invalid info to the server browser list
962 if(Info
.m_NumClients
< 0 || Info
.m_NumClients
> MAX_CLIENTS
|| Info
.m_MaxClients
< 0 || Info
.m_MaxClients
> MAX_CLIENTS
||
963 Info
.m_NumPlayers
< 0 || Info
.m_NumPlayers
> Info
.m_NumClients
|| Info
.m_MaxPlayers
< 0 || Info
.m_MaxPlayers
> Info
.m_MaxClients
)
966 net_addr_str(&pPacket
->m_Address
, Info
.m_aAddress
, sizeof(Info
.m_aAddress
));
968 for(int i
= 0; i
< Info
.m_NumClients
; i
++)
970 str_copy(Info
.m_aClients
[i
].m_aName
, Up
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
), sizeof(Info
.m_aClients
[i
].m_aName
));
971 str_copy(Info
.m_aClients
[i
].m_aClan
, Up
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
), sizeof(Info
.m_aClients
[i
].m_aClan
));
972 Info
.m_aClients
[i
].m_Country
= str_toint(Up
.GetString());
973 Info
.m_aClients
[i
].m_Score
= str_toint(Up
.GetString());
974 Info
.m_aClients
[i
].m_Player
= str_toint(Up
.GetString()) != 0 ? true : false;
980 qsort(Info
.m_aClients
, Info
.m_NumClients
, sizeof(*Info
.m_aClients
), PlayerScoreComp
);
982 if(net_addr_comp(&m_ServerAddress
, &pPacket
->m_Address
) == 0)
984 mem_copy(&m_CurrentServerInfo
, &Info
, sizeof(m_CurrentServerInfo
));
985 m_CurrentServerInfo
.m_NetAddr
= m_ServerAddress
;
986 m_CurrentServerInfoRequestTime
= -1;
989 m_ServerBrowser
.Set(pPacket
->m_Address
, IServerBrowser::SET_TOKEN
, Token
, &Info
);
994 void CClient::ProcessServerPacket(CNetChunk
*pPacket
)
997 Unpacker
.Reset(pPacket
->m_pData
, pPacket
->m_DataSize
);
999 // unpack msgid and system flag
1000 int Msg
= Unpacker
.GetInt();
1004 if(Unpacker
.Error())
1010 if(Msg
== NETMSG_MAP_CHANGE
)
1012 const char *pMap
= Unpacker
.GetString(CUnpacker::SANITIZE_CC
|CUnpacker::SKIP_START_WHITESPACES
);
1013 int MapCrc
= Unpacker
.GetInt();
1014 int MapSize
= Unpacker
.GetInt();
1015 const char *pError
= 0;
1017 if(Unpacker
.Error())
1020 // check for valid standard map
1021 if(!m_MapChecker
.IsMapValid(pMap
, MapCrc
, MapSize
))
1022 pError
= "invalid standard map";
1024 for(int i
= 0; pMap
[i
]; i
++) // protect the player from nasty map names
1026 if(pMap
[i
] == '/' || pMap
[i
] == '\\')
1027 pError
= "strange character in map name";
1031 pError
= "invalid map size";
1034 DisconnectWithReason(pError
);
1037 pError
= LoadMapSearch(pMap
, MapCrc
);
1041 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/network", "loading done");
1046 str_format(m_aMapdownloadFilename
, sizeof(m_aMapdownloadFilename
), "downloadedmaps/%s_%08x.map", pMap
, MapCrc
);
1049 str_format(aBuf
, sizeof(aBuf
), "starting to download map to '%s'", m_aMapdownloadFilename
);
1050 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/network", aBuf
);
1052 m_MapdownloadChunk
= 0;
1053 str_copy(m_aMapdownloadName
, pMap
, sizeof(m_aMapdownloadName
));
1054 if(m_MapdownloadFile
)
1055 io_close(m_MapdownloadFile
);
1056 m_MapdownloadFile
= Storage()->OpenFile(m_aMapdownloadFilename
, IOFLAG_WRITE
, IStorage::TYPE_SAVE
);
1057 m_MapdownloadCrc
= MapCrc
;
1058 m_MapdownloadTotalsize
= MapSize
;
1059 m_MapdownloadAmount
= 0;
1061 CMsgPacker
Msg(NETMSG_REQUEST_MAP_DATA
);
1062 Msg
.AddInt(m_MapdownloadChunk
);
1063 SendMsgEx(&Msg
, MSGFLAG_VITAL
|MSGFLAG_FLUSH
);
1065 if(g_Config
.m_Debug
)
1067 str_format(aBuf
, sizeof(aBuf
), "requested chunk %d", m_MapdownloadChunk
);
1068 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client/network", aBuf
);
1073 else if(Msg
== NETMSG_MAP_DATA
)
1075 int Last
= Unpacker
.GetInt();
1076 int MapCRC
= Unpacker
.GetInt();
1077 int Chunk
= Unpacker
.GetInt();
1078 int Size
= Unpacker
.GetInt();
1079 const unsigned char *pData
= Unpacker
.GetRaw(Size
);
1081 // check fior errors
1082 if(Unpacker
.Error() || Size
<= 0 || MapCRC
!= m_MapdownloadCrc
|| Chunk
!= m_MapdownloadChunk
|| !m_MapdownloadFile
)
1085 io_write(m_MapdownloadFile
, pData
, Size
);
1087 m_MapdownloadAmount
+= Size
;
1092 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/network", "download complete, loading map");
1094 if(m_MapdownloadFile
)
1095 io_close(m_MapdownloadFile
);
1096 m_MapdownloadFile
= 0;
1097 m_MapdownloadAmount
= 0;
1098 m_MapdownloadTotalsize
= -1;
1101 pError
= LoadMap(m_aMapdownloadName
, m_aMapdownloadFilename
, m_MapdownloadCrc
);
1104 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/network", "loading done");
1108 DisconnectWithReason(pError
);
1112 // request new chunk
1113 m_MapdownloadChunk
++;
1115 CMsgPacker
Msg(NETMSG_REQUEST_MAP_DATA
);
1116 Msg
.AddInt(m_MapdownloadChunk
);
1117 SendMsgEx(&Msg
, MSGFLAG_VITAL
|MSGFLAG_FLUSH
);
1119 if(g_Config
.m_Debug
)
1122 str_format(aBuf
, sizeof(aBuf
), "requested chunk %d", m_MapdownloadChunk
);
1123 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client/network", aBuf
);
1127 else if(Msg
== NETMSG_CON_READY
)
1129 GameClient()->OnConnected();
1131 else if(Msg
== NETMSG_PING
)
1133 CMsgPacker
Msg(NETMSG_PING_REPLY
);
1136 else if(Msg
== NETMSG_RCON_CMD_ADD
)
1138 const char *pName
= Unpacker
.GetString(CUnpacker::SANITIZE_CC
);
1139 const char *pHelp
= Unpacker
.GetString(CUnpacker::SANITIZE_CC
);
1140 const char *pParams
= Unpacker
.GetString(CUnpacker::SANITIZE_CC
);
1141 if(Unpacker
.Error() == 0)
1142 m_pConsole
->RegisterTemp(pName
, pParams
, CFGFLAG_SERVER
, pHelp
);
1144 else if(Msg
== NETMSG_RCON_CMD_REM
)
1146 const char *pName
= Unpacker
.GetString(CUnpacker::SANITIZE_CC
);
1147 if(Unpacker
.Error() == 0)
1148 m_pConsole
->DeregisterTemp(pName
);
1150 else if(Msg
== NETMSG_RCON_AUTH_STATUS
)
1152 int Result
= Unpacker
.GetInt();
1153 if(Unpacker
.Error() == 0)
1154 m_RconAuthed
= Result
;
1155 m_UseTempRconCommands
= Unpacker
.GetInt();
1156 if(Unpacker
.Error() != 0)
1157 m_UseTempRconCommands
= 0;
1159 else if(Msg
== NETMSG_RCON_LINE
)
1161 const char *pLine
= Unpacker
.GetString();
1162 if(Unpacker
.Error() == 0)
1163 GameClient()->OnRconLine(pLine
);
1165 else if(Msg
== NETMSG_PING_REPLY
)
1168 str_format(aBuf
, sizeof(aBuf
), "latency %.2f", (time_get() - m_PingStartTime
)*1000 / (float)time_freq());
1169 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client/network", aBuf
);
1171 else if(Msg
== NETMSG_INPUTTIMING
)
1173 int InputPredTick
= Unpacker
.GetInt();
1174 int TimeLeft
= Unpacker
.GetInt();
1176 // adjust our prediction time
1178 for(int k
= 0; k
< 200; k
++)
1180 if(m_aInputs
[k
].m_Tick
== InputPredTick
)
1182 Target
= m_aInputs
[k
].m_PredictedTime
+ (time_get() - m_aInputs
[k
].m_Time
);
1183 Target
= Target
- (int64
)(((TimeLeft
-PREDICTION_MARGIN
)/1000.0f
)*time_freq());
1189 m_PredictedTime
.Update(&m_InputtimeMarginGraph
, Target
, TimeLeft
, 1);
1191 else if(Msg
== NETMSG_SNAP
|| Msg
== NETMSG_SNAPSINGLE
|| Msg
== NETMSG_SNAPEMPTY
)
1195 int GameTick
= Unpacker
.GetInt();
1196 int DeltaTick
= GameTick
-Unpacker
.GetInt();
1199 int CompleteSize
= 0;
1200 const char *pData
= 0;
1202 // we are not allowed to process snapshot yet
1203 if(State() < IClient::STATE_LOADING
)
1206 if(Msg
== NETMSG_SNAP
)
1208 NumParts
= Unpacker
.GetInt();
1209 Part
= Unpacker
.GetInt();
1212 if(Msg
!= NETMSG_SNAPEMPTY
)
1214 Crc
= Unpacker
.GetInt();
1215 PartSize
= Unpacker
.GetInt();
1218 pData
= (const char *)Unpacker
.GetRaw(PartSize
);
1220 if(Unpacker
.Error())
1223 if(GameTick
>= m_CurrentRecvTick
)
1225 if(GameTick
!= m_CurrentRecvTick
)
1227 m_SnapshotParts
= 0;
1228 m_CurrentRecvTick
= GameTick
;
1231 // TODO: clean this up abit
1232 mem_copy((char*)m_aSnapshotIncommingData
+ Part
*MAX_SNAPSHOT_PACKSIZE
, pData
, PartSize
);
1233 m_SnapshotParts
|= 1<<Part
;
1235 if(m_SnapshotParts
== (unsigned)((1<<NumParts
)-1))
1237 static CSnapshot Emptysnap
;
1238 CSnapshot
*pDeltaShot
= &Emptysnap
;
1242 unsigned char aTmpBuffer2
[CSnapshot::MAX_SIZE
];
1243 unsigned char aTmpBuffer3
[CSnapshot::MAX_SIZE
];
1244 CSnapshot
*pTmpBuffer3
= (CSnapshot
*)aTmpBuffer3
; // Fix compiler warning for strict-aliasing
1247 CompleteSize
= (NumParts
-1) * MAX_SNAPSHOT_PACKSIZE
+ PartSize
;
1249 // reset snapshoting
1250 m_SnapshotParts
= 0;
1252 // find snapshot that we should use as delta
1258 int DeltashotSize
= m_SnapshotStorage
.Get(DeltaTick
, 0, &pDeltaShot
, 0);
1260 if(DeltashotSize
< 0)
1262 // couldn't find the delta snapshots that the server used
1263 // to compress this snapshot. force the server to resync
1264 if(g_Config
.m_Debug
)
1267 str_format(aBuf
, sizeof(aBuf
), "error, couldn't find the delta snapshot");
1268 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", aBuf
);
1272 // TODO: combine this with the input message
1278 // decompress snapshot
1279 pDeltaData
= m_SnapshotDelta
.EmptyDelta();
1280 DeltaSize
= sizeof(int)*3;
1284 int IntSize
= CVariableInt::Decompress(m_aSnapshotIncommingData
, CompleteSize
, aTmpBuffer2
);
1286 if(IntSize
< 0) // failure during decompression, bail
1289 pDeltaData
= aTmpBuffer2
;
1290 DeltaSize
= IntSize
;
1294 PurgeTick
= DeltaTick
;
1295 SnapSize
= m_SnapshotDelta
.UnpackDelta(pDeltaShot
, pTmpBuffer3
, pDeltaData
, DeltaSize
);
1298 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", "delta unpack failed!");
1302 if(Msg
!= NETMSG_SNAPEMPTY
&& pTmpBuffer3
->Crc() != Crc
)
1304 if(g_Config
.m_Debug
)
1307 str_format(aBuf
, sizeof(aBuf
), "snapshot crc error #%d - tick=%d wantedcrc=%d gotcrc=%d compressed_size=%d delta_tick=%d",
1308 m_SnapCrcErrors
, GameTick
, Crc
, pTmpBuffer3
->Crc(), CompleteSize
, DeltaTick
);
1309 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", aBuf
);
1313 if(m_SnapCrcErrors
> 10)
1315 // to many errors, send reset
1318 m_SnapCrcErrors
= 0;
1328 // purge old snapshots
1329 PurgeTick
= DeltaTick
;
1330 if(m_aSnapshots
[SNAP_PREV
] && m_aSnapshots
[SNAP_PREV
]->m_Tick
< PurgeTick
)
1331 PurgeTick
= m_aSnapshots
[SNAP_PREV
]->m_Tick
;
1332 if(m_aSnapshots
[SNAP_CURRENT
] && m_aSnapshots
[SNAP_CURRENT
]->m_Tick
< PurgeTick
)
1333 PurgeTick
= m_aSnapshots
[SNAP_PREV
]->m_Tick
;
1334 m_SnapshotStorage
.PurgeUntil(PurgeTick
);
1337 m_SnapshotStorage
.Add(GameTick
, time_get(), SnapSize
, pTmpBuffer3
, 1);
1339 // add snapshot to demo
1340 if(m_DemoRecorder
.IsRecording())
1343 m_DemoRecorder
.RecordSnapshot(GameTick
, pTmpBuffer3
, SnapSize
);
1346 // apply snapshot, cycle pointers
1347 m_RecivedSnapshots
++;
1349 m_CurrentRecvTick
= GameTick
;
1351 // we got two snapshots until we see us self as connected
1352 if(m_RecivedSnapshots
== 2)
1354 // start at 200ms and work from there
1355 m_PredictedTime
.Init(GameTick
*time_freq()/50);
1356 m_PredictedTime
.SetAdjustSpeed(1, 1000.0f
);
1357 m_GameTime
.Init((GameTick
-1)*time_freq()/50);
1358 m_aSnapshots
[SNAP_PREV
] = m_SnapshotStorage
.m_pFirst
;
1359 m_aSnapshots
[SNAP_CURRENT
] = m_SnapshotStorage
.m_pLast
;
1360 m_LocalStartTime
= time_get();
1361 SetState(IClient::STATE_ONLINE
);
1362 DemoRecorder_HandleAutoStart();
1366 if(m_RecivedSnapshots
> 2)
1368 int64 Now
= m_GameTime
.Get(time_get());
1369 int64 TickStart
= GameTick
*time_freq()/50;
1370 int64 TimeLeft
= (TickStart
-Now
)*1000 / time_freq();
1371 m_GameTime
.Update(&m_GametimeMarginGraph
, (GameTick
-1)*time_freq()/50, TimeLeft
, 0);
1375 m_AckGameTick
= GameTick
;
1383 if(m_DemoRecorder
.IsRecording())
1384 m_DemoRecorder
.RecordMessage(pPacket
->m_pData
, pPacket
->m_DataSize
);
1386 GameClient()->OnMessage(Msg
, &Unpacker
);
1390 void CClient::PumpNetwork()
1392 m_NetClient
.Update();
1394 if(State() != IClient::STATE_DEMOPLAYBACK
)
1397 if(State() != IClient::STATE_OFFLINE
&& State() != IClient::STATE_QUITING
&& m_NetClient
.State() == NETSTATE_OFFLINE
)
1399 SetState(IClient::STATE_OFFLINE
);
1402 str_format(aBuf
, sizeof(aBuf
), "offline error='%s'", m_NetClient
.ErrorString());
1403 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client", aBuf
);
1407 if(State() == IClient::STATE_CONNECTING
&& m_NetClient
.State() == NETSTATE_ONLINE
)
1409 // we switched to online
1410 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client", "connected, sending info");
1411 SetState(IClient::STATE_LOADING
);
1418 while(m_NetClient
.Recv(&Packet
))
1420 if(Packet
.m_ClientID
== -1)
1421 ProcessConnlessPacket(&Packet
);
1423 ProcessServerPacket(&Packet
);
1427 void CClient::OnDemoPlayerSnapshot(void *pData
, int Size
)
1429 // update ticks, they could have changed
1430 const CDemoPlayer::CPlaybackInfo
*pInfo
= m_DemoPlayer
.Info();
1431 CSnapshotStorage::CHolder
*pTemp
;
1432 m_CurGameTick
= pInfo
->m_Info
.m_CurrentTick
;
1433 m_PrevGameTick
= pInfo
->m_PreviousTick
;
1436 pTemp
= m_aSnapshots
[SNAP_PREV
];
1437 m_aSnapshots
[SNAP_PREV
] = m_aSnapshots
[SNAP_CURRENT
];
1438 m_aSnapshots
[SNAP_CURRENT
] = pTemp
;
1440 mem_copy(m_aSnapshots
[SNAP_CURRENT
]->m_pSnap
, pData
, Size
);
1441 mem_copy(m_aSnapshots
[SNAP_CURRENT
]->m_pAltSnap
, pData
, Size
);
1443 GameClient()->OnNewSnapshot();
1446 void CClient::OnDemoPlayerMessage(void *pData
, int Size
)
1449 Unpacker
.Reset(pData
, Size
);
1451 // unpack msgid and system flag
1452 int Msg
= Unpacker
.GetInt();
1456 if(Unpacker
.Error())
1460 GameClient()->OnMessage(Msg
, &Unpacker
);
1463 const IDemoPlayer::CInfo *client_demoplayer_getinfo()
1465 static DEMOPLAYBACK_INFO ret;
1466 const DEMOREC_PLAYBACKINFO *info = m_DemoPlayer.Info();
1467 ret.first_tick = info->first_tick;
1468 ret.last_tick = info->last_tick;
1469 ret.current_tick = info->current_tick;
1470 ret.paused = info->paused;
1471 ret.speed = info->speed;
1476 void DemoPlayer()->SetPos(float percent)
1478 demorec_playback_set(percent);
1481 void DemoPlayer()->SetSpeed(float speed)
1483 demorec_playback_setspeed(speed);
1486 void DemoPlayer()->SetPause(int paused)
1489 demorec_playback_pause();
1491 demorec_playback_unpause();
1494 void CClient::Update()
1496 if(State() == IClient::STATE_DEMOPLAYBACK
)
1498 m_DemoPlayer
.Update();
1499 if(m_DemoPlayer
.IsPlaying())
1502 const CDemoPlayer::CPlaybackInfo
*pInfo
= m_DemoPlayer
.Info();
1503 m_CurGameTick
= pInfo
->m_Info
.m_CurrentTick
;
1504 m_PrevGameTick
= pInfo
->m_PreviousTick
;
1505 m_GameIntraTick
= pInfo
->m_IntraTick
;
1506 m_GameTickTime
= pInfo
->m_TickTime
;
1510 // disconnect on error
1514 else if(State() == IClient::STATE_ONLINE
&& m_RecivedSnapshots
>= 3)
1518 int64 Freq
= time_freq();
1519 int64 Now
= m_GameTime
.Get(time_get());
1520 int64 PredNow
= m_PredictedTime
.Get(time_get());
1524 CSnapshotStorage::CHolder
*pCur
= m_aSnapshots
[SNAP_CURRENT
];
1525 int64 TickStart
= (pCur
->m_Tick
)*time_freq()/50;
1529 CSnapshotStorage::CHolder
*pNext
= m_aSnapshots
[SNAP_CURRENT
]->m_pNext
;
1532 m_aSnapshots
[SNAP_PREV
] = m_aSnapshots
[SNAP_CURRENT
];
1533 m_aSnapshots
[SNAP_CURRENT
] = pNext
;
1536 m_CurGameTick
= m_aSnapshots
[SNAP_CURRENT
]->m_Tick
;
1537 m_PrevGameTick
= m_aSnapshots
[SNAP_PREV
]->m_Tick
;
1539 if(m_aSnapshots
[SNAP_CURRENT
] && m_aSnapshots
[SNAP_PREV
])
1541 GameClient()->OnNewSnapshot();
1552 if(m_aSnapshots
[SNAP_CURRENT
] && m_aSnapshots
[SNAP_PREV
])
1554 int64 CurtickStart
= (m_aSnapshots
[SNAP_CURRENT
]->m_Tick
)*time_freq()/50;
1555 int64 PrevtickStart
= (m_aSnapshots
[SNAP_PREV
]->m_Tick
)*time_freq()/50;
1556 int PrevPredTick
= (int)(PredNow
*50/time_freq());
1557 int NewPredTick
= PrevPredTick
+1;
1559 m_GameIntraTick
= (Now
- PrevtickStart
) / (float)(CurtickStart
-PrevtickStart
);
1560 m_GameTickTime
= (Now
- PrevtickStart
) / (float)Freq
; //(float)SERVER_TICK_SPEED);
1562 CurtickStart
= NewPredTick
*time_freq()/50;
1563 PrevtickStart
= PrevPredTick
*time_freq()/50;
1564 m_PredIntraTick
= (PredNow
- PrevtickStart
) / (float)(CurtickStart
-PrevtickStart
);
1566 if(NewPredTick
< m_aSnapshots
[SNAP_PREV
]->m_Tick
-SERVER_TICK_SPEED
|| NewPredTick
> m_aSnapshots
[SNAP_PREV
]->m_Tick
+SERVER_TICK_SPEED
)
1568 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client", "prediction time reset!");
1569 m_PredictedTime
.Init(m_aSnapshots
[SNAP_CURRENT
]->m_Tick
*time_freq()/50);
1572 if(NewPredTick
> m_PredTick
)
1574 m_PredTick
= NewPredTick
;
1582 // only do sane predictions
1585 if(m_PredTick
> m_CurGameTick
&& m_PredTick
< m_CurGameTick
+50)
1586 GameClient()->OnPredict();
1589 // fetch server info if we don't have it
1590 if(State() >= IClient::STATE_LOADING
&&
1591 m_CurrentServerInfoRequestTime
>= 0 &&
1592 time_get() > m_CurrentServerInfoRequestTime
)
1594 m_ServerBrowser
.Request(m_ServerAddress
);
1595 m_CurrentServerInfoRequestTime
= time_get()+time_freq()*2;
1599 // STRESS TEST: join the server again
1600 if(g_Config
.m_DbgStress
)
1602 static int64 ActionTaken
= 0;
1603 int64 Now
= time_get();
1604 if(State() == IClient::STATE_OFFLINE
)
1606 if(Now
> ActionTaken
+time_freq()*2)
1608 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "stress", "reconnecting!");
1609 Connect(g_Config
.m_DbgStressServer
);
1615 if(Now
> ActionTaken
+time_freq()*(10+g_Config
.m_DbgStress
))
1617 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "stress", "disconnecting!");
1627 // update the maser server registry
1628 MasterServer()->Update();
1630 // update the server browser
1631 m_ServerBrowser
.Update(m_ResortServerBrowser
);
1632 m_ResortServerBrowser
= false;
1635 void CClient::VersionUpdate()
1637 if(m_VersionInfo
.m_State
== CVersionInfo::STATE_INIT
)
1639 Engine()->HostLookup(&m_VersionInfo
.m_VersionServeraddr
, g_Config
.m_ClVersionServer
, m_NetClient
.NetType());
1640 m_VersionInfo
.m_State
= CVersionInfo::STATE_START
;
1642 else if(m_VersionInfo
.m_State
== CVersionInfo::STATE_START
)
1644 if(m_VersionInfo
.m_VersionServeraddr
.m_Job
.Status() == CJob::STATE_DONE
)
1648 mem_zero(&Packet
, sizeof(Packet
));
1650 m_VersionInfo
.m_VersionServeraddr
.m_Addr
.port
= VERSIONSRV_PORT
;
1652 Packet
.m_ClientID
= -1;
1653 Packet
.m_Address
= m_VersionInfo
.m_VersionServeraddr
.m_Addr
;
1654 Packet
.m_pData
= VERSIONSRV_GETVERSION
;
1655 Packet
.m_DataSize
= sizeof(VERSIONSRV_GETVERSION
);
1656 Packet
.m_Flags
= NETSENDFLAG_CONNLESS
;
1658 m_NetClient
.Send(&Packet
);
1659 m_VersionInfo
.m_State
= CVersionInfo::STATE_READY
;
1664 void CClient::RegisterInterfaces()
1666 Kernel()->RegisterInterface(static_cast<IDemoRecorder
*>(&m_DemoRecorder
));
1667 Kernel()->RegisterInterface(static_cast<IDemoPlayer
*>(&m_DemoPlayer
));
1668 Kernel()->RegisterInterface(static_cast<IServerBrowser
*>(&m_ServerBrowser
));
1669 Kernel()->RegisterInterface(static_cast<IFriends
*>(&m_Friends
));
1672 void CClient::InitInterfaces()
1675 m_pEngine
= Kernel()->RequestInterface
<IEngine
>();
1676 m_pEditor
= Kernel()->RequestInterface
<IEditor
>();
1677 m_pGraphics
= Kernel()->RequestInterface
<IEngineGraphics
>();
1678 m_pSound
= Kernel()->RequestInterface
<IEngineSound
>();
1679 m_pGameClient
= Kernel()->RequestInterface
<IGameClient
>();
1680 m_pInput
= Kernel()->RequestInterface
<IEngineInput
>();
1681 m_pMap
= Kernel()->RequestInterface
<IEngineMap
>();
1682 m_pMasterServer
= Kernel()->RequestInterface
<IEngineMasterServer
>();
1683 m_pStorage
= Kernel()->RequestInterface
<IStorage
>();
1686 m_ServerBrowser
.SetBaseInfo(&m_NetClient
, m_pGameClient
->NetVersion());
1692 int64 ReportTime
= time_get();
1693 int64 ReportInterval
= time_freq()*1;
1695 m_LocalStartTime
= time_get();
1696 m_SnapshotParts
= 0;
1699 if(m_pGraphics
->Init() != 0)
1705 mem_zero(&BindAddr
, sizeof(BindAddr
));
1706 BindAddr
.type
= NETTYPE_ALL
;
1707 if(!m_NetClient
.Open(BindAddr
, 0))
1709 dbg_msg("client", "couldn't start network");
1714 // init font rendering
1715 Kernel()->RequestInterface
<IEngineTextRender
>()->Init();
1720 // start refreshing addresses while we load
1721 MasterServer()->RefreshAddresses(m_NetClient
.NetType());
1726 // init sound, allowed to fail
1727 m_SoundInitFailed
= Sound()->Init() != 0;
1733 GameClient()->OnInit();
1735 str_format(aBuf
, sizeof(aBuf
), "version %s", GameClient()->NetVersion());
1736 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "client", aBuf
);
1738 // connect to the server if wanted
1740 if(config.cl_connect[0] != 0)
1741 Connect(config.cl_connect);
1742 config.cl_connect[0] = 0;
1746 m_FpsGraph
.Init(0.0f
, 200.0f
);
1748 // never start with the editor
1749 g_Config
.m_ClEditor
= 0;
1751 Input()->MouseModeRelative();
1753 // process pending commands
1754 m_pConsole
->StoreCommands(false);
1758 int64 FrameStartTime
= time_get();
1764 // handle pending connects
1765 if(m_aCmdConnect
[0])
1767 str_copy(g_Config
.m_UiServerAddress
, m_aCmdConnect
, sizeof(g_Config
.m_UiServerAddress
));
1768 Connect(m_aCmdConnect
);
1769 m_aCmdConnect
[0] = 0;
1773 if(Input()->Update())
1780 if(!m_pGraphics
->WindowActive())
1782 if(m_WindowMustRefocus
== 0)
1783 Input()->MouseModeAbsolute();
1784 m_WindowMustRefocus
= 1;
1786 else if (g_Config
.m_DbgFocus
&& Input()->KeyPressed(KEY_ESCAPE
))
1788 Input()->MouseModeAbsolute();
1789 m_WindowMustRefocus
= 1;
1793 if(m_WindowMustRefocus
&& m_pGraphics
->WindowActive())
1795 if(m_WindowMustRefocus
< 3)
1797 Input()->MouseModeAbsolute();
1798 m_WindowMustRefocus
++;
1801 if(m_WindowMustRefocus
>= 3 || Input()->KeyPressed(KEY_MOUSE_1
))
1803 Input()->MouseModeRelative();
1804 m_WindowMustRefocus
= 0;
1808 // panic quit button
1809 if(Input()->KeyPressed(KEY_LCTRL
) && Input()->KeyPressed(KEY_LSHIFT
) && Input()->KeyPressed('q'))
1812 if(Input()->KeyPressed(KEY_LCTRL
) && Input()->KeyPressed(KEY_LSHIFT
) && Input()->KeyDown('d'))
1813 g_Config
.m_Debug
^= 1;
1815 if(Input()->KeyPressed(KEY_LCTRL
) && Input()->KeyPressed(KEY_LSHIFT
) && Input()->KeyDown('g'))
1816 g_Config
.m_DbgGraphs
^= 1;
1818 if(Input()->KeyPressed(KEY_LCTRL
) && Input()->KeyPressed(KEY_LSHIFT
) && Input()->KeyDown('e'))
1820 g_Config
.m_ClEditor
= g_Config
.m_ClEditor
^1;
1821 Input()->MouseModeRelative();
1825 if(!gfx_window_open())
1830 if(g_Config
.m_ClEditor
)
1834 GameClient()->OnActivateEditor();
1835 m_EditorActive
= true;
1839 m_pEditor
->UpdateAndRender();
1841 m_pGraphics
->Swap();
1846 m_EditorActive
= false;
1850 if(g_Config
.m_DbgStress
)
1852 if((m_Frames
%10) == 0)
1855 m_pGraphics
->Swap();
1861 m_pGraphics
->Swap();
1865 AutoScreenshot_Cleanup();
1868 if(State() == IClient::STATE_QUITING
)
1872 if(g_Config
.m_DbgStress
)
1874 else if(g_Config
.m_ClCpuThrottle
|| !m_pGraphics
->WindowActive())
1877 if(g_Config
.m_DbgHitch
)
1879 thread_sleep(g_Config
.m_DbgHitch
);
1880 g_Config
.m_DbgHitch
= 0;
1883 if(ReportTime
< time_get())
1885 if(0 && g_Config
.m_Debug
)
1887 dbg_msg("client/report", "fps=%.02f (%.02f %.02f) netstate=%d",
1888 m_Frames
/(float)(ReportInterval
/time_freq()),
1889 1.0f
/m_FrameTimeHigh
,
1890 1.0f
/m_FrameTimeLow
,
1891 m_NetClient
.State());
1894 m_FrameTimeHigh
= 0;
1896 ReportTime
+= ReportInterval
;
1900 m_FrameTime
= (time_get()-FrameStartTime
)/(float)time_freq();
1901 if(m_FrameTime
< m_FrameTimeLow
)
1902 m_FrameTimeLow
= m_FrameTime
;
1903 if(m_FrameTime
> m_FrameTimeHigh
)
1904 m_FrameTimeHigh
= m_FrameTime
;
1906 m_LocalTime
= (time_get()-m_LocalStartTime
)/(float)time_freq();
1908 m_FpsGraph
.Add(1.0f
/m_FrameTime
, 1,1,1);
1911 GameClient()->OnShutdown();
1914 m_pGraphics
->Shutdown();
1915 m_pSound
->Shutdown();
1919 void CClient::Con_Connect(IConsole::IResult
*pResult
, void *pUserData
)
1921 CClient
*pSelf
= (CClient
*)pUserData
;
1922 str_copy(pSelf
->m_aCmdConnect
, pResult
->GetString(0), sizeof(pSelf
->m_aCmdConnect
));
1925 void CClient::Con_Disconnect(IConsole::IResult
*pResult
, void *pUserData
)
1927 CClient
*pSelf
= (CClient
*)pUserData
;
1928 pSelf
->Disconnect();
1931 void CClient::Con_Quit(IConsole::IResult
*pResult
, void *pUserData
)
1933 CClient
*pSelf
= (CClient
*)pUserData
;
1937 void CClient::Con_Minimize(IConsole::IResult
*pResult
, void *pUserData
)
1939 CClient
*pSelf
= (CClient
*)pUserData
;
1940 pSelf
->Graphics()->Minimize();
1943 void CClient::Con_Ping(IConsole::IResult
*pResult
, void *pUserData
)
1945 CClient
*pSelf
= (CClient
*)pUserData
;
1947 CMsgPacker
Msg(NETMSG_PING
);
1948 pSelf
->SendMsgEx(&Msg
, 0);
1949 pSelf
->m_PingStartTime
= time_get();
1952 void CClient::AutoScreenshot_Start()
1954 if(g_Config
.m_ClAutoScreenshot
)
1956 Graphics()->TakeScreenshot("auto/autoscreen");
1957 m_AutoScreenshotRecycle
= true;
1961 void CClient::AutoScreenshot_Cleanup()
1963 if(m_AutoScreenshotRecycle
)
1965 if(g_Config
.m_ClAutoScreenshotMax
)
1967 // clean up auto taken screens
1968 CFileCollection AutoScreens
;
1969 AutoScreens
.Init(Storage(), "screenshots/auto", "autoscreen", ".png", g_Config
.m_ClAutoScreenshotMax
);
1971 m_AutoScreenshotRecycle
= false;
1975 void CClient::Con_Screenshot(IConsole::IResult
*pResult
, void *pUserData
)
1977 CClient
*pSelf
= (CClient
*)pUserData
;
1978 pSelf
->Graphics()->TakeScreenshot(0);
1981 void CClient::Con_Rcon(IConsole::IResult
*pResult
, void *pUserData
)
1983 CClient
*pSelf
= (CClient
*)pUserData
;
1984 pSelf
->Rcon(pResult
->GetString(0));
1987 void CClient::Con_RconAuth(IConsole::IResult
*pResult
, void *pUserData
)
1989 CClient
*pSelf
= (CClient
*)pUserData
;
1990 pSelf
->RconAuth("", pResult
->GetString(0));
1993 void CClient::Con_AddFavorite(IConsole::IResult
*pResult
, void *pUserData
)
1995 CClient
*pSelf
= (CClient
*)pUserData
;
1997 if(net_addr_from_str(&Addr
, pResult
->GetString(0)) == 0)
1998 pSelf
->m_ServerBrowser
.AddFavorite(Addr
);
2001 void CClient::Con_RemoveFavorite(IConsole::IResult
*pResult
, void *pUserData
)
2003 CClient
*pSelf
= (CClient
*)pUserData
;
2005 if(net_addr_from_str(&Addr
, pResult
->GetString(0)) == 0)
2006 pSelf
->m_ServerBrowser
.RemoveFavorite(Addr
);
2009 const char *CClient::DemoPlayer_Play(const char *pFilename
, int StorageType
)
2014 m_NetClient
.ResetErrorString();
2016 // try to start playback
2017 m_DemoPlayer
.SetListner(this);
2019 if(m_DemoPlayer
.Load(Storage(), m_pConsole
, pFilename
, StorageType
))
2020 return "error loading demo";
2023 Crc
= (m_DemoPlayer
.Info()->m_Header
.m_aMapCrc
[0]<<24)|
2024 (m_DemoPlayer
.Info()->m_Header
.m_aMapCrc
[1]<<16)|
2025 (m_DemoPlayer
.Info()->m_Header
.m_aMapCrc
[2]<<8)|
2026 (m_DemoPlayer
.Info()->m_Header
.m_aMapCrc
[3]);
2027 pError
= LoadMapSearch(m_DemoPlayer
.Info()->m_Header
.m_aMapName
, Crc
);
2030 DisconnectWithReason(pError
);
2034 GameClient()->OnConnected();
2037 mem_zero(m_aDemorecSnapshotData
, sizeof(m_aDemorecSnapshotData
));
2039 m_aSnapshots
[SNAP_CURRENT
] = &m_aDemorecSnapshotHolders
[SNAP_CURRENT
];
2040 m_aSnapshots
[SNAP_PREV
] = &m_aDemorecSnapshotHolders
[SNAP_PREV
];
2042 m_aSnapshots
[SNAP_CURRENT
]->m_pSnap
= (CSnapshot
*)m_aDemorecSnapshotData
[SNAP_CURRENT
][0];
2043 m_aSnapshots
[SNAP_CURRENT
]->m_pAltSnap
= (CSnapshot
*)m_aDemorecSnapshotData
[SNAP_CURRENT
][1];
2044 m_aSnapshots
[SNAP_CURRENT
]->m_SnapSize
= 0;
2045 m_aSnapshots
[SNAP_CURRENT
]->m_Tick
= -1;
2047 m_aSnapshots
[SNAP_PREV
]->m_pSnap
= (CSnapshot
*)m_aDemorecSnapshotData
[SNAP_PREV
][0];
2048 m_aSnapshots
[SNAP_PREV
]->m_pAltSnap
= (CSnapshot
*)m_aDemorecSnapshotData
[SNAP_PREV
][1];
2049 m_aSnapshots
[SNAP_PREV
]->m_SnapSize
= 0;
2050 m_aSnapshots
[SNAP_PREV
]->m_Tick
= -1;
2052 // enter demo playback state
2053 SetState(IClient::STATE_DEMOPLAYBACK
);
2055 m_DemoPlayer
.Play();
2056 GameClient()->OnEnterGame();
2061 void CClient::Con_Play(IConsole::IResult
*pResult
, void *pUserData
)
2063 CClient
*pSelf
= (CClient
*)pUserData
;
2064 pSelf
->DemoPlayer_Play(pResult
->GetString(0), IStorage::TYPE_ALL
);
2067 void CClient::DemoRecorder_Start(const char *pFilename
, bool WithTimestamp
)
2069 if(State() != IClient::STATE_ONLINE
)
2070 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_STANDARD
, "demorec/record", "client is not online");
2073 char aFilename
[128];
2077 str_timestamp(aDate
, sizeof(aDate
));
2078 str_format(aFilename
, sizeof(aFilename
), "demos/%s_%s.demo", pFilename
, aDate
);
2081 str_format(aFilename
, sizeof(aFilename
), "demos/%s.demo", pFilename
);
2082 m_DemoRecorder
.Start(Storage(), m_pConsole
, aFilename
, GameClient()->NetVersion(), m_aCurrentMap
, m_CurrentMapCrc
, "client");
2086 void CClient::DemoRecorder_HandleAutoStart()
2088 if(g_Config
.m_ClAutoDemoRecord
)
2090 DemoRecorder_Stop();
2091 DemoRecorder_Start("auto/autorecord", true);
2092 if(g_Config
.m_ClAutoDemoMax
)
2094 // clean up auto recorded demos
2095 CFileCollection AutoDemos
;
2096 AutoDemos
.Init(Storage(), "demos/auto", "autorecord", ".demo", g_Config
.m_ClAutoDemoMax
);
2101 void CClient::DemoRecorder_Stop()
2103 m_DemoRecorder
.Stop();
2106 void CClient::Con_Record(IConsole::IResult
*pResult
, void *pUserData
)
2108 CClient
*pSelf
= (CClient
*)pUserData
;
2109 if(pResult
->NumArguments())
2110 pSelf
->DemoRecorder_Start(pResult
->GetString(0), false);
2112 pSelf
->DemoRecorder_Start("demo", true);
2115 void CClient::Con_StopRecord(IConsole::IResult
*pResult
, void *pUserData
)
2117 CClient
*pSelf
= (CClient
*)pUserData
;
2118 pSelf
->DemoRecorder_Stop();
2121 void CClient::ServerBrowserUpdate()
2123 m_ResortServerBrowser
= true;
2126 void CClient::ConchainServerBrowserUpdate(IConsole::IResult
*pResult
, void *pUserData
, IConsole::FCommandCallback pfnCallback
, void *pCallbackUserData
)
2128 pfnCallback(pResult
, pCallbackUserData
);
2129 if(pResult
->NumArguments())
2130 ((CClient
*)pUserData
)->ServerBrowserUpdate();
2133 void CClient::RegisterCommands()
2135 m_pConsole
= Kernel()->RequestInterface
<IConsole
>();
2136 // register server dummy commands for tab completion
2137 m_pConsole
->Register("kick", "i?r", CFGFLAG_SERVER
, 0, 0, "Kick player with specified id for any reason");
2138 m_pConsole
->Register("ban", "s?ir", CFGFLAG_SERVER
, 0, 0, "Ban player with ip/id for x minutes for any reason");
2139 m_pConsole
->Register("unban", "s", CFGFLAG_SERVER
, 0, 0, "Unban ip");
2140 m_pConsole
->Register("bans", "", CFGFLAG_SERVER
, 0, 0, "Show banlist");
2141 m_pConsole
->Register("status", "", CFGFLAG_SERVER
, 0, 0, "List players");
2142 m_pConsole
->Register("shutdown", "", CFGFLAG_SERVER
, 0, 0, "Shut down");
2143 m_pConsole
->Register("record", "?s", CFGFLAG_SERVER
, 0, 0, "Record to a file");
2144 m_pConsole
->Register("stoprecord", "", CFGFLAG_SERVER
, 0, 0, "Stop recording");
2145 m_pConsole
->Register("reload", "", CFGFLAG_SERVER
, 0, 0, "Reload the map");
2147 m_pConsole
->Register("quit", "", CFGFLAG_CLIENT
|CFGFLAG_STORE
, Con_Quit
, this, "Quit Teeworlds");
2148 m_pConsole
->Register("exit", "", CFGFLAG_CLIENT
|CFGFLAG_STORE
, Con_Quit
, this, "Quit Teeworlds");
2149 m_pConsole
->Register("minimize", "", CFGFLAG_CLIENT
|CFGFLAG_STORE
, Con_Minimize
, this, "Minimize Teeworlds");
2150 m_pConsole
->Register("connect", "s", CFGFLAG_CLIENT
, Con_Connect
, this, "Connect to the specified host/ip");
2151 m_pConsole
->Register("disconnect", "", CFGFLAG_CLIENT
, Con_Disconnect
, this, "Disconnect from the server");
2152 m_pConsole
->Register("ping", "", CFGFLAG_CLIENT
, Con_Ping
, this, "Ping the current server");
2153 m_pConsole
->Register("screenshot", "", CFGFLAG_CLIENT
, Con_Screenshot
, this, "Take a screenshot");
2154 m_pConsole
->Register("rcon", "r", CFGFLAG_CLIENT
, Con_Rcon
, this, "Send specified command to rcon");
2155 m_pConsole
->Register("rcon_auth", "s", CFGFLAG_CLIENT
, Con_RconAuth
, this, "Authenticate to rcon");
2156 m_pConsole
->Register("play", "r", CFGFLAG_CLIENT
, Con_Play
, this, "Play the file specified");
2157 m_pConsole
->Register("record", "?s", CFGFLAG_CLIENT
, Con_Record
, this, "Record to the file");
2158 m_pConsole
->Register("stoprecord", "", CFGFLAG_CLIENT
, Con_StopRecord
, this, "Stop recording");
2159 m_pConsole
->Register("add_favorite", "s", CFGFLAG_CLIENT
, Con_AddFavorite
, this, "Add a server as a favorite");
2160 m_pConsole
->Register("remove_favorite", "s", CFGFLAG_CLIENT
, Con_RemoveFavorite
, this, "Remove a server from favorites");
2162 // used for server browser update
2163 m_pConsole
->Chain("br_filter_string", ConchainServerBrowserUpdate
, this);
2164 m_pConsole
->Chain("br_filter_gametype", ConchainServerBrowserUpdate
, this);
2165 m_pConsole
->Chain("br_filter_serveraddress", ConchainServerBrowserUpdate
, this);
2168 static CClient
*CreateClient() { return new CClient(); }
2173 Client Predicted Time
2182 #if defined(CONF_PLATFORM_MACOSX)
2183 extern "C" int SDL_main(int argc
, const char **argv
) // ignore_convention
2185 int main(int argc
, const char **argv
) // ignore_convention
2188 #if defined(CONF_FAMILY_WINDOWS)
2189 for(int i
= 1; i
< argc
; i
++) // ignore_convention
2191 if(str_comp("-s", argv
[i
]) == 0 || str_comp("--silent", argv
[i
]) == 0) // ignore_convention
2199 CClient
*pClient
= CreateClient();
2200 IKernel
*pKernel
= IKernel::Create();
2201 pKernel
->RegisterInterface(pClient
);
2202 pClient
->RegisterInterfaces();
2204 // create the components
2205 IEngine
*pEngine
= CreateEngine("Teeworlds");
2206 IConsole
*pConsole
= CreateConsole(CFGFLAG_CLIENT
);
2207 IStorage
*pStorage
= CreateStorage("Teeworlds", argc
, argv
); // ignore_convention
2208 IConfig
*pConfig
= CreateConfig();
2209 IEngineGraphics
*pEngineGraphics
= CreateEngineGraphics();
2210 IEngineSound
*pEngineSound
= CreateEngineSound();
2211 IEngineInput
*pEngineInput
= CreateEngineInput();
2212 IEngineTextRender
*pEngineTextRender
= CreateEngineTextRender();
2213 IEngineMap
*pEngineMap
= CreateEngineMap();
2214 IEngineMasterServer
*pEngineMasterServer
= CreateEngineMasterServer();
2217 bool RegisterFail
= false;
2219 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(pEngine
);
2220 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(pConsole
);
2221 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(pConfig
);
2223 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IEngineGraphics
*>(pEngineGraphics
)); // register graphics as both
2224 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IGraphics
*>(pEngineGraphics
));
2226 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IEngineSound
*>(pEngineSound
)); // register as both
2227 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<ISound
*>(pEngineSound
));
2229 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IEngineInput
*>(pEngineInput
)); // register as both
2230 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IInput
*>(pEngineInput
));
2232 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IEngineTextRender
*>(pEngineTextRender
)); // register as both
2233 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<ITextRender
*>(pEngineTextRender
));
2235 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IEngineMap
*>(pEngineMap
)); // register as both
2236 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IMap
*>(pEngineMap
));
2238 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IEngineMasterServer
*>(pEngineMasterServer
)); // register as both
2239 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(static_cast<IMasterServer
*>(pEngineMasterServer
));
2241 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(CreateEditor());
2242 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(CreateGameClient());
2243 RegisterFail
= RegisterFail
|| !pKernel
->RegisterInterface(pStorage
);
2251 pEngineMasterServer
->Init();
2252 pEngineMasterServer
->Load();
2254 // register all console commands
2255 pClient
->RegisterCommands();
2257 pKernel
->RequestInterface
<IGameClient
>()->OnConsoleInit();
2259 // init client's interfaces
2260 pClient
->InitInterfaces();
2262 // execute config file
2263 pConsole
->ExecuteFile("settings.cfg");
2265 // execute autoexec file
2266 pConsole
->ExecuteFile("autoexec.cfg");
2268 // parse the command line arguments
2269 if(argc
> 1) // ignore_convention
2270 pConsole
->ParseArguments(argc
-1, &argv
[1]); // ignore_convention
2272 // restore empty config strings to their defaults
2273 pConfig
->RestoreStrings();
2275 pClient
->Engine()->InitLogfile();
2278 dbg_msg("client", "starting...");
2281 // write down the config and quit