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. */
5 #include <stdlib.h> // qsort
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>
42 #include "serverbrowser.h"
45 #if defined(CONF_FAMILY_WINDOWS)
46 #define _WIN32_WINNT 0x0501
47 #define WIN32_LEAN_AND_MEAN
52 void CGraph::Init(float Min
, float Max
)
59 void CGraph::ScaleMax()
63 for(i
= 0; i
< MAX_VALUES
; i
++)
65 if(m_aValues
[i
] > m_Max
)
70 void CGraph::ScaleMin()
74 for(i
= 0; i
< MAX_VALUES
; i
++)
76 if(m_aValues
[i
] < m_Min
)
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
);
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
)
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];
169 AdjustSpeed
= m_aAdjustSpeed
[1];
171 float a
= ((Now
-m_Snap
)/(float)time_freq()) * AdjustSpeed
;
175 int64 r
= c
+ (int64
)((t
-c
)*a
);
177 m_Graph
.Add(a
+0.5f
,1,1,1);
182 void CSmoothTime::UpdateInt(int64 Target
)
184 int64 Now
= time_get();
185 m_Current
= Get(Now
);
190 void CSmoothTime::Update(CGraph
*pGraph
, int64 Target
, int TimeLeft
, int AdjustDirection
)
202 if(m_SpikeCounter
> 50)
206 if(IsSpike
&& m_SpikeCounter
< 15)
208 // ignore this ping spike
210 pGraph
->Add(TimeLeft
, 1,1,0);
214 pGraph
->Add(TimeLeft
, 1,0,0);
215 if(m_aAdjustSpeed
[AdjustDirection
] < 30.0f
)
216 m_aAdjustSpeed
[AdjustDirection
] *= 2.0f
;
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
;
236 CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta
), m_DemoRecorder(&m_SnapshotDelta
)
246 m_FrameTime
= 0.0001f
;
247 m_FrameTimeLow
= 1.0f
;
248 m_FrameTimeHigh
= 0.0f
;
251 m_GameTickSpeed
= SERVER_TICK_SPEED
;
253 m_WindowMustRefocus
= 0;
255 m_AutoScreenshotRecycle
= false;
256 m_EditorActive
= false;
259 m_CurrentRecvTick
= 0;
263 m_aVersionStr
[0] = '0';
264 m_aVersionStr
[1] = 0;
270 m_aCurrentMap
[0] = 0;
274 m_aCmdConnect
[0] = 0;
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;
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
)
309 if(State() == IClient::STATE_OFFLINE
)
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)
322 *((unsigned char*)Packet
.m_pData
) <<= 1;
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
);
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
)
368 CMsgPacker
Msg(NETMSG_RCON_AUTH
);
369 Msg
.AddString(pName
, 32);
370 Msg
.AddString(pPassword
, 32);
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
)
390 CMsgPacker
Msg(NETMSG_INPUT
);
391 Msg
.AddInt(m_AckGameTick
);
392 Msg
.AddInt(m_PredTick
);
395 for(i
= 0; i
< Size
/4; i
++)
396 Msg
.AddInt(pInput
[i
]);
402 void CClient::SendInput()
404 int64 Now
= time_get();
410 int Size
= GameClient()->OnSnapInput(m_aInputs
[m_CurrentInput
].m_aData
);
416 CMsgPacker
Msg(NETMSG_INPUT
);
417 Msg
.AddInt(m_AckGameTick
);
418 Msg
.AddInt(m_PredTick
);
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
;
426 for(int i
= 0; i
< Size
/4; i
++)
427 Msg
.AddInt(m_aInputs
[m_CurrentInput
].m_aData
[i
]);
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
)
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
))
451 return (int *)m_aInputs
[Best
].m_aData
;
455 // ------ state handling -----
456 void CClient::SetState(int s
)
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
);
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()
475 for(i
= 0; i
< 200; i
++)
476 m_aInputs
[i
].m_Tick
= -1;
480 m_aSnapshots
[SNAP_CURRENT
] = 0;
481 m_aSnapshots
[SNAP_PREV
] = 0;
482 m_SnapshotStorage
.PurgeAll();
483 m_RecivedSnapshots
= 0;
486 m_CurrentRecvTick
= 0;
491 void CClient::EnterGame()
493 if(State() == IClient::STATE_DEMOPLAYBACK
)
496 // now we will wait for two snapshots
497 // to finish the connection
502 void CClient::Connect(const char *pAddress
)
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
);
516 if(net_host_lookup(m_aServerAddressStr
, &m_ServerAddress
, m_NetClient
.NetType()) != 0)
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());
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())
533 m_InputtimeMarginGraph
.Init(-150.0f
, 150.0f
);
534 m_GametimeMarginGraph
.Init(-150.0f
, 150.0f
);
537 void CClient::DisconnectWithReason(const char *pReason
)
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
549 m_pConsole
->DeregisterTempAll();
550 m_NetClient
.Disconnect(pReason
);
551 SetState(IClient::STATE_OFFLINE
);
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
));
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
);
598 void *CClient::SnapGetItem(int SnapID
, int Index
, CSnapItem
*pItem
)
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
)
612 dbg_assert(SnapID
>= 0 && SnapID
< NUM_SNAPSHOT_TYPES
, "invalid SnapID");
613 i
= m_aSnapshots
[SnapID
]->m_pAltSnap
->GetItem(Index
);
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");
624 void *CClient::SnapFindItem(int SnapID
, int Type
, int ID
)
626 // TODO: linear search. should be fixed.
629 if(!m_aSnapshots
[SnapID
])
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();
641 int CClient::SnapNumItems(int SnapID
)
643 dbg_assert(SnapID
>= 0 && SnapID
< NUM_SNAPSHOT_TYPES
, "invalid SnapID");
644 if(!m_aSnapshots
[SnapID
])
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();
663 if(!g_Config
.m_Debug
)
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();
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
);
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
);
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
);
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");
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();
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
);
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
);
786 // stop demo recording if we loaded a new map
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();
802 const char *CClient::LoadMapSearch(const char *pMapName
, int WantedCrc
)
804 const char *pError
= 0;
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
);
816 // try the downloaded maps
817 str_format(aBuf
, sizeof(aBuf
), "downloadedmaps/%s_%08x.map", pMapName
, WantedCrc
);
818 pError
= LoadMap(pMapName
, aBuf
, WantedCrc
);
822 // search for the map within subfolders
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
);
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
)
837 if(!p0
->m_Player
&& p1
->m_Player
)
839 if(p0
->m_Score
== p1
->m_Score
)
841 if(p0
->m_Score
< p1
->m_Score
)
846 void CClient::ProcessConnlessPacket(CNetChunk
*pPacket
)
849 if(m_VersionInfo
.m_State
== CVersionInfo::STATE_READY
&& net_addr_comp(&pPacket
->m_Address
, &m_VersionInfo
.m_VersionServeraddr
.m_Addr
) == 0)
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
));
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
868 str_format(m_aVersionStr
, sizeof(m_aVersionStr
), "%d.%d.%d", pVersionData
[1], pVersionData
[2], pVersionData
[3]);
871 // request the map version list now
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
);
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
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)
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
++)
920 static char IPV4Mapping
[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF };
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];
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);
944 if(pPacket
->m_DataSize
>= (int)sizeof(SERVERBROWSE_INFO
) && mem_comp(pPacket
->m_pData
, SERVERBROWSE_INFO
, sizeof(SERVERBROWSE_INFO
)) == 0)
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
)
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;
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;
990 m_ServerBrowser
.Set(pPacket
->m_Address
, IServerBrowser::SET_TOKEN
, Token
, &Info
);
995 void CClient::ProcessServerPacket(CNetChunk
*pPacket
)
998 Unpacker
.Reset(pPacket
->m_pData
, pPacket
->m_DataSize
);
1000 // unpack msgid and system flag
1001 int Msg
= Unpacker
.GetInt();
1005 if(Unpacker
.Error())
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())
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";
1032 pError
= "invalid map size";
1035 DisconnectWithReason(pError
);
1038 pError
= LoadMapSearch(pMap
, MapCrc
);
1042 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/network", "loading done");
1047 str_format(m_aMapdownloadFilename
, sizeof(m_aMapdownloadFilename
), "downloadedmaps/%s_%08x.map", pMap
, MapCrc
);
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
)
1086 io_write(m_MapdownloadFile
, pData
, Size
);
1088 m_MapdownloadAmount
+= Size
;
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;
1102 pError
= LoadMap(m_aMapdownloadName
, m_aMapdownloadFilename
, m_MapdownloadCrc
);
1105 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_ADDINFO
, "client/network", "loading done");
1109 DisconnectWithReason(pError
);
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
)
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
);
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
)
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
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());
1190 m_PredictedTime
.Update(&m_InputtimeMarginGraph
, Target
, TimeLeft
, 1);
1192 else if(Msg
== NETMSG_SNAP
|| Msg
== NETMSG_SNAPSINGLE
|| Msg
== NETMSG_SNAPEMPTY
)
1196 int GameTick
= Unpacker
.GetInt();
1197 int DeltaTick
= GameTick
-Unpacker
.GetInt();
1200 int CompleteSize
= 0;
1201 const char *pData
= 0;
1203 // we are not allowed to process snapshot yet
1204 if(State() < IClient::STATE_LOADING
)
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())
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
;
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
1248 CompleteSize
= (NumParts
-1) * MAX_SNAPSHOT_PACKSIZE
+ PartSize
;
1250 // reset snapshoting
1251 m_SnapshotParts
= 0;
1253 // find snapshot that we should use as delta
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
)
1268 str_format(aBuf
, sizeof(aBuf
), "error, couldn't find the delta snapshot");
1269 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", aBuf
);
1273 // TODO: combine this with the input message
1279 // decompress snapshot
1280 pDeltaData
= m_SnapshotDelta
.EmptyDelta();
1281 DeltaSize
= sizeof(int)*3;
1285 int IntSize
= CVariableInt::Decompress(m_aSnapshotIncommingData
, CompleteSize
, aTmpBuffer2
);
1287 if(IntSize
< 0) // failure during decompression, bail
1290 pDeltaData
= aTmpBuffer2
;
1291 DeltaSize
= IntSize
;
1295 PurgeTick
= DeltaTick
;
1296 SnapSize
= m_SnapshotDelta
.UnpackDelta(pDeltaShot
, pTmpBuffer3
, pDeltaData
, DeltaSize
);
1299 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "client", "delta unpack failed!");
1303 if(Msg
!= NETMSG_SNAPEMPTY
&& pTmpBuffer3
->Crc() != Crc
)
1305 if(g_Config
.m_Debug
)
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
);
1314 if(m_SnapCrcErrors
> 10)
1316 // to many errors, send reset
1319 m_SnapCrcErrors
= 0;
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
);
1338 m_SnapshotStorage
.Add(GameTick
, time_get(), SnapSize
, pTmpBuffer3
, 1);
1340 // add snapshot to demo
1341 if(m_DemoRecorder
.IsRecording())
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();
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);
1376 m_AckGameTick
= GameTick
;
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
)
1398 if(State() != IClient::STATE_OFFLINE
&& State() != IClient::STATE_QUITING
&& m_NetClient
.State() == NETSTATE_OFFLINE
)
1400 SetState(IClient::STATE_OFFLINE
);
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
);
1419 while(m_NetClient
.Recv(&Packet
))
1421 if(Packet
.m_ClientID
== -1)
1422 ProcessConnlessPacket(&Packet
);
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
;
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
)
1450 Unpacker
.Reset(pData
, Size
);
1452 // unpack msgid and system flag
1453 int Msg
= Unpacker
.GetInt();
1457 if(Unpacker
.Error())
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;
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)
1490 demorec_playback_pause();
1492 demorec_playback_unpause();
1495 void CClient::Update()
1497 if(State() == IClient::STATE_DEMOPLAYBACK
)
1499 m_DemoPlayer
.Update();
1500 if(m_DemoPlayer
.IsPlaying())
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
;
1511 // disconnect on error
1515 else if(State() == IClient::STATE_ONLINE
&& m_RecivedSnapshots
>= 3)
1519 int64 Freq
= time_freq();
1520 int64 Now
= m_GameTime
.Get(time_get());
1521 int64 PredNow
= m_PredictedTime
.Get(time_get());
1525 CSnapshotStorage::CHolder
*pCur
= m_aSnapshots
[SNAP_CURRENT
];
1526 int64 TickStart
= (pCur
->m_Tick
)*time_freq()/50;
1530 CSnapshotStorage::CHolder
*pNext
= m_aSnapshots
[SNAP_CURRENT
]->m_pNext
;
1533 m_aSnapshots
[SNAP_PREV
] = m_aSnapshots
[SNAP_CURRENT
];
1534 m_aSnapshots
[SNAP_CURRENT
] = pNext
;
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();
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
;
1583 // only do sane predictions
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
);
1616 if(Now
> ActionTaken
+time_freq()*(10+g_Config
.m_DbgStress
))
1618 m_pConsole
->Print(IConsole::OUTPUT_LEVEL_DEBUG
, "stress", "disconnecting!");
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
)
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()
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());
1693 int64 ReportTime
= time_get();
1694 int64 ReportInterval
= time_freq()*1;
1696 m_LocalStartTime
= time_get();
1697 m_SnapshotParts
= 0;
1700 if(m_pGraphics
->Init() != 0)
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");
1715 // init font rendering
1716 Kernel()->RequestInterface
<IEngineTextRender
>()->Init();
1721 // start refreshing addresses while we load
1722 MasterServer()->RefreshAddresses(m_NetClient
.NetType());
1727 // init sound, allowed to fail
1728 m_SoundInitFailed
= Sound()->Init() != 0;
1734 GameClient()->OnInit();
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);
1759 int64 FrameStartTime
= time_get();
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;
1774 if(Input()->Update())
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;
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'))
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())
1831 if(g_Config
.m_ClEditor
)
1835 GameClient()->OnActivateEditor();
1836 m_EditorActive
= true;
1840 m_pEditor
->UpdateAndRender();
1842 m_pGraphics
->Swap();
1847 m_EditorActive
= false;
1851 if(g_Config
.m_DbgStress
)
1853 if((m_Frames
%10) == 0)
1856 m_pGraphics
->Swap();
1862 m_pGraphics
->Swap();
1866 AutoScreenshot_Cleanup();
1869 if(State() == IClient::STATE_QUITING
)
1873 if(g_Config
.m_DbgStress
)
1875 else if(g_Config
.m_ClCpuThrottle
|| !m_pGraphics
->WindowActive())
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());
1895 m_FrameTimeHigh
= 0;
1897 ReportTime
+= ReportInterval
;
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();
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
;
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
;
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
;
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
)
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";
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
);
2031 DisconnectWithReason(pError
);
2035 GameClient()->OnConnected();
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();
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");
2074 char aFilename
[128];
2078 str_timestamp(aDate
, sizeof(aDate
));
2079 str_format(aFilename
, sizeof(aFilename
), "demos/%s_%s.demo", pFilename
, aDate
);
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);
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
;
2179 Client Predicted Time
2188 #if defined(CONF_PLATFORM_MACOSX)
2189 extern "C" int SDL_main(int argc
, const char **argv
) // ignore_convention
2191 int main(int argc
, const char **argv
) // ignore_convention
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
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
);
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();
2284 dbg_msg("client", "starting...");
2287 // write down the config and quit