fixed that client resets tuning. Closes #746
[twcon.git] / src / game / client / gameclient.cpp
blob7b6b1192db7c14888e1d9567dfa0062ecf7a4cd4
1 /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2 /* If you are missing that file, acquire a complete release at teeworlds.com. */
3 #include <engine/editor.h>
4 #include <engine/engine.h>
5 #include <engine/friends.h>
6 #include <engine/graphics.h>
7 #include <engine/textrender.h>
8 #include <engine/demo.h>
9 #include <engine/map.h>
10 #include <engine/storage.h>
11 #include <engine/sound.h>
12 #include <engine/serverbrowser.h>
13 #include <engine/shared/demo.h>
14 #include <engine/shared/config.h>
16 #include <game/generated/protocol.h>
17 #include <game/generated/client_data.h>
19 #include <game/localization.h>
20 #include <game/version.h>
21 #include "render.h"
23 #include "gameclient.h"
25 #include "components/binds.h"
26 #include "components/broadcast.h"
27 #include "components/camera.h"
28 #include "components/chat.h"
29 #include "components/console.h"
30 #include "components/controls.h"
31 #include "components/countryflags.h"
32 #include "components/damageind.h"
33 #include "components/debughud.h"
34 #include "components/effects.h"
35 #include "components/emoticon.h"
36 #include "components/flow.h"
37 #include "components/hud.h"
38 #include "components/items.h"
39 #include "components/killmessages.h"
40 #include "components/mapimages.h"
41 #include "components/maplayers.h"
42 #include "components/menus.h"
43 #include "components/motd.h"
44 #include "components/particles.h"
45 #include "components/players.h"
46 #include "components/nameplates.h"
47 #include "components/scoreboard.h"
48 #include "components/skins.h"
49 #include "components/sounds.h"
50 #include "components/spectator.h"
51 #include "components/voting.h"
53 CGameClient g_GameClient;
55 // instanciate all systems
56 static CKillMessages gs_KillMessages;
57 static CCamera gs_Camera;
58 static CChat gs_Chat;
59 static CMotd gs_Motd;
60 static CBroadcast gs_Broadcast;
61 static CGameConsole gs_GameConsole;
62 static CBinds gs_Binds;
63 static CParticles gs_Particles;
64 static CMenus gs_Menus;
65 static CSkins gs_Skins;
66 static CCountryFlags gs_CountryFlags;
67 static CFlow gs_Flow;
68 static CHud gs_Hud;
69 static CDebugHud gs_DebugHud;
70 static CControls gs_Controls;
71 static CEffects gs_Effects;
72 static CScoreboard gs_Scoreboard;
73 static CSounds gs_Sounds;
74 static CEmoticon gs_Emoticon;
75 static CDamageInd gsDamageInd;
76 static CVoting gs_Voting;
77 static CSpectator gs_Spectator;
79 static CPlayers gs_Players;
80 static CNamePlates gs_NamePlates;
81 static CItems gs_Items;
82 static CMapImages gs_MapImages;
84 static CMapLayers gs_MapLayersBackGround(CMapLayers::TYPE_BACKGROUND);
85 static CMapLayers gs_MapLayersForeGround(CMapLayers::TYPE_FOREGROUND);
87 CGameClient::CStack::CStack() { m_Num = 0; }
88 void CGameClient::CStack::Add(class CComponent *pComponent) { m_paComponents[m_Num++] = pComponent; }
90 const char *CGameClient::Version() { return GAME_VERSION; }
91 const char *CGameClient::NetVersion() { return GAME_NETVERSION; }
92 const char *CGameClient::GetItemName(int Type) { return m_NetObjHandler.GetObjName(Type); }
94 void CGameClient::OnConsoleInit()
96 m_pEngine = Kernel()->RequestInterface<IEngine>();
97 m_pClient = Kernel()->RequestInterface<IClient>();
98 m_pGraphics = Kernel()->RequestInterface<IGraphics>();
99 m_pTextRender = Kernel()->RequestInterface<ITextRender>();
100 m_pSound = Kernel()->RequestInterface<ISound>();
101 m_pInput = Kernel()->RequestInterface<IInput>();
102 m_pConsole = Kernel()->RequestInterface<IConsole>();
103 m_pStorage = Kernel()->RequestInterface<IStorage>();
104 m_pDemoPlayer = Kernel()->RequestInterface<IDemoPlayer>();
105 m_pDemoRecorder = Kernel()->RequestInterface<IDemoRecorder>();
106 m_pServerBrowser = Kernel()->RequestInterface<IServerBrowser>();
107 m_pEditor = Kernel()->RequestInterface<IEditor>();
108 m_pFriends = Kernel()->RequestInterface<IFriends>();
110 // setup pointers
111 m_pBinds = &::gs_Binds;
112 m_pGameConsole = &::gs_GameConsole;
113 m_pParticles = &::gs_Particles;
114 m_pMenus = &::gs_Menus;
115 m_pSkins = &::gs_Skins;
116 m_pCountryFlags = &::gs_CountryFlags;
117 m_pChat = &::gs_Chat;
118 m_pFlow = &::gs_Flow;
119 m_pCamera = &::gs_Camera;
120 m_pControls = &::gs_Controls;
121 m_pEffects = &::gs_Effects;
122 m_pSounds = &::gs_Sounds;
123 m_pMotd = &::gs_Motd;
124 m_pDamageind = &::gsDamageInd;
125 m_pMapimages = &::gs_MapImages;
126 m_pVoting = &::gs_Voting;
127 m_pScoreboard = &::gs_Scoreboard;
128 m_pItems = &::gs_Items;
130 // make a list of all the systems, make sure to add them in the corrent render order
131 m_All.Add(m_pSkins);
132 m_All.Add(m_pCountryFlags);
133 m_All.Add(m_pMapimages);
134 m_All.Add(m_pEffects); // doesn't render anything, just updates effects
135 m_All.Add(m_pParticles);
136 m_All.Add(m_pBinds);
137 m_All.Add(m_pControls);
138 m_All.Add(m_pCamera);
139 m_All.Add(m_pSounds);
140 m_All.Add(m_pVoting);
141 m_All.Add(m_pParticles); // doesn't render anything, just updates all the particles
143 m_All.Add(&gs_MapLayersBackGround); // first to render
144 m_All.Add(&m_pParticles->m_RenderTrail);
145 m_All.Add(m_pItems);
146 m_All.Add(&gs_Players);
147 m_All.Add(&gs_MapLayersForeGround);
148 m_All.Add(&m_pParticles->m_RenderExplosions);
149 m_All.Add(&gs_NamePlates);
150 m_All.Add(&m_pParticles->m_RenderGeneral);
151 m_All.Add(m_pDamageind);
152 m_All.Add(&gs_Hud);
153 m_All.Add(&gs_Spectator);
154 m_All.Add(&gs_Emoticon);
155 m_All.Add(&gs_KillMessages);
156 m_All.Add(m_pChat);
157 m_All.Add(&gs_Broadcast);
158 m_All.Add(&gs_DebugHud);
159 m_All.Add(&gs_Scoreboard);
160 m_All.Add(m_pMotd);
161 m_All.Add(m_pMenus);
162 m_All.Add(m_pGameConsole);
164 // build the input stack
165 m_Input.Add(&m_pMenus->m_Binder); // this will take over all input when we want to bind a key
166 m_Input.Add(&m_pBinds->m_SpecialBinds);
167 m_Input.Add(m_pGameConsole);
168 m_Input.Add(m_pChat); // chat has higher prio due to tha you can quit it by pressing esc
169 m_Input.Add(m_pMotd); // for pressing esc to remove it
170 m_Input.Add(m_pMenus);
171 m_Input.Add(&gs_Spectator);
172 m_Input.Add(&gs_Emoticon);
173 m_Input.Add(m_pControls);
174 m_Input.Add(m_pBinds);
176 // add the some console commands
177 Console()->Register("team", "i", CFGFLAG_CLIENT, ConTeam, this, "Switch team");
178 Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself");
180 // register server dummy commands for tab completion
181 Console()->Register("tune", "si", CFGFLAG_SERVER, 0, 0, "Tune variable to value");
182 Console()->Register("tune_reset", "", CFGFLAG_SERVER, 0, 0, "Reset tuning");
183 Console()->Register("tune_dump", "", CFGFLAG_SERVER, 0, 0, "Dump tuning");
184 Console()->Register("change_map", "?r", CFGFLAG_SERVER, 0, 0, "Change map");
185 Console()->Register("restart", "?i", CFGFLAG_SERVER, 0, 0, "Restart in x seconds");
186 Console()->Register("broadcast", "r", CFGFLAG_SERVER, 0, 0, "Broadcast message");
187 Console()->Register("say", "r", CFGFLAG_SERVER, 0, 0, "Say in chat");
188 Console()->Register("set_team", "ii?i", CFGFLAG_SERVER, 0, 0, "Set team of player to team");
189 Console()->Register("set_team_all", "i", CFGFLAG_SERVER, 0, 0, "Set team of all players to team");
190 Console()->Register("add_vote", "sr", CFGFLAG_SERVER, 0, 0, "Add a voting option");
191 Console()->Register("remove_vote", "s", CFGFLAG_SERVER, 0, 0, "remove a voting option");
192 Console()->Register("force_vote", "ss?r", CFGFLAG_SERVER, 0, 0, "Force a voting option");
193 Console()->Register("clear_votes", "", CFGFLAG_SERVER, 0, 0, "Clears the voting options");
194 Console()->Register("vote", "r", CFGFLAG_SERVER, 0, 0, "Force a vote to yes/no");
197 // propagate pointers
198 m_UI.SetGraphics(Graphics(), TextRender());
199 m_RenderTools.m_pGraphics = Graphics();
200 m_RenderTools.m_pUI = UI();
201 for(int i = 0; i < m_All.m_Num; i++)
202 m_All.m_paComponents[i]->m_pClient = this;
204 // let all the other components register their console commands
205 for(int i = 0; i < m_All.m_Num; i++)
206 m_All.m_paComponents[i]->OnConsoleInit();
210 Console()->Chain("player_name", ConchainSpecialInfoupdate, this);
211 Console()->Chain("player_clan", ConchainSpecialInfoupdate, this);
212 Console()->Chain("player_country", ConchainSpecialInfoupdate, this);
213 Console()->Chain("player_use_custom_color", ConchainSpecialInfoupdate, this);
214 Console()->Chain("player_color_body", ConchainSpecialInfoupdate, this);
215 Console()->Chain("player_color_feet", ConchainSpecialInfoupdate, this);
216 Console()->Chain("player_skin", ConchainSpecialInfoupdate, this);
219 m_SuppressEvents = false;
222 void CGameClient::OnInit()
224 int64 Start = time_get();
226 // set the language
227 g_Localization.Load(g_Config.m_ClLanguagefile, Storage(), Console());
229 // TODO: this should be different
230 // setup item sizes
231 for(int i = 0; i < NUM_NETOBJTYPES; i++)
232 Client()->SnapSetStaticsize(i, m_NetObjHandler.GetObjSize(i));
234 // load default font
235 static CFont *pDefaultFont = 0;
236 char aFilename[512];
237 IOHANDLE File = Storage()->OpenFile("fonts/DejaVuSans.ttf", IOFLAG_READ, IStorage::TYPE_ALL, aFilename, sizeof(aFilename));
238 if(File)
240 io_close(File);
241 pDefaultFont = TextRender()->LoadFont(aFilename);
242 TextRender()->SetDefaultFont(pDefaultFont);
244 if(!pDefaultFont)
245 Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "gameclient", "failed to load font. filename='fonts/DejaVuSans.ttf'");
247 // init all components
248 for(int i = m_All.m_Num-1; i >= 0; --i)
249 m_All.m_paComponents[i]->OnInit();
251 // setup load amount// load textures
252 for(int i = 0; i < g_pData->m_NumImages; i++)
254 g_pData->m_aImages[i].m_Id = Graphics()->LoadTexture(g_pData->m_aImages[i].m_pFilename, IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
255 g_GameClient.m_pMenus->RenderLoading();
258 for(int i = 0; i < m_All.m_Num; i++)
259 m_All.m_paComponents[i]->OnReset();
261 int64 End = time_get();
262 char aBuf[256];
263 str_format(aBuf, sizeof(aBuf), "initialisation finished after %.2fms", ((End-Start)*1000)/(float)time_freq());
264 Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "gameclient", aBuf);
266 m_ServerMode = SERVERMODE_PURE;
269 void CGameClient::DispatchInput()
271 // handle mouse movement
272 float x = 0.0f, y = 0.0f;
273 Input()->MouseRelative(&x, &y);
274 if(x != 0.0f || y != 0.0f)
276 for(int h = 0; h < m_Input.m_Num; h++)
278 if(m_Input.m_paComponents[h]->OnMouseMove(x, y))
279 break;
283 // handle key presses
284 for(int i = 0; i < Input()->NumEvents(); i++)
286 IInput::CEvent e = Input()->GetEvent(i);
288 for(int h = 0; h < m_Input.m_Num; h++)
290 if(m_Input.m_paComponents[h]->OnInput(e))
292 //dbg_msg("", "%d char=%d key=%d flags=%d", h, e.ch, e.key, e.flags);
293 break;
298 // clear all events for this frame
299 Input()->ClearEvents();
303 int CGameClient::OnSnapInput(int *pData)
305 return m_pControls->SnapInput(pData);
308 void CGameClient::OnConnected()
310 m_Layers.Init(Kernel());
311 m_Collision.Init(Layers());
313 RenderTools()->RenderTilemapGenerateSkip(Layers());
315 for(int i = 0; i < m_All.m_Num; i++)
317 m_All.m_paComponents[i]->OnMapLoad();
318 m_All.m_paComponents[i]->OnReset();
321 CServerInfo CurrentServerInfo;
322 Client()->GetServerInfo(&CurrentServerInfo);
324 m_ServerMode = SERVERMODE_PURE;
325 m_LastSendInfo = 0;
327 // send the inital info
328 SendInfo(true);
331 void CGameClient::OnReset()
333 // clear out the invalid pointers
334 m_LastNewPredictedTick = -1;
335 mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap));
337 for(int i = 0; i < MAX_CLIENTS; i++)
338 m_aClients[i].Reset();
340 for(int i = 0; i < m_All.m_Num; i++)
341 m_All.m_paComponents[i]->OnReset();
343 m_DemoSpecID = SPEC_FREEVIEW;
344 m_FlagDropTick[TEAM_RED] = 0;
345 m_FlagDropTick[TEAM_BLUE] = 0;
346 m_Tuning = CTuningParams();
350 void CGameClient::UpdatePositions()
352 // local character position
353 if(g_Config.m_ClPredict && Client()->State() != IClient::STATE_DEMOPLAYBACK)
355 if(!m_Snap.m_pLocalCharacter || (m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER))
357 // don't use predicted
359 else
360 m_LocalCharacterPos = mix(m_PredictedPrevChar.m_Pos, m_PredictedChar.m_Pos, Client()->PredIntraGameTick());
362 else if(m_Snap.m_pLocalCharacter && m_Snap.m_pLocalPrevCharacter)
364 m_LocalCharacterPos = mix(
365 vec2(m_Snap.m_pLocalPrevCharacter->m_X, m_Snap.m_pLocalPrevCharacter->m_Y),
366 vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y), Client()->IntraGameTick());
369 // spectator position
370 if(m_Snap.m_SpecInfo.m_Active)
372 if(Client()->State() == IClient::STATE_DEMOPLAYBACK && DemoPlayer()->GetDemoType() == IDemoPlayer::DEMOTYPE_SERVER &&
373 m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW)
375 m_Snap.m_SpecInfo.m_Position = mix(
376 vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Prev.m_Y),
377 vec2(m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Cur.m_X, m_Snap.m_aCharacters[m_Snap.m_SpecInfo.m_SpectatorID].m_Cur.m_Y),
378 Client()->IntraGameTick());
379 m_Snap.m_SpecInfo.m_UsePosition = true;
381 else if(m_Snap.m_pSpectatorInfo && (Client()->State() == IClient::STATE_DEMOPLAYBACK || m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW))
383 if(m_Snap.m_pPrevSpectatorInfo)
384 m_Snap.m_SpecInfo.m_Position = mix(vec2(m_Snap.m_pPrevSpectatorInfo->m_X, m_Snap.m_pPrevSpectatorInfo->m_Y),
385 vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y), Client()->IntraGameTick());
386 else
387 m_Snap.m_SpecInfo.m_Position = vec2(m_Snap.m_pSpectatorInfo->m_X, m_Snap.m_pSpectatorInfo->m_Y);
388 m_Snap.m_SpecInfo.m_UsePosition = true;
394 static void Evolve(CNetObj_Character *pCharacter, int Tick)
396 CWorldCore TempWorld;
397 CCharacterCore TempCore;
398 mem_zero(&TempCore, sizeof(TempCore));
399 TempCore.Init(&TempWorld, g_GameClient.Collision());
400 TempCore.Read(pCharacter);
402 while(pCharacter->m_Tick < Tick)
404 pCharacter->m_Tick++;
405 TempCore.Tick(false);
406 TempCore.Move();
407 TempCore.Quantize();
410 TempCore.Write(pCharacter);
414 void CGameClient::OnRender()
416 /*Graphics()->Clear(1,0,0);
418 menus->render_background();
419 return;*/
421 Graphics()->Clear(1,0,0);
422 Graphics()->MapScreen(0,0,100,100);
424 Graphics()->QuadsBegin();
425 Graphics()->SetColor(1,1,1,1);
426 Graphics()->QuadsDraw(50, 50, 30, 30);
427 Graphics()->QuadsEnd();
429 return;*/
431 // update the local character and spectate position
432 UpdatePositions();
434 // dispatch all input to systems
435 DispatchInput();
437 // render all systems
438 for(int i = 0; i < m_All.m_Num; i++)
439 m_All.m_paComponents[i]->OnRender();
441 // clear new tick flags
442 m_NewTick = false;
443 m_NewPredictedTick = false;
445 // check if client info has to be resent
446 if(m_LastSendInfo && Client()->State() == IClient::STATE_ONLINE && !m_pMenus->IsActive() && m_LastSendInfo+time_freq()*5 < time_get())
448 // resend if client info differs
449 if(str_comp(g_Config.m_PlayerName, m_aClients[m_Snap.m_LocalClientID].m_aName) ||
450 str_comp(g_Config.m_PlayerClan, m_aClients[m_Snap.m_LocalClientID].m_aClan) ||
451 g_Config.m_PlayerCountry != m_aClients[m_Snap.m_LocalClientID].m_Country ||
452 str_comp(g_Config.m_PlayerSkin, m_aClients[m_Snap.m_LocalClientID].m_aSkinName) ||
453 (m_Snap.m_pGameInfoObj && !(m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS) && // no teamgame?
454 (g_Config.m_PlayerUseCustomColor != m_aClients[m_Snap.m_LocalClientID].m_UseCustomColor ||
455 g_Config.m_PlayerColorBody != m_aClients[m_Snap.m_LocalClientID].m_ColorBody ||
456 g_Config.m_PlayerColorFeet != m_aClients[m_Snap.m_LocalClientID].m_ColorFeet)))
458 SendInfo(false);
460 m_LastSendInfo = 0;
464 void CGameClient::OnRelease()
466 // release all systems
467 for(int i = 0; i < m_All.m_Num; i++)
468 m_All.m_paComponents[i]->OnRelease();
471 void CGameClient::OnMessage(int MsgId, CUnpacker *pUnpacker)
473 // special messages
474 if(MsgId == NETMSGTYPE_SV_EXTRAPROJECTILE)
476 int Num = pUnpacker->GetInt();
478 for(int k = 0; k < Num; k++)
480 CNetObj_Projectile Proj;
481 for(unsigned i = 0; i < sizeof(CNetObj_Projectile)/sizeof(int); i++)
482 ((int *)&Proj)[i] = pUnpacker->GetInt();
484 if(pUnpacker->Error())
485 return;
487 g_GameClient.m_pItems->AddExtraProjectile(&Proj);
490 return;
492 else if(MsgId == NETMSGTYPE_SV_TUNEPARAMS)
494 // unpack the new tuning
495 CTuningParams NewTuning;
496 int *pParams = (int *)&NewTuning;
497 for(unsigned i = 0; i < sizeof(CTuningParams)/sizeof(int); i++)
498 pParams[i] = pUnpacker->GetInt();
500 // check for unpacking errors
501 if(pUnpacker->Error())
502 return;
504 m_ServerMode = SERVERMODE_PURE;
506 // apply new tuning
507 m_Tuning = NewTuning;
508 return;
511 void *pRawMsg = m_NetObjHandler.SecureUnpackMsg(MsgId, pUnpacker);
512 if(!pRawMsg)
514 char aBuf[256];
515 str_format(aBuf, sizeof(aBuf), "dropped weird message '%s' (%d), failed on '%s'", m_NetObjHandler.GetMsgName(MsgId), MsgId, m_NetObjHandler.FailedMsgOn());
516 Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "client", aBuf);
517 return;
520 // TODO: this should be done smarter
521 for(int i = 0; i < m_All.m_Num; i++)
522 m_All.m_paComponents[i]->OnMessage(MsgId, pRawMsg);
524 if(MsgId == NETMSGTYPE_SV_READYTOENTER)
526 Client()->EnterGame();
528 else if (MsgId == NETMSGTYPE_SV_EMOTICON)
530 CNetMsg_Sv_Emoticon *pMsg = (CNetMsg_Sv_Emoticon *)pRawMsg;
532 // apply
533 m_aClients[pMsg->m_ClientID].m_Emoticon = pMsg->m_Emoticon;
534 m_aClients[pMsg->m_ClientID].m_EmoticonStart = Client()->GameTick();
536 else if(MsgId == NETMSGTYPE_SV_SOUNDGLOBAL)
538 if(m_SuppressEvents)
539 return;
541 // don't enqueue pseudo-global sounds from demos (created by PlayAndRecord)
542 CNetMsg_Sv_SoundGlobal *pMsg = (CNetMsg_Sv_SoundGlobal *)pRawMsg;
543 if(pMsg->m_SoundID == SOUND_CTF_DROP || pMsg->m_SoundID == SOUND_CTF_RETURN ||
544 pMsg->m_SoundID == SOUND_CTF_CAPTURE || pMsg->m_SoundID == SOUND_CTF_GRAB_EN ||
545 pMsg->m_SoundID == SOUND_CTF_GRAB_PL)
546 g_GameClient.m_pSounds->Enqueue(CSounds::CHN_GLOBAL, pMsg->m_SoundID);
547 else
548 g_GameClient.m_pSounds->Play(CSounds::CHN_GLOBAL, pMsg->m_SoundID, 1.0f, vec2(0,0));
552 void CGameClient::OnStateChange(int NewState, int OldState)
554 // reset everything when not already connected (to keep gathered stuff)
555 if(NewState < IClient::STATE_ONLINE)
556 OnReset();
558 // then change the state
559 for(int i = 0; i < m_All.m_Num; i++)
560 m_All.m_paComponents[i]->OnStateChange(NewState, OldState);
563 void CGameClient::OnShutdown() {}
564 void CGameClient::OnEnterGame() {}
566 void CGameClient::OnGameOver()
568 if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
569 Client()->AutoScreenshot_Start();
572 void CGameClient::OnStartGame()
574 if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
575 Client()->DemoRecorder_HandleAutoStart();
578 void CGameClient::OnRconLine(const char *pLine)
580 m_pGameConsole->PrintLine(CGameConsole::CONSOLETYPE_REMOTE, pLine);
583 void CGameClient::ProcessEvents()
585 if(m_SuppressEvents)
586 return;
588 int SnapType = IClient::SNAP_CURRENT;
589 int Num = Client()->SnapNumItems(SnapType);
590 for(int Index = 0; Index < Num; Index++)
592 IClient::CSnapItem Item;
593 const void *pData = Client()->SnapGetItem(SnapType, Index, &Item);
595 if(Item.m_Type == NETEVENTTYPE_DAMAGEIND)
597 CNetEvent_DamageInd *ev = (CNetEvent_DamageInd *)pData;
598 g_GameClient.m_pEffects->DamageIndicator(vec2(ev->m_X, ev->m_Y), GetDirection(ev->m_Angle));
600 else if(Item.m_Type == NETEVENTTYPE_EXPLOSION)
602 CNetEvent_Explosion *ev = (CNetEvent_Explosion *)pData;
603 g_GameClient.m_pEffects->Explosion(vec2(ev->m_X, ev->m_Y));
605 else if(Item.m_Type == NETEVENTTYPE_HAMMERHIT)
607 CNetEvent_HammerHit *ev = (CNetEvent_HammerHit *)pData;
608 g_GameClient.m_pEffects->HammerHit(vec2(ev->m_X, ev->m_Y));
610 else if(Item.m_Type == NETEVENTTYPE_SPAWN)
612 CNetEvent_Spawn *ev = (CNetEvent_Spawn *)pData;
613 g_GameClient.m_pEffects->PlayerSpawn(vec2(ev->m_X, ev->m_Y));
615 else if(Item.m_Type == NETEVENTTYPE_DEATH)
617 CNetEvent_Death *ev = (CNetEvent_Death *)pData;
618 g_GameClient.m_pEffects->PlayerDeath(vec2(ev->m_X, ev->m_Y), ev->m_ClientID);
620 else if(Item.m_Type == NETEVENTTYPE_SOUNDWORLD)
622 CNetEvent_SoundWorld *ev = (CNetEvent_SoundWorld *)pData;
623 g_GameClient.m_pSounds->Play(CSounds::CHN_WORLD, ev->m_SoundID, 1.0f, vec2(ev->m_X, ev->m_Y));
628 void CGameClient::OnNewSnapshot()
630 m_NewTick = true;
632 // clear out the invalid pointers
633 mem_zero(&g_GameClient.m_Snap, sizeof(g_GameClient.m_Snap));
634 m_Snap.m_LocalClientID = -1;
636 // secure snapshot
638 int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT);
639 for(int Index = 0; Index < Num; Index++)
641 IClient::CSnapItem Item;
642 void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, Index, &Item);
643 if(m_NetObjHandler.ValidateObj(Item.m_Type, pData, Item.m_DataSize) != 0)
645 if(g_Config.m_Debug)
647 char aBuf[256];
648 str_format(aBuf, sizeof(aBuf), "invalidated index=%d type=%d (%s) size=%d id=%d", Index, Item.m_Type, m_NetObjHandler.GetObjName(Item.m_Type), Item.m_DataSize, Item.m_ID);
649 Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf);
651 Client()->SnapInvalidateItem(IClient::SNAP_CURRENT, Index);
656 ProcessEvents();
658 if(g_Config.m_DbgStress)
660 if((Client()->GameTick()%100) == 0)
662 char aMessage[64];
663 int MsgLen = rand()%(sizeof(aMessage)-1);
664 for(int i = 0; i < MsgLen; i++)
665 aMessage[i] = 'a'+(rand()%('z'-'a'));
666 aMessage[MsgLen] = 0;
668 CNetMsg_Cl_Say Msg;
669 Msg.m_Team = rand()&1;
670 Msg.m_pMessage = aMessage;
671 Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
675 // go trough all the items in the snapshot and gather the info we want
677 m_Snap.m_aTeamSize[TEAM_RED] = m_Snap.m_aTeamSize[TEAM_BLUE] = 0;
679 int Num = Client()->SnapNumItems(IClient::SNAP_CURRENT);
680 for(int i = 0; i < Num; i++)
682 IClient::CSnapItem Item;
683 const void *pData = Client()->SnapGetItem(IClient::SNAP_CURRENT, i, &Item);
685 if(Item.m_Type == NETOBJTYPE_CLIENTINFO)
687 const CNetObj_ClientInfo *pInfo = (const CNetObj_ClientInfo *)pData;
688 int ClientID = Item.m_ID;
689 IntsToStr(&pInfo->m_Name0, 4, m_aClients[ClientID].m_aName);
690 IntsToStr(&pInfo->m_Clan0, 3, m_aClients[ClientID].m_aClan);
691 m_aClients[ClientID].m_Country = pInfo->m_Country;
692 IntsToStr(&pInfo->m_Skin0, 6, m_aClients[ClientID].m_aSkinName);
694 m_aClients[ClientID].m_UseCustomColor = pInfo->m_UseCustomColor;
695 m_aClients[ClientID].m_ColorBody = pInfo->m_ColorBody;
696 m_aClients[ClientID].m_ColorFeet = pInfo->m_ColorFeet;
698 // prepare the info
699 if(m_aClients[ClientID].m_aSkinName[0] == 'x' || m_aClients[ClientID].m_aSkinName[1] == '_')
700 str_copy(m_aClients[ClientID].m_aSkinName, "default", 64);
702 m_aClients[ClientID].m_SkinInfo.m_ColorBody = m_pSkins->GetColorV4(m_aClients[ClientID].m_ColorBody);
703 m_aClients[ClientID].m_SkinInfo.m_ColorFeet = m_pSkins->GetColorV4(m_aClients[ClientID].m_ColorFeet);
704 m_aClients[ClientID].m_SkinInfo.m_Size = 64;
706 // find new skin
707 m_aClients[ClientID].m_SkinID = g_GameClient.m_pSkins->Find(m_aClients[ClientID].m_aSkinName);
708 if(m_aClients[ClientID].m_SkinID < 0)
710 m_aClients[ClientID].m_SkinID = g_GameClient.m_pSkins->Find("default");
711 if(m_aClients[ClientID].m_SkinID < 0)
712 m_aClients[ClientID].m_SkinID = 0;
715 if(m_aClients[ClientID].m_UseCustomColor)
716 m_aClients[ClientID].m_SkinInfo.m_Texture = g_GameClient.m_pSkins->Get(m_aClients[ClientID].m_SkinID)->m_ColorTexture;
717 else
719 m_aClients[ClientID].m_SkinInfo.m_Texture = g_GameClient.m_pSkins->Get(m_aClients[ClientID].m_SkinID)->m_OrgTexture;
720 m_aClients[ClientID].m_SkinInfo.m_ColorBody = vec4(1,1,1,1);
721 m_aClients[ClientID].m_SkinInfo.m_ColorFeet = vec4(1,1,1,1);
724 m_aClients[ClientID].UpdateRenderInfo();
727 else if(Item.m_Type == NETOBJTYPE_PLAYERINFO)
729 const CNetObj_PlayerInfo *pInfo = (const CNetObj_PlayerInfo *)pData;
731 m_aClients[pInfo->m_ClientID].m_Team = pInfo->m_Team;
732 m_aClients[pInfo->m_ClientID].m_Active = true;
733 m_Snap.m_paPlayerInfos[pInfo->m_ClientID] = pInfo;
734 m_Snap.m_NumPlayers++;
736 if(pInfo->m_Local)
738 m_Snap.m_LocalClientID = Item.m_ID;
739 m_Snap.m_pLocalInfo = pInfo;
741 if(pInfo->m_Team == TEAM_SPECTATORS)
743 m_Snap.m_SpecInfo.m_Active = true;
744 m_Snap.m_SpecInfo.m_SpectatorID = SPEC_FREEVIEW;
748 // calculate team-balance
749 if(pInfo->m_Team != TEAM_SPECTATORS)
750 m_Snap.m_aTeamSize[pInfo->m_Team]++;
753 else if(Item.m_Type == NETOBJTYPE_CHARACTER)
755 const void *pOld = Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, Item.m_ID);
756 m_Snap.m_aCharacters[Item.m_ID].m_Cur = *((const CNetObj_Character *)pData);
757 if(pOld)
759 m_Snap.m_aCharacters[Item.m_ID].m_Active = true;
760 m_Snap.m_aCharacters[Item.m_ID].m_Prev = *((const CNetObj_Character *)pOld);
762 if(m_Snap.m_aCharacters[Item.m_ID].m_Prev.m_Tick)
763 Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Prev, Client()->PrevGameTick());
764 if(m_Snap.m_aCharacters[Item.m_ID].m_Cur.m_Tick)
765 Evolve(&m_Snap.m_aCharacters[Item.m_ID].m_Cur, Client()->GameTick());
768 else if(Item.m_Type == NETOBJTYPE_SPECTATORINFO)
770 m_Snap.m_pSpectatorInfo = (const CNetObj_SpectatorInfo *)pData;
771 m_Snap.m_pPrevSpectatorInfo = (const CNetObj_SpectatorInfo *)Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_SPECTATORINFO, Item.m_ID);
773 m_Snap.m_SpecInfo.m_SpectatorID = m_Snap.m_pSpectatorInfo->m_SpectatorID;
775 else if(Item.m_Type == NETOBJTYPE_GAMEINFO)
777 static bool s_GameOver = 0;
778 m_Snap.m_pGameInfoObj = (const CNetObj_GameInfo *)pData;
779 if(!s_GameOver && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)
780 OnGameOver();
781 else if(s_GameOver && !(m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER))
782 OnStartGame();
783 s_GameOver = m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER;
785 else if(Item.m_Type == NETOBJTYPE_GAMEDATA)
787 m_Snap.m_pGameDataObj = (const CNetObj_GameData *)pData;
788 m_Snap.m_GameDataSnapID = Item.m_ID;
789 if(m_Snap.m_pGameDataObj->m_FlagCarrierRed == FLAG_TAKEN)
791 if(m_FlagDropTick[TEAM_RED] == 0)
792 m_FlagDropTick[TEAM_RED] = Client()->GameTick();
794 else if(m_FlagDropTick[TEAM_RED] != 0)
795 m_FlagDropTick[TEAM_RED] = 0;
796 if(m_Snap.m_pGameDataObj->m_FlagCarrierBlue == FLAG_TAKEN)
798 if(m_FlagDropTick[TEAM_BLUE] == 0)
799 m_FlagDropTick[TEAM_BLUE] = Client()->GameTick();
801 else if(m_FlagDropTick[TEAM_BLUE] != 0)
802 m_FlagDropTick[TEAM_BLUE] = 0;
804 else if(Item.m_Type == NETOBJTYPE_FLAG)
805 m_Snap.m_paFlags[Item.m_ID%2] = (const CNetObj_Flag *)pData;
809 // setup local pointers
810 if(m_Snap.m_LocalClientID >= 0)
812 CSnapState::CCharacterInfo *c = &m_Snap.m_aCharacters[m_Snap.m_LocalClientID];
813 if(c->m_Active)
815 m_Snap.m_pLocalCharacter = &c->m_Cur;
816 m_Snap.m_pLocalPrevCharacter = &c->m_Prev;
817 m_LocalCharacterPos = vec2(m_Snap.m_pLocalCharacter->m_X, m_Snap.m_pLocalCharacter->m_Y);
819 else if(Client()->SnapFindItem(IClient::SNAP_PREV, NETOBJTYPE_CHARACTER, m_Snap.m_LocalClientID))
821 // player died
822 m_pControls->OnPlayerDeath();
825 else
827 m_Snap.m_SpecInfo.m_Active = true;
828 if(Client()->State() == IClient::STATE_DEMOPLAYBACK && DemoPlayer()->GetDemoType() == IDemoPlayer::DEMOTYPE_SERVER &&
829 m_DemoSpecID != SPEC_FREEVIEW && m_Snap.m_aCharacters[m_DemoSpecID].m_Active)
830 m_Snap.m_SpecInfo.m_SpectatorID = m_DemoSpecID;
831 else
832 m_Snap.m_SpecInfo.m_SpectatorID = SPEC_FREEVIEW;
835 // clear out unneeded client data
836 for(int i = 0; i < MAX_CLIENTS; ++i)
838 if(!m_Snap.m_paPlayerInfos[i] && m_aClients[i].m_Active)
839 m_aClients[i].Reset();
842 // update friend state
843 for(int i = 0; i < MAX_CLIENTS; ++i)
845 if(i == m_Snap.m_LocalClientID || !m_Snap.m_paPlayerInfos[i] || !Friends()->IsFriend(m_aClients[i].m_aName, m_aClients[i].m_aClan, true))
846 m_aClients[i].m_Friend = false;
847 else
848 m_aClients[i].m_Friend = true;
851 // sort player infos by score
852 mem_copy(m_Snap.m_paInfoByScore, m_Snap.m_paPlayerInfos, sizeof(m_Snap.m_paInfoByScore));
853 for(int k = 0; k < MAX_CLIENTS-1; k++) // ffs, bubblesort
855 for(int i = 0; i < MAX_CLIENTS-k-1; i++)
857 if(m_Snap.m_paInfoByScore[i+1] && (!m_Snap.m_paInfoByScore[i] || m_Snap.m_paInfoByScore[i]->m_Score < m_Snap.m_paInfoByScore[i+1]->m_Score))
859 const CNetObj_PlayerInfo *pTmp = m_Snap.m_paInfoByScore[i];
860 m_Snap.m_paInfoByScore[i] = m_Snap.m_paInfoByScore[i+1];
861 m_Snap.m_paInfoByScore[i+1] = pTmp;
866 CTuningParams StandardTuning;
867 CServerInfo CurrentServerInfo;
868 Client()->GetServerInfo(&CurrentServerInfo);
869 if(CurrentServerInfo.m_aGameType[0] != '0')
871 if(str_comp(CurrentServerInfo.m_aGameType, "DM") != 0 && str_comp(CurrentServerInfo.m_aGameType, "TDM") != 0 && str_comp(CurrentServerInfo.m_aGameType, "CTF") != 0)
872 m_ServerMode = SERVERMODE_MOD;
873 else if(mem_comp(&StandardTuning, &m_Tuning, sizeof(CTuningParams)) == 0)
874 m_ServerMode = SERVERMODE_PURE;
875 else
876 m_ServerMode = SERVERMODE_PUREMOD;
881 void CGameClient::OnPredict()
883 // store the previous values so we can detect prediction errors
884 CCharacterCore BeforePrevChar = m_PredictedPrevChar;
885 CCharacterCore BeforeChar = m_PredictedChar;
887 // we can't predict without our own id or own character
888 if(m_Snap.m_LocalClientID == -1 || !m_Snap.m_aCharacters[m_Snap.m_LocalClientID].m_Active)
889 return;
891 // don't predict anything if we are paused
892 if(m_Snap.m_pGameInfoObj && m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)
894 if(m_Snap.m_pLocalCharacter)
895 m_PredictedChar.Read(m_Snap.m_pLocalCharacter);
896 if(m_Snap.m_pLocalPrevCharacter)
897 m_PredictedPrevChar.Read(m_Snap.m_pLocalPrevCharacter);
898 return;
901 // repredict character
902 CWorldCore World;
903 World.m_Tuning = m_Tuning;
905 // search for players
906 for(int i = 0; i < MAX_CLIENTS; i++)
908 if(!m_Snap.m_aCharacters[i].m_Active)
909 continue;
911 g_GameClient.m_aClients[i].m_Predicted.Init(&World, Collision());
912 World.m_apCharacters[i] = &g_GameClient.m_aClients[i].m_Predicted;
913 g_GameClient.m_aClients[i].m_Predicted.Read(&m_Snap.m_aCharacters[i].m_Cur);
916 // predict
917 for(int Tick = Client()->GameTick()+1; Tick <= Client()->PredGameTick(); Tick++)
919 // fetch the local
920 if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID])
921 m_PredictedPrevChar = *World.m_apCharacters[m_Snap.m_LocalClientID];
923 // first calculate where everyone should move
924 for(int c = 0; c < MAX_CLIENTS; c++)
926 if(!World.m_apCharacters[c])
927 continue;
929 mem_zero(&World.m_apCharacters[c]->m_Input, sizeof(World.m_apCharacters[c]->m_Input));
930 if(m_Snap.m_LocalClientID == c)
932 // apply player input
933 int *pInput = Client()->GetInput(Tick);
934 if(pInput)
935 World.m_apCharacters[c]->m_Input = *((CNetObj_PlayerInput*)pInput);
936 World.m_apCharacters[c]->Tick(true);
938 else
939 World.m_apCharacters[c]->Tick(false);
943 // move all players and quantize their data
944 for(int c = 0; c < MAX_CLIENTS; c++)
946 if(!World.m_apCharacters[c])
947 continue;
949 World.m_apCharacters[c]->Move();
950 World.m_apCharacters[c]->Quantize();
953 // check if we want to trigger effects
954 if(Tick > m_LastNewPredictedTick)
956 m_LastNewPredictedTick = Tick;
957 m_NewPredictedTick = true;
959 if(m_Snap.m_LocalClientID != -1 && World.m_apCharacters[m_Snap.m_LocalClientID])
961 vec2 Pos = World.m_apCharacters[m_Snap.m_LocalClientID]->m_Pos;
962 int Events = World.m_apCharacters[m_Snap.m_LocalClientID]->m_TriggeredEvents;
963 if(Events&COREEVENT_GROUND_JUMP) g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, Pos);
965 /*if(events&COREEVENT_AIR_JUMP)
967 GameClient.effects->air_jump(pos);
968 GameClient.sounds->play_and_record(SOUNDS::CHN_WORLD, SOUND_PLAYER_AIRJUMP, 1.0f, pos);
971 //if(events&COREEVENT_HOOK_LAUNCH) snd_play_random(CHN_WORLD, SOUND_HOOK_LOOP, 1.0f, pos);
972 //if(events&COREEVENT_HOOK_ATTACH_PLAYER) snd_play_random(CHN_WORLD, SOUND_HOOK_ATTACH_PLAYER, 1.0f, pos);
973 if(Events&COREEVENT_HOOK_ATTACH_GROUND) g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_ATTACH_GROUND, 1.0f, Pos);
974 if(Events&COREEVENT_HOOK_HIT_NOHOOK) g_GameClient.m_pSounds->PlayAndRecord(CSounds::CHN_WORLD, SOUND_HOOK_NOATTACH, 1.0f, Pos);
975 //if(events&COREEVENT_HOOK_RETRACT) snd_play_random(CHN_WORLD, SOUND_PLAYER_JUMP, 1.0f, pos);
979 if(Tick == Client()->PredGameTick() && World.m_apCharacters[m_Snap.m_LocalClientID])
980 m_PredictedChar = *World.m_apCharacters[m_Snap.m_LocalClientID];
983 if(g_Config.m_Debug && g_Config.m_ClPredict && m_PredictedTick == Client()->PredGameTick())
985 CNetObj_CharacterCore Before = {0}, Now = {0}, BeforePrev = {0}, NowPrev = {0};
986 BeforeChar.Write(&Before);
987 BeforePrevChar.Write(&BeforePrev);
988 m_PredictedChar.Write(&Now);
989 m_PredictedPrevChar.Write(&NowPrev);
991 if(mem_comp(&Before, &Now, sizeof(CNetObj_CharacterCore)) != 0)
993 Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", "prediction error");
994 for(unsigned i = 0; i < sizeof(CNetObj_CharacterCore)/sizeof(int); i++)
995 if(((int *)&Before)[i] != ((int *)&Now)[i])
997 char aBuf[256];
998 str_format(aBuf, sizeof(aBuf), " %d %d %d (%d %d)", i, ((int *)&Before)[i], ((int *)&Now)[i], ((int *)&BeforePrev)[i], ((int *)&NowPrev)[i]);
999 Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client", aBuf);
1004 m_PredictedTick = Client()->PredGameTick();
1007 void CGameClient::OnActivateEditor()
1009 OnRelease();
1012 void CGameClient::CClientData::UpdateRenderInfo()
1014 m_RenderInfo = m_SkinInfo;
1016 // force team colors
1017 if(g_GameClient.m_Snap.m_pGameInfoObj && g_GameClient.m_Snap.m_pGameInfoObj->m_GameFlags&GAMEFLAG_TEAMS)
1019 m_RenderInfo.m_Texture = g_GameClient.m_pSkins->Get(m_SkinID)->m_ColorTexture;
1020 const int TeamColors[2] = {65387, 10223467};
1021 if(m_Team >= TEAM_RED && m_Team <= TEAM_BLUE)
1023 m_RenderInfo.m_ColorBody = g_GameClient.m_pSkins->GetColorV4(TeamColors[m_Team]);
1024 m_RenderInfo.m_ColorFeet = g_GameClient.m_pSkins->GetColorV4(TeamColors[m_Team]);
1026 else
1028 m_RenderInfo.m_ColorBody = g_GameClient.m_pSkins->GetColorV4(12895054);
1029 m_RenderInfo.m_ColorFeet = g_GameClient.m_pSkins->GetColorV4(12895054);
1034 void CGameClient::CClientData::Reset()
1036 m_aName[0] = 0;
1037 m_aClan[0] = 0;
1038 m_Country = -1;
1039 m_SkinID = 0;
1040 m_Team = 0;
1041 m_Angle = 0;
1042 m_Emoticon = 0;
1043 m_EmoticonStart = -1;
1044 m_Active = false;
1045 m_ChatIgnore = false;
1046 m_SkinInfo.m_Texture = g_GameClient.m_pSkins->Get(0)->m_ColorTexture;
1047 m_SkinInfo.m_ColorBody = vec4(1,1,1,1);
1048 m_SkinInfo.m_ColorFeet = vec4(1,1,1,1);
1049 UpdateRenderInfo();
1052 void CGameClient::SendSwitchTeam(int Team)
1054 CNetMsg_Cl_SetTeam Msg;
1055 Msg.m_Team = Team;
1056 Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
1059 void CGameClient::SendInfo(bool Start)
1061 if(Start)
1063 CNetMsg_Cl_StartInfo Msg;
1064 Msg.m_pName = g_Config.m_PlayerName;
1065 Msg.m_pClan = g_Config.m_PlayerClan;
1066 Msg.m_Country = g_Config.m_PlayerCountry;
1067 Msg.m_pSkin = g_Config.m_PlayerSkin;
1068 Msg.m_UseCustomColor = g_Config.m_PlayerUseCustomColor;
1069 Msg.m_ColorBody = g_Config.m_PlayerColorBody;
1070 Msg.m_ColorFeet = g_Config.m_PlayerColorFeet;
1071 Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
1073 else
1075 CNetMsg_Cl_ChangeInfo Msg;
1076 Msg.m_pName = g_Config.m_PlayerName;
1077 Msg.m_pClan = g_Config.m_PlayerClan;
1078 Msg.m_Country = g_Config.m_PlayerCountry;
1079 Msg.m_pSkin = g_Config.m_PlayerSkin;
1080 Msg.m_UseCustomColor = g_Config.m_PlayerUseCustomColor;
1081 Msg.m_ColorBody = g_Config.m_PlayerColorBody;
1082 Msg.m_ColorFeet = g_Config.m_PlayerColorFeet;
1083 Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
1085 // activate timer to resend the info if it gets filtered
1086 if(!m_LastSendInfo || m_LastSendInfo+time_freq()*5 < time_get())
1087 m_LastSendInfo = time_get();
1091 void CGameClient::SendKill(int ClientID)
1093 CNetMsg_Cl_Kill Msg;
1094 Client()->SendPackMsg(&Msg, MSGFLAG_VITAL);
1097 void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData)
1099 ((CGameClient*)pUserData)->SendSwitchTeam(pResult->GetInteger(0));
1102 void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData)
1104 ((CGameClient*)pUserData)->SendKill(-1);
1107 void CGameClient::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
1109 pfnCallback(pResult, pCallbackUserData);
1110 if(pResult->NumArguments())
1111 ((CGameClient*)pUserData)->SendInfo(false);
1114 IGameClient *CreateGameClient()
1116 return &g_GameClient;