-
Notifications
You must be signed in to change notification settings - Fork 710
/
sv_client.cpp
2534 lines (2076 loc) · 72 KB
/
sv_client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
//===========================================================================//
#include "server_pch.h"
#include "framesnapshot.h"
#include "checksum_engine.h"
#include "sv_main.h"
#include "GameEventManager.h"
#include "networkstringtable.h"
#include "demo.h"
#include "PlayerState.h"
#include "tier0/vprof.h"
#include "sv_packedentities.h"
#include "LocalNetworkBackdoor.h"
#include "testscriptmgr.h"
#include "hltvserver.h"
#if defined( REPLAY_ENABLED )
#include "replayserver.h"
#endif
#include "pr_edict.h"
#include "logofile_shared.h"
#include "dt_send_eng.h"
#include "sv_plugin.h"
#include "download.h"
#include "cmodel_engine.h"
#include "tier1/commandbuffer.h"
#include "gl_cvars.h"
#include "tier2/tier2.h"
#include "matchmaking/imatchframework.h"
#include "audio/public/vox.h" // TERROR: for net_showreliablesounds
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "ihltv.h"
#include "tier1/utlstringtoken.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
extern CNetworkStringTableContainer *networkStringTableContainerServer;
static ConVar sv_timeout( "sv_timeout", "65", 0, "After this many seconds without a message from a client, the client is dropped" );
static ConVar sv_maxrate( "sv_maxrate", "0", FCVAR_REPLICATED | FCVAR_RELEASE, "Max bandwidth rate allowed on server, 0 == unlimited", true, 0, true, MAX_RATE );
static ConVar sv_minrate( "sv_minrate", STRINGIFY( MIN_RATE ), FCVAR_REPLICATED | FCVAR_RELEASE, "Min bandwidth rate allowed on server, 0 == unlimited", true, 0, true, MAX_RATE );
ConVar sv_maxupdaterate( "sv_maxupdaterate", "64", FCVAR_REPLICATED | FCVAR_RELEASE, "Maximum updates per second that the server will allow" ); // we need to be able to set max rate to 128
ConVar sv_minupdaterate( "sv_minupdaterate", "64", FCVAR_REPLICATED | FCVAR_RELEASE, "Minimum updates per second that the server will allow" );
ConVar sv_stressbots("sv_stressbots", "0", FCVAR_RELEASE, "If set to 1, the server calculates data and fills packets to bots. Used for perf testing.");
ConVar sv_replaybots( "sv_replaybots", "1", FCVAR_RELEASE, "If set to 1, the server records data needed to replay network stream from bot's perspective" );
static ConVar sv_allowdownload ("sv_allowdownload", "1", FCVAR_RELEASE, "Allow clients to download files");
static ConVar sv_allowupload ("sv_allowupload", "1", FCVAR_RELEASE, "Allow clients to upload customizations files");
ConVar sv_sendtables ( "sv_sendtables", "0", FCVAR_DEVELOPMENTONLY, "Force full sendtable sending path." );
#if HLTV_REPLAY_ENABLED
ConVar spec_replay_enable( "spec_replay_enable", "0", FCVAR_RELEASE|FCVAR_REPLICATED, "Enable Killer Replay, requires hltv server running." );
#endif
ConVar spec_replay_message_time( "spec_replay_message_time", "9.5", FCVAR_RELEASE | FCVAR_REPLICATED, "How long to show the message about Killer Replay after death. The best setting is a bit shorter than spec_replay_autostart_delay + spec_replay_leadup_time + spec_replay_winddown_time" );
ConVar spec_replay_rate_limit( "spec_replay_rate_limit", "3", FCVAR_RELEASE | FCVAR_REPLICATED, "Minimum allowable pause between replay requests in seconds" );
static ConVar ss_voice_hearpartner( "ss_voice_hearpartner", "0", 0, "Route voice between splitscreen players on same system." );
ConVar sv_max_dropped_packets_to_process( "sv_max_dropped_packets_to_process", "10", FCVAR_RELEASE, "Max dropped packets to process. Lower settings prevent lagged players from simulating too far in the past. Setting of 0 disables cap." );
ConVar cl_allowdownload( "cl_allowdownload", "1", FCVAR_ARCHIVE, "Client downloads customization files" );
static ConVar sv_quota_stringcmdspersecond( "sv_quota_stringcmdspersecond", "40", FCVAR_RELEASE, "How many string commands per second clients are allowed to submit, 0 to disallow all string commands" );
// TERROR:
static ConVar net_showreliablesounds( "net_showreliablesounds", "0", FCVAR_CHEAT );
ConVar replay_debug( "replay_debug", "0", FCVAR_RELEASE | FCVAR_REPLICATED);
ConVar sv_allow_legacy_cmd_execution_from_client( "sv_allow_legacy_cmd_execution_from_client", "0", FCVAR_RELEASE, "Enables old concommand execution behavior allowing remote clients to run any command not explicitly flagged as disallowed." );
extern ConVar sv_maxreplay;
extern ConVar tv_snapshotrate;
extern ConVar tv_transmitall;
extern ConVar sv_pure_kick_clients;
extern ConVar sv_pure_trace;
#if defined( REPLAY_ENABLED )
extern ConVar replay_snapshotrate;
extern ConVar replay_transmitall;
#endif
// static ConVar sv_failuretime( "sv_failuretime", "0.5", 0, "After this long without a packet from client, don't send any more until client starts sending again" );
static const char * s_clcommands[] =
{
"status",
"pause",
"setpause",
"unpause",
"ping",
"rpt_server_enable",
"rpt_client_enable",
#ifndef DEDICATED
"rpt",
"rpt_connect",
"rpt_password",
"rpt_screenshot",
"rpt_download_log",
#endif
"ss_connect",
"ss_disconnect",
#if defined( REPLAY_ENABLED )
"request_replay_demo",
#endif
NULL,
};
// Used on the server and on the client to bound its cl_rate cvar.
int ClampClientRate( int nRate )
{
// Apply mod specific clamps
if ( sv_maxrate.GetInt() > 0 )
{
nRate = MIN( nRate, sv_maxrate.GetInt() );
}
if ( sv_minrate.GetInt() > 0 )
{
nRate = MAX( nRate, sv_minrate.GetInt() );
}
// Apply overall clamp
nRate = clamp( nRate, MIN_RATE, MAX_RATE );
return nRate;
}
// Validate minimum number of required clients to be connected to a server
enum ValidateMinRequiredClients_t
{
VALIDATE_SPAWN,
VALIDATE_DISCONNECT
};
void SV_ValidateMinRequiredClients( ValidateMinRequiredClients_t eReason )
{
// FIXME: This gives false positives for drops and disconnects. (kwd)
return;
if ( !IsX360() && !sv.IsDedicatedForXbox() )
return;
static ConVarRef s_director_min_start_players( "director_min_start_players", true );
if ( !s_director_min_start_players.IsValid() )
return;
int numRequiredByDirector = s_director_min_start_players.GetInt();
if ( numRequiredByDirector <= 0 )
return;
switch ( eReason )
{
case VALIDATE_SPAWN:
{
// If at least one client has already spawned in the server, if there is a required
// minimum number of players and some of the players are not yet connected to server
// then most likely they dropped out or failed to connect, need to lower the minimum
// number of required players and proceed with game.
int numConnected = sv.GetClientCount();
int numInState = 0;
// Determine how many clients are above signon state NEW
for ( int j = 0 ; j < numConnected ; j++ )
{
IClient *client = sv.GetClient( j );
if ( !client )
continue;
if ( !client->IsSpawned() )
continue;
++ numInState;
}
if ( numRequiredByDirector > numInState )
{
s_director_min_start_players.SetValue( numInState );
ConMsg( "SV_ValidateMinRequiredClients: spawn: lowered min start players to %d.\n",
numInState );
}
}
break;
case VALIDATE_DISCONNECT:
{
// If somebody disconnects from the server we decrement the minimum number of players required
-- numRequiredByDirector;
s_director_min_start_players.SetValue( numRequiredByDirector );
ConMsg( "SV_ValidateMinRequiredClients: disconnect: lowered min start players to %d.\n",
numRequiredByDirector );
}
break;
}
}
CGameClient::CGameClient(int slot, CBaseServer *pServer )
{
Clear();
m_nClientSlot = slot;
m_nEntityIndex = slot+1;
m_Server = pServer;
m_pCurrentFrame = NULL;
m_bIsInReplayMode = false;
// NULL out data we'll never use.
memset( &m_PrevPackInfo, 0, sizeof( m_PrevPackInfo ) );
m_PrevPackInfo.m_pTransmitEdict = &m_PrevTransmitEdict;
m_flTimeClientBecameFullyConnected = -1.0f;
m_flLastClientCommandQuotaStart = -1.0f;
m_numClientCommandsInQuota = 0;
}
CGameClient::~CGameClient()
{
}
bool CGameClient::CLCMsg_ClientInfo( const CCLCMsg_ClientInfo& msg )
{
BaseClass::CLCMsg_ClientInfo( msg );
if ( m_bIsHLTV )
{
Disconnect( "CLCMsg_ClientInfo: SourceTV can not connect to game directly.\n" );
return false;
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
Disconnect( "CLCMsg_ClientInfo: Replay can not connect to game directly.\n" );
return false;
}
#endif
if ( sv_allowupload.GetBool() )
{
// download all missing customizations files from this client;
DownloadCustomizations();
}
return true;
}
bool CGameClient::CLCMsg_Move( const CCLCMsg_Move& msg )
{
// Don't process usercmds until the client is active. If we do, there can be weird behavior
// like the game trying to send reliable messages to the client and having those messages discarded.
if ( !IsActive() )
return true;
if ( m_LastMovementTick == sv.m_nTickCount )
{
// Only one movement command per frame, someone is cheating.
return true;
}
m_LastMovementTick = sv.m_nTickCount;
INetChannel *netchan = sv.GetBaseUserForSplitClient( this )->m_NetChannel;
int totalcmds = msg.num_backup_commands() + msg.num_new_commands();
// Decrement drop count by held back packet count
int netdrop = netchan->GetDropNumber();
// Dropped packet count is reported by clients
if ( sv_max_dropped_packets_to_process.GetInt() )
netdrop = Clamp( netdrop, 0, sv_max_dropped_packets_to_process.GetInt() );
bool ignore = !sv.IsActive();
#ifdef DEDICATED
bool paused = sv.IsPaused();
#else
bool paused = sv.IsPaused() || ( !sv.IsMultiplayer() && Con_IsVisible() );
#endif
// Make sure player knows of correct server time
g_ServerGlobalVariables.curtime = sv.GetTime();
g_ServerGlobalVariables.frametime = host_state.interval_per_tick;
// COM_Log( "sv.log", " executing %i move commands from client starting with command %i(%i)\n",
// numcmds,
// m_Client->netchan->incoming_sequence,
// m_Client->netchan->incoming_sequence & SV_UPDATE_MASK );
bf_read DataIn( &msg.data()[0], msg.data().size() );
serverGameClients->ProcessUsercmds
(
edict, // Player edict
&DataIn,
msg.num_new_commands(),
totalcmds, // Commands in packet
netdrop, // Number of dropped commands
ignore, // Don't actually run anything
paused // Run, but don't actually do any movement
);
if ( DataIn.IsOverflowed() )
{
Disconnect( "ProcessUsercmds: Overflowed reading usercmd data (check sending and receiving code for mismatches)!\n" );
return false;
}
return true;
}
bool CGameClient::CLCMsg_VoiceData( const CCLCMsg_VoiceData& msg )
{
serverGameClients->ClientVoice( edict );
SV_BroadcastVoiceData( this, msg );
return true;
}
bool CGameClient::CLCMsg_CmdKeyValues( const CCLCMsg_CmdKeyValues& msg )
{
KeyValues *keyvalues = CmdKeyValuesHelper::CLCMsg_GetKeyValues( msg );
KeyValues::AutoDelete autodelete_keyvalues( keyvalues );
serverGameClients->ClientCommandKeyValues( edict, keyvalues );
if ( IsX360() || NET_IsDedicatedForXbox() )
{
// See if a player was removed
if ( !Q_stricmp( keyvalues->GetName(), "OnPlayerRemovedFromSession" ) )
{
XUID xuid = keyvalues->GetUint64( "xuid", 0ull );
for ( int iPlayerIndex = 0; iPlayerIndex < sv.GetClientCount(); iPlayerIndex++ )
{
CBaseClient *pClient = (CBaseClient *) sv.GetClient( iPlayerIndex );
if ( !pClient || !xuid || pClient->GetClientXuid() != xuid )
continue;
pClient->Disconnect( "Player removed from host session\n" );
return true;
}
}
}
MEM_ALLOC_CREDIT();
KeyValues *pEvent = new KeyValues( "Server::CmdKeyValues" );
pEvent->AddSubKey( autodelete_keyvalues.Detach() );
pEvent->SetPtr( "edict", edict );
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( pEvent );
return true;
}
bool CGameClient::CLCMsg_HltvReplay( const CCLCMsg_HltvReplay &msg )
{
int nRequest = msg.request();
if ( nRequest == REPLAY_EVENT_STUCK_NEED_FULL_UPDATE )
{
if ( m_nForceWaitForTick > 0 )
{
// <sergiy> if we are indeed waiting for tick confirmation, the client may indeed be stuck. Let them have another update. This is prone to a mildly annoying attack: client can go into a loop requesting updates, which will raise server's CPU usage and traffic and may cause server to skip ticks on high-load casual servers. So later on, I should probably rate-limit this. But hopefully I'll find why the client gets stuck before too long.
UpdateAcknowledgedFramecount( -1 );
}
}
else if ( nRequest )
{
ClientReplayEventParams_t params( nRequest );
if ( params.m_flSlowdownRate > 0.01f && params.m_flSlowdownRate < 10.0f && params.m_flSlowdownLength > 0.01f && params.m_flSlowdownLength <= 5.0f )
{
// keep defaults in suspicious cases
params.m_flSlowdownRate = msg.slowdown_rate();
params.m_flSlowdownLength = msg.slowdown_length();
}
params.m_nPrimaryTargetEntIndex = msg.primary_target_ent_index();
params.m_flEventTime = msg.event_time();
serverGameClients->ClientReplayEvent( edict, params );
}
else
{
if ( IsHltvReplay() )
m_HltvReplayStats.nUserCancels++;
StopHltvReplay();
}
return true;
}
bool CGameClient::SVCMsg_UserMessage( const CSVCMsg_UserMessage &msg )
{
serverGameClients->ClientSvcUserMessage( edict, msg.msg_type(), msg.passthrough(), msg.msg_data().size(), &msg.msg_data()[0] );
return true;
}
bool CGameClient::CLCMsg_RespondCvarValue( const CCLCMsg_RespondCvarValue& msg )
{
if ( msg.cookie() > 0 )
{
if ( g_pServerPluginHandler )
g_pServerPluginHandler->OnQueryCvarValueFinished( ( EQueryCvarValueStatus )msg.cookie(), edict, ( EQueryCvarValueStatus )msg.status_code(), msg.name().c_str(), msg.value().c_str() );
}
else
{
// Negative cookie means the game DLL asked for the value.
if ( serverGameDLL && g_bServerGameDLLGreaterThanV5 )
{
#ifdef REL_TO_STAGING_MERGE_TODO
serverGameDLL->OnQueryCvarValueFinished( msg.cookie(), edict, msg.status_code(), msg.name().c_str(), msg.value().c_str() );
#endif
}
}
return true;
}
#include "pure_server.h"
bool CGameClient::CLCMsg_FileCRCCheck( const CCLCMsg_FileCRCCheck& msg )
{
// Ignore this message if we're not in pure server mode...
if ( !sv.IsInPureServerMode() )
return true;
char warningStr[1024] = {0};
// first check against all the other files users have sent
FileHash_t filehash;
V_memcpy( filehash.m_md5contents.bits, msg.md5().c_str(), MD5_DIGEST_LENGTH );
filehash.m_crcIOSequence = msg.crc();
filehash.m_eFileHashType = msg.file_hash_type();
filehash.m_cbFileLen = msg.file_len();
filehash.m_nPackFileNumber = msg.pack_file_number();
filehash.m_PackFileID = msg.pack_file_id();
const char *path = CCLCMsg_FileCRCCheck_t::GetPath( msg );
const char *fileName = CCLCMsg_FileCRCCheck_t::GetFileName( msg );
if ( g_PureFileTracker.DoesFileMatch( path, fileName, msg.file_fraction(), &filehash, GetNetworkID() ) )
{
// track successful file
}
else
{
V_snprintf( warningStr, sizeof( warningStr ), "Pure server: file [%s]\\%s does not match the server's file.", path, fileName );
}
// still ToDo:
// 1. make sure the user sends some files
// 2. make sure the user doesnt skip any files
// 3. make sure the user sends the right files...
if ( warningStr[0] )
{
if ( serverGameDLL )
{
serverGameDLL->OnPureServerFileValidationFailure( edict, path, fileName, filehash.m_crcIOSequence, filehash.m_eFileHashType,
filehash.m_cbFileLen, filehash.m_nPackFileNumber, filehash.m_PackFileID );
}
if ( sv_pure_kick_clients.GetInt() )
{
Disconnect( warningStr );
}
else
{
ClientPrintf( "Warning: %s\n", warningStr );
if ( sv_pure_trace.GetInt() >= 1 )
{
Msg( "[%s] %s\n", GetNetworkIDString(), warningStr );
}
}
}
else
{
if ( sv_pure_trace.GetInt() == 2 )
{
Msg( "Pure server CRC check: client %s passed check for [%s]\\%s\n", GetClientName(), path, fileName );
}
}
return true;
}
void CGameClient::DownloadCustomizations()
{
if ( !cl_allowdownload.GetBool() )
return; // client doesn't want to download any customizations
for ( int i=0; i<MAX_CUSTOM_FILES; i++ )
{
if ( m_nCustomFiles[i].crc == 0 )
continue; // slot not used
CCustomFilename hexname( m_nCustomFiles[i].crc );
if ( g_pFileSystem->FileExists( hexname.m_Filename ) )
continue; // we already have it
// we don't have it, request download from client
m_nCustomFiles[i].reqID = m_NetChannel->RequestFile( hexname.m_Filename, false );
}
}
void CGameClient::Connect(const char * szName, int nUserID, INetChannel *pNetChannel, bool bFakePlayer, CrossPlayPlatform_t clientPlatform, const CMsg_CVars *pVecCvars /*= NULL*/)
{
BaseClass::Connect( szName, nUserID, pNetChannel, bFakePlayer, clientPlatform, pVecCvars );
edict = EDICT_NUM( m_nEntityIndex );
// init PackInfo
m_PackInfo.m_pClientEnt = edict;
m_PackInfo.m_nPVSSize = sizeof( m_PackInfo.m_PVS );
// fire global game event
IGameEvent *event = g_GameEventManager.CreateEvent( "player_connect" );
{
event->SetInt( "userid", m_UserID );
event->SetInt( "index", m_nClientSlot );
event->SetString( "name", m_Name );
event->SetUint64( "xuid", GetClientXuid() );
event->SetString( "networkid", GetNetworkIDString() );
// event->SetString( "address", m_NetChannel?m_NetChannel->GetAddress():"none" );
event->SetInt( "bot", m_bFakePlayer?1:0 );
g_GameEventManager.FireEvent( event );
}
}
static ConVar sv_maxclientframes( "sv_maxclientframes", "128" );
static ConVar sv_extra_client_connect_time( "sv_extra_client_connect_time", "15.0", 0,
"Seconds after client connect during which extra frames are buffered to prevent non-delta'd update" );
void CGameClient::SetupPackInfo( CFrameSnapshot *pSnapshot )
{
Assert( !IsHltvReplay() );
// Compute Vis for each client
m_PackInfo.m_nPVSSize = (GetCollisionBSPData()->numclusters + 7) / 8;
serverGameClients->ClientSetupVisibility( (edict_t *)m_pViewEntity,
m_PackInfo.m_pClientEnt, m_PackInfo.m_PVS, m_PackInfo.m_nPVSSize );
// This is the frame we are creating, i.e., the next
// frame after the last one that the client acknowledged
m_pCurrentFrame = AllocateFrame();
m_pCurrentFrame->Init( pSnapshot );
m_PackInfo.m_pTransmitEdict = &m_pCurrentFrame->transmit_entity;
// if this client is the HLTV or Replay client, add the nocheck PVS bit array
// normal clients don't need that extra array
#if defined( REPLAY_ENABLED )
if ( IsHLTV() || IsReplay() )
#else
if ( IsHLTV() )
#endif
{
// the hltv client doesn't has a ClientFrame list
m_pCurrentFrame->transmit_always = new CBitVec<MAX_EDICTS>;
m_PackInfo.m_pTransmitAlways = m_pCurrentFrame->transmit_always;
}
else
{
m_PackInfo.m_pTransmitAlways = NULL;
}
// Add frame to ClientFrame list
int nMaxFrames = MAX( sv_maxclientframes.GetInt(), MAX_CLIENT_FRAMES );
// Only do this on dedicated servers (360 dedicated servers are !IsX360 so this check isn't strictly necessary)
// and non-x360 am servers due to concerns over memory growth on the consoles.
if ( ( !IsX360() || sv.IsDedicated() ) &&
( m_flTimeClientBecameFullyConnected != -1.0f ) &&
( realtime - m_flTimeClientBecameFullyConnected ) < sv_extra_client_connect_time.GetFloat() )
{
// For 15 seconds, the max will go from 128 (default) to 450 (assuming 0.0333 world tick interval)
// or to 960 (assuming 0.015625, 64 fps).
// In practice during changelevel on 360 I've seen it get up to 210 or so which is only 60% greater
// than the 128 frame max default, so 450 seems like it should capture all cases.
nMaxFrames = MAX( nMaxFrames, (int)( sv_extra_client_connect_time.GetFloat() / m_Server->GetTickInterval() ) );
// Msg( "Allowing up to %d frames for player for %f more seconds\n", nMaxFrames, realtime - m_flTimeClientBecameFullyConnected );
}
if ( sv_maxreplay.GetFloat() > 0 )
{
// if the server has replay features enabled, allow a way bigger frame buffer
nMaxFrames = MAX( nMaxFrames, sv_maxreplay.GetFloat() / m_Server->GetTickInterval() );
}
// During the startup period we retain additional frames. Once nMaxFrames drops we need to
// purge the extra frames or else we may permanently be using too much memory.
int frameCount = AddClientFrame( m_pCurrentFrame );
while ( nMaxFrames < frameCount )
{
// If the client has more than nMaxFrames frames, the server will start to eat too much memory.
RemoveOldestFrame();
--frameCount;
}
m_PackInfo.m_AreasNetworked = 0;
int areaCount = g_AreasNetworked.Count();
for ( int j = 0; j < areaCount; j++ )
{
// Msg("CGameClient::SetupPackInfo: too much areas (%i)", areaCount );
AssertOnce( m_PackInfo.m_AreasNetworked < MAX_WORLD_AREAS );
if ( m_PackInfo.m_AreasNetworked >= MAX_WORLD_AREAS )
break;
m_PackInfo.m_Areas[m_PackInfo.m_AreasNetworked] = g_AreasNetworked[ j ];
m_PackInfo.m_AreasNetworked++;
}
CM_SetupAreaFloodNums( m_PackInfo.m_AreaFloodNums, &m_PackInfo.m_nMapAreas );
}
ConVar spec_replay_rate_base( "spec_replay_rate_base", "1", FCVAR_RELEASE | FCVAR_REPLICATED, "Base time scale of Killer Replay.Experimental." );
void CGameClient::SetupHltvFrame( int nServerTick )
{
Assert( m_nHltvReplayDelay && m_pHltvReplayServer );
int nReplayTick = nServerTick - m_nHltvReplayDelay;
CClientFrame *pFrame = m_pHltvReplayServer->ExpandAndGetClientFrame( nReplayTick, false );
if ( !pFrame )
return;
m_pCurrentFrame = pFrame;
}
void CGameClient::SetupPrevPackInfo()
{
Assert( !IsHltvReplay() );
memcpy( &m_PrevTransmitEdict, m_PackInfo.m_pTransmitEdict, sizeof( m_PrevTransmitEdict ) );
// Copy the relevant fields into m_PrevPackInfo.
m_PrevPackInfo.m_AreasNetworked = m_PackInfo.m_AreasNetworked;
memcpy( m_PrevPackInfo.m_Areas, m_PackInfo.m_Areas, sizeof( m_PackInfo.m_Areas[0] ) * m_PackInfo.m_AreasNetworked );
m_PrevPackInfo.m_nPVSSize = m_PackInfo.m_nPVSSize;
memcpy( m_PrevPackInfo.m_PVS, m_PackInfo.m_PVS, m_PackInfo.m_nPVSSize );
m_PrevPackInfo.m_nMapAreas = m_PackInfo.m_nMapAreas;
memcpy( m_PrevPackInfo.m_AreaFloodNums, m_PackInfo.m_AreaFloodNums, m_PackInfo.m_nMapAreas * sizeof( m_PackInfo.m_nMapAreas ) );
}
/*
================
CheckRate
Make sure channel rate for active client is within server bounds
================
*/
void CGameClient::SetRate(int nRate, bool bForce )
{
if ( !bForce )
{
nRate = ClampClientRate( nRate );
}
BaseClass::SetRate( nRate, bForce );
}
void CGameClient::SetUpdateRate( float fUpdateRate, bool bForce )
{
if ( !bForce )
{
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
// Clients connected to our HLTV server will receive updates at tv_snapshotrate
fUpdateRate = hltv->GetSnapshotRate();
}
else
{
if ( sv_maxupdaterate.GetFloat() > 0 )
{
fUpdateRate = clamp( fUpdateRate, 1, sv_maxupdaterate.GetFloat() );
}
if ( sv_minupdaterate.GetInt() > 0 )
{
fUpdateRate = clamp( fUpdateRate, sv_minupdaterate.GetFloat(), 128.0f );
}
}
}
BaseClass::SetUpdateRate( fUpdateRate, bForce );
}
void CGameClient::UpdateUserSettings()
{
// set voice loopback
m_bVoiceLoopback = m_ConVars->GetInt( "voice_loopback", 0 ) != 0;
BaseClass::UpdateUserSettings();
// Give entity dll a chance to look at the changes.
// Do this after BaseClass::UpdateUserSettings() so name changes like prepending a (1)
// take effect before the server dll sees the name.
g_pServerPluginHandler->ClientSettingsChanged( edict );
}
//-----------------------------------------------------------------------------
// Purpose: A File has been received, if it's a logo, send it on to any other players who need it
// and return true, otherwise, return false
// Input : *cl -
// *filename -
// Output : Returns true on success, false on failure.
/*-----------------------------------------------------------------------------
bool CGameClient::ProcessIncomingLogo( const char *filename )
{
char crcfilename[ 512 ];
char logohex[ 16 ];
Q_binarytohex( (byte *)&logo, sizeof( logo ), logohex, sizeof( logohex ) );
Q_snprintf( crcfilename, sizeof( crcfilename ), "materials/decals/downloads/%s.vtf", logohex );
// It's not a logo file?
if ( Q_strcasecmp( filename, crcfilename ) )
{
return false;
}
// First, make sure crc is valid
CRC32_t check;
CRC_File( &check, crcfilename );
if ( check != logo )
{
ConMsg( "Incoming logo file didn't match player's logo CRC, ignoring\n" );
// Still note that it was a logo!
return true;
}
// Okay, looks good, see if any other players need this logo file
SV_SendLogo( check );
return true;
} */
bool CGameClient::IsHearingClient( int index ) const
{
#if defined( REPLAY_ENABLED )
if ( IsHLTV() || IsReplay() )
#else
if ( IsHLTV() )
#endif
return true;
if ( index == GetPlayerSlot() )
return m_bVoiceLoopback;
CGameClient *pClient = sv.Client( index );
// Don't send voice from one splitscreen partner to another on the same box
if ( !ss_voice_hearpartner.GetBool() &&
IsSplitScreenPartner( pClient ) )
{
return false;
}
return pClient->m_VoiceStreams.Get( GetPlayerSlot() ) != 0;
}
bool CGameClient::IsProximityHearingClient( int index ) const
{
CGameClient *pClient = sv.Client( index );
return pClient->m_VoiceProximity.Get( GetPlayerSlot() ) != 0;
}
void CGameClient::Inactivate( void )
{
if ( edict && !edict->IsFree() )
{
m_Server->RemoveClientFromGame( this );
}
if ( IsHLTV() )
{
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
hltv->Changelevel( true );
}
}
m_nHltvReplayDelay = 0;
m_pHltvReplayServer = NULL;
m_nHltvReplayStopAt = 0;
m_nHltvReplayStartAt = 0;
m_nHltvLastSendTick = 0; // last send tick, don't send ticks twice
#if defined( REPLAY_ENABLED )
if ( IsReplay() )
{
replay->Changelevel();
}
#endif
BaseClass::Inactivate();
m_Sounds.Purge();
ConVarRef voice_verbose( "voice_verbose" );
if ( voice_verbose.GetBool() )
{
Msg( "* CGameClient::Inactivate: Clearing m_VoiceStreams/m_VoiceProximity for %s (%s)\n", GetClientName(), GetNetChannel() ? GetNetChannel()->GetAddress() : "null" );
}
m_VoiceStreams.ClearAll();
m_VoiceProximity.ClearAll();
DeleteClientFrames( -1 ); // delete all
}
bool CGameClient::UpdateAcknowledgedFramecount(int tick)
{
// free old client frames which won't be used anymore
if ( tick != m_nDeltaTick )
{
// delta tick changed, free all frames smaller than tick
int removeTick = tick;
if ( sv_maxreplay.GetFloat() > 0 )
removeTick -= (sv_maxreplay.GetFloat() / m_Server->GetTickInterval() ); // keep a replay buffer
if ( removeTick > 0 )
{
DeleteClientFrames( removeTick );
}
}
return BaseClass::UpdateAcknowledgedFramecount( tick );
}
void CGameClient::Clear()
{
if ( m_bIsHLTV )
{
if ( CHLTVServer *hltv = GetAnyConnectedHltvServer() )
{
hltv->Shutdown();
}
}
#if defined( REPLAY_ENABLED )
if ( m_bIsReplay )
{
replay->Shutdown();
}
#endif
BaseClass::Clear();
m_HltvQueuedMessages.PurgeAndDeleteElements();
// free all frames
DeleteClientFrames( -1 );
m_Sounds.Purge();
ConVarRef voice_verbose( "voice_verbose" );
if ( voice_verbose.GetBool() )
{
Msg( "* CGameClient::Clear: Clearing m_VoiceStreams/m_VoiceProximity for %s (%s)\n", GetClientName(), GetNetChannel() ? GetNetChannel()->GetAddress() : "null" );
}
m_VoiceStreams.ClearAll();
m_VoiceProximity.ClearAll();
edict = NULL;
m_pViewEntity = NULL;
m_bVoiceLoopback = false;
m_LastMovementTick = 0;
m_nSoundSequence = 0;
m_flTimeClientBecameFullyConnected = -1.0f;
m_flLastClientCommandQuotaStart = -1.0f;
m_numClientCommandsInQuota = 0;
m_nHltvReplayDelay = 0;
m_pHltvReplayServer = NULL;
m_nHltvReplayStopAt = 0;
m_nHltvReplayStartAt = 0;
m_nHltvLastSendTick = 0;
m_flHltvLastReplayRequestTime = -spec_replay_message_time.GetFloat();
}
void CGameClient::Reconnect( void )
{
// If the client was connected before, tell the game .dll to disconnect him/her.
sv.RemoveClientFromGame( this );
BaseClass::Reconnect();
}
void CGameClient::PerformDisconnection( const char *pReason )
{
// notify other clients of player leaving the game
// send the username and network id so we don't depend on the CBasePlayer pointer
IGameEvent *event = g_GameEventManager.CreateEvent( "player_disconnect" );
if ( event )
{
event->SetInt("userid", GetUserID() );
event->SetString("reason", pReason );
event->SetString("name", GetClientName() );
event->SetUint64("xuid", GetClientXuid() );
event->SetString("networkid", GetNetworkIDString() );
g_GameEventManager.FireEvent( event );
}
m_Server->RemoveClientFromGame( this );
int nDisconnectSignonState = GetSignonState();
BaseClass::PerformDisconnection( pReason );
if ( nDisconnectSignonState >= SIGNONSTATE_NEW )
{
SV_ValidateMinRequiredClients( VALIDATE_DISCONNECT );
}
m_nHltvReplayDelay = 0;
m_pHltvReplayServer = NULL;
m_nHltvReplayStopAt = 0;
m_nHltvReplayStartAt = 0;
m_nHltvLastSendTick = 0;
}
HltvReplayStats_t m_DisconnectedClientsHltvReplayStats;
void CGameClient::Disconnect( const char *fmt )
{
// Remember what state we had when "Disconnect" got called
int nDisconnectSignonState = GetSignonState();
if ( nDisconnectSignonState == SIGNONSTATE_NONE )
return; // no recursion
m_DisconnectedClientsHltvReplayStats += m_HltvReplayStats;
m_HltvReplayStats.Reset();
BaseClass::Disconnect( fmt );
}
bool CGameClient::ProcessSignonStateMsg( int state, int spawncount )
{
if ( state == SIGNONSTATE_SPAWN || state == SIGNONSTATE_CHANGELEVEL )
{
StopHltvReplay();
}
else if ( state == SIGNONSTATE_CONNECTED )
{
if ( !CheckConnect() )
return false;
// Allow long enough time-out to load a map
float flTimeout = SIGNON_TIME_OUT;
if ( sv.IsDedicatedForXbox() )
flTimeout = SIGNON_TIME_OUT_360;
m_NetChannel->SetTimeout( flTimeout );
m_NetChannel->SetFileTransmissionMode( false );
m_NetChannel->SetMaxBufferSize( true, NET_MAX_PAYLOAD );
}
else if ( state == SIGNONSTATE_NEW )
{
if ( !sv.IsMultiplayer() )
{
// local client as received and create string tables,
// now link server tables to client tables
SV_InstallClientStringTableMirrors();
}
}
else if ( state == SIGNONSTATE_FULL )
{
if ( sv.m_bLoadgame )
{
// If this game was loaded from savegame, finish restoring game now
sv.FinishRestore();
}
m_NetChannel->SetTimeout( sv_timeout.GetFloat() ); // use smaller timeout limit
m_NetChannel->SetFileTransmissionMode( true );
g_pServerPluginHandler->ClientFullyConnect( edict );
}
return BaseClass::ProcessSignonStateMsg( state, spawncount );
}
void CGameClient::SendSound( SoundInfo_t &sound, bool isReliable )
{
#if defined( REPLAY_ENABLED )
if ( IsFakeClient() && !IsHLTV() && !IsReplay() && !IsSplitScreenUser() )
#else
if ( IsFakeClient() && !IsHLTV() && !IsSplitScreenUser() )
#endif
{
return; // dont send sound messages to bots
}
// don't send sound messages while client is replay mode
if ( m_bIsInReplayMode )