forked from splewis/get5
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathget5.sp
2437 lines (2139 loc) · 104 KB
/
get5.sp
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
/**
* =============================================================================
* Get5
* Copyright (C) 2016. Sean Lewis. All rights reserved.
* =============================================================================
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "include/get5.inc"
#include "include/logdebug.inc"
#include "include/restorecvars.inc"
#include <cstrike>
#include <json> // github.com/clugg/sm-json
#include <regex>
#include <sdkhooks>
#include <sdktools>
#include <sourcemod>
#include <testing>
#undef REQUIRE_EXTENSIONS
#include <SteamWorks>
#pragma semicolon 1
#pragma newdecls required
/**
* Increases stack space to 131072 cells (or 512KB; a cell is 4 bytes).
* This is to prevent "Not enough space on the heap" error when dumping match stats while also
* allowing a 64KB static buffer for JSON encode output for HTTP events.
* Default heap size is 1024 cells (4KB).
*/
#pragma dynamic 131072
#include "get5/util.sp"
/** ConVar handles **/
ConVar g_AllowPauseCancellationCvar;
ConVar g_AllowTechPauseCvar;
ConVar g_MaxTechPauseDurationCvar;
ConVar g_MaxTechPausesCvar;
ConVar g_AutoTechPauseMissingPlayersCvar;
ConVar g_AutoLoadConfigCvar;
ConVar g_AutoReadyActivePlayersCvar;
ConVar g_BackupSystemEnabledCvar;
ConVar g_RemoteBackupURLCvar;
ConVar g_RemoteBackupURLHeaderValueCvar;
ConVar g_RemoteBackupURLHeaderKeyCvar;
ConVar g_CheckAuthsCvar;
ConVar g_DateFormatCvar;
ConVar g_DamagePrintCvar;
ConVar g_DamagePrintExcessCvar;
ConVar g_DamagePrintFormatCvar;
ConVar g_DemoNameFormatCvar;
ConVar g_DisplayGotvVetoCvar;
ConVar g_EventLogFormatCvar;
ConVar g_EventLogRemoteURLCvar;
ConVar g_EventLogRemoteHeaderKeyCvar;
ConVar g_EventLogRemoteHeaderValueCvar;
ConVar g_FixedPauseTimeCvar;
ConVar g_KickClientImmunityCvar;
ConVar g_KickClientsWithNoMatchCvar;
ConVar g_KickOnForceEndCvar;
ConVar g_LiveCfgCvar;
ConVar g_LiveWingmanCfgCvar;
ConVar g_TeamsFileCvar;
ConVar g_MapsFileCvar;
ConVar g_CvarsFileCvar;
ConVar g_MuteAllChatDuringMapSelectionCvar;
ConVar g_WarmupCfgCvar;
ConVar g_KnifeCfgCvar;
ConVar g_LiveCountdownTimeCvar;
ConVar g_MaxBackupAgeCvar;
ConVar g_MaxTacticalPausesCvar;
ConVar g_MaxPauseTimeCvar;
ConVar g_MessagePrefixCvar;
ConVar g_PauseOnVetoCvar;
ConVar g_AllowUnpausingFixedPausesCvar;
ConVar g_PausingEnabledCvar;
ConVar g_PrettyPrintJsonCvar;
ConVar g_ReadyTeamTagCvar;
ConVar g_AllowForceReadyCvar;
ConVar g_ResetPausesEachHalfCvar;
ConVar g_ServerIdCvar;
ConVar g_ResetCvarsOnEndCvar;
ConVar g_SetClientClanTagCvar;
ConVar g_SetHostnameCvar;
ConVar g_StatsPathFormatCvar;
ConVar g_StopCommandEnabledCvar;
ConVar g_StopCommandNoDamageCvar;
ConVar g_StopCommandTimeLimitCvar;
ConVar g_TeamTimeToKnifeDecisionCvar;
ConVar g_TimeToStartCvar;
ConVar g_TimeToStartVetoCvar;
ConVar g_TimeFormatCvar;
ConVar g_VetoCountdownCvar;
ConVar g_PrintUpdateNoticeCvar;
ConVar g_RoundBackupPathCvar;
ConVar g_PhaseAnnouncementCountCvar;
ConVar g_Team1NameColorCvar;
ConVar g_Team2NameColorCvar;
ConVar g_SpecNameColorCvar;
ConVar g_SurrenderEnabledCvar;
ConVar g_MinimumRoundDeficitForSurrenderCvar;
ConVar g_VotesRequiredForSurrenderCvar;
ConVar g_SurrenderVoteTimeLimitCvar;
ConVar g_SurrenderCooldownCvar;
ConVar g_ForfeitEnabledCvar;
ConVar g_ForfeitCountdownTimeCvar;
ConVar g_DemoUploadURLCvar;
ConVar g_DemoUploadHeaderKeyCvar;
ConVar g_DemoUploadHeaderValueCvar;
ConVar g_DemoUploadDeleteAfterCvar;
ConVar g_DemoPathCvar;
// Autoset convars (not meant for users to set)
ConVar g_GameStateCvar;
ConVar g_LastGet5BackupCvar;
ConVar g_VersionCvar;
// Hooked cvars built into csgo
ConVar g_CoachingEnabledCvar;
/** Series config game-state **/
int g_MapsToWin = 1; // Maps needed to win the series.
bool g_SeriesCanClinch = true;
bool g_Wingman = false;
bool g_MapReloadRequired = false; // Gets set to true on match-win, so matches are always reloaded if a previous
// game was played on the same map with no reload in between.
int g_RoundNumber = -1; // The round number, 0-indexed. -1 if the match is not live.
// The active map number, used by stats. Required as the calculated round number changes immediately
// as a map ends, but before the map changes to the next.
int g_MapNumber = 0; // the current map number, starting at 0.
int g_NumberOfMapsInSeries = 0; // the number of maps to play in the series.
char g_MatchID[MATCH_ID_LENGTH];
ArrayList g_MapPoolList;
ArrayList g_TeamPlayers[MATCHTEAM_COUNT];
ArrayList g_TeamCoaches[MATCHTEAM_COUNT];
StringMap g_PlayerNames;
StringMap g_ChatCommands;
char g_TeamIDs[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_TeamNames[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_TeamTags[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_FormattedTeamNames[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_TeamFlags[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_TeamLogos[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_TeamMatchTexts[MATCHTEAM_COUNT][MAX_CVAR_LENGTH];
char g_MatchTitle[MAX_CVAR_LENGTH];
int g_FavoredTeamPercentage = 0;
char g_FavoredTeamText[MAX_CVAR_LENGTH];
char g_HostnamePreGet5[MAX_CVAR_LENGTH];
int g_PlayersPerTeam = 5;
int g_CoachesPerTeam = 2;
int g_MinPlayersToReady = 1;
bool g_CoachesMustReady = false;
int g_MinSpectatorsToReady = 0;
float g_RoundStartedTime = 0.0;
float g_BombPlantedTime = 0.0;
Get5BombSite g_BombSiteLastPlanted = Get5BombSite_Unknown;
bool g_SkipVeto = false;
MatchSideType g_MatchSideType = MatchSideType_Standard;
ArrayList g_CvarNames;
ArrayList g_CvarValues;
bool g_InScrimMode = false;
/** Knife for sides **/
bool g_HasKnifeRoundStarted = false;
Get5Team g_KnifeWinnerTeam = Get5Team_None;
Handle g_KnifeChangedCvars = INVALID_HANDLE;
Handle g_KnifeDecisionTimer = INVALID_HANDLE;
Handle g_KnifeCountdownTimer = INVALID_HANDLE;
/** Pausing **/
bool g_IsChangingPauseState = false; // Used to prevent mp_pause_match and mp_unpause_match from being called directly.
Get5Team g_PausingTeam = Get5Team_None; // The team that last called for a pause.
Get5PauseType g_PauseType = Get5PauseType_None; // The type of pause last initiated.
Handle g_PauseTimer = INVALID_HANDLE;
int g_LatestPauseDuration = -1;
bool g_TeamReadyForUnpause[MATCHTEAM_COUNT];
bool g_TeamGivenStopCommand[MATCHTEAM_COUNT];
int g_TacticalPauseTimeUsed[MATCHTEAM_COUNT];
int g_TacticalPausesUsed[MATCHTEAM_COUNT];
int g_TechnicalPausesUsed[MATCHTEAM_COUNT];
/** Surrender/forfeit **/
int g_SurrenderVotes[MATCHTEAM_COUNT];
float g_SurrenderFailedAt[MATCHTEAM_COUNT];
bool g_SurrenderedPlayers[MAXPLAYERS + 1];
Handle g_SurrenderTimers[MATCHTEAM_COUNT];
Get5Team g_PendingSurrenderTeam = Get5Team_None;
Handle g_ForfeitTimer = INVALID_HANDLE;
int g_ForfeitSecondsPassed = 0;
Get5Team g_ForfeitingTeam = Get5Team_None;
/** Other state **/
Get5State g_GameState = Get5State_None;
ArrayList g_MapsToPlay;
ArrayList g_MapSides;
ArrayList g_MapsLeftInVetoPool;
ArrayList g_MapBanOrder;
Get5Team g_LastVetoTeam;
Handle g_InfoTimer = INVALID_HANDLE;
Handle g_MatchConfigExecTimer = INVALID_HANDLE;
Handle g_ResetCvarsTimer = INVALID_HANDLE;
/** Backup data **/
bool g_DoingBackupRestoreNow = false;
// Stats values
StringMap g_FlashbangContainer; // Stores flashbang-entity-id -> Get5FlashbangDetonatedEvent.
StringMap g_HEGrenadeContainer; // Stores he-entity-id -> Get5HEDetonatedEvent.
StringMap g_MolotovContainer; // Stores molotov-entity-id -> Get5MolotovDetonatedEvent.
// Molotov detonate and start-burning/extinguish are two separate events always fired right
// after each other. We need this to bind them together as detonate does not have client id.
int g_LatestUserIdToDetonateMolotov = 0;
int g_LatestMolotovToExtinguishBySmoke = 0; // Attributes extinguish booleans to smoke grenades.
bool g_FirstKillDone = false;
bool g_FirstDeathDone = false;
bool g_SetTeamClutching[4];
int g_RoundKills[MAXPLAYERS + 1]; // kills per round each client has gotten
int g_RoundClutchingEnemyCount[MAXPLAYERS + 1]; // number of enemies left alive when last alive on your team
int g_PlayerKilledBy[MAXPLAYERS + 1];
float g_PlayerKilledByTime[MAXPLAYERS + 1];
int g_DamageDone[MAXPLAYERS + 1][MAXPLAYERS + 1];
int g_DamageDoneHits[MAXPLAYERS + 1][MAXPLAYERS + 1];
bool g_DamageDoneKill[MAXPLAYERS + 1][MAXPLAYERS + 1];
bool g_DamageDoneAssist[MAXPLAYERS + 1][MAXPLAYERS + 1];
bool g_DamageDoneFlashAssist[MAXPLAYERS + 1][MAXPLAYERS + 1];
bool g_PlayerRoundKillOrAssistOrTradedDeath[MAXPLAYERS + 1];
bool g_PlayerSurvived[MAXPLAYERS + 1];
bool g_PlayerHasTakenDamage = false;
KeyValues g_StatsKv;
ArrayList g_TeamScoresPerMap = null;
char g_LoadedConfigFile[PLATFORM_MAX_PATH];
int g_VetoCaptains[MATCHTEAM_COUNT]; // Clients doing the map vetos.
int g_TeamSeriesScores[MATCHTEAM_COUNT]; // Current number of maps won per-team.
bool g_TeamReadyOverride[MATCHTEAM_COUNT]; // Whether a team has been voluntarily force readied.
bool g_ClientReady[MAXPLAYERS + 1]; // Whether clients are marked ready.
int g_TeamSide[MATCHTEAM_COUNT]; // Current CS_TEAM_* side for the team.
int g_TeamStartingSide[MATCHTEAM_COUNT];
int g_ReadyTimeWaitingUsed = 0;
char g_LastKickedPlayerAuth[64];
/** Chat aliases loaded **/
#define ALIAS_LENGTH 64
#define COMMAND_LENGTH 64
ArrayList g_ChatAliases;
ArrayList g_ChatAliasesCommands;
/** Map-game state not related to the actual gameplay. **/
char g_DemoFilePath[PLATFORM_MAX_PATH]; // full path to demo file being recorded to, including .dem extension
char g_DemoFileName[PLATFORM_MAX_PATH]; // the file name of the demo file, including .dem extension
bool g_MapChangePending = false;
bool g_PendingSideSwap = false;
Handle g_PendingMapChangeTimer = INVALID_HANDLE;
bool g_ClientPendingTeamCheck[MAXPLAYERS + 1];
/** Setup menu state **/
Menu g_ActiveSetupMenu = null;
bool g_SetupMenuWingman = false;
int g_SetupMenuPlayersPerTeam = 5;
bool g_SetupMenuFriendlyFire = true;
bool g_SetupMenuClinch = true;
bool g_SetupMenuOvertime = true;
Get5SetupMenu_MapSelectionMode g_SetupMenuMapSelection = Get5SetupMenu_MapSelectionMode_PickBan;
Get5SetupMenu_TeamSelectionMode g_SetupMenuTeamSelection = Get5SetupMenu_TeamSelectionMode_Current;
MatchSideType g_SetupMenuSideType = MatchSideType_Standard;
int g_SetupMenuSeriesLength = 1;
ArrayList g_SetupMenuSelectedMaps;
JSON_Object g_SetupMenuMapPool;
char g_SetupMenuSelectedMapPool[64];
int g_SetupMenuTeam1Captain = -1;
int g_SetupMenuTeam2Captain = -1;
char g_SetupMenuTeamForTeam1[64] = "";
char g_SetupMenuTeamForTeam2[64] = "";
JSON_Object g_SetupMenuAvailableTeams;
// int g_SetupMenuLeader = -1;
// version check state
bool g_RunningPrereleaseVersion = false;
bool g_NewerVersionAvailable = false;
Handle g_MatchConfigChangedCvars = INVALID_HANDLE;
/** Forwards **/
Handle g_OnBackupRestore = INVALID_HANDLE;
Handle g_OnBombDefused = INVALID_HANDLE;
Handle g_OnBombExploded = INVALID_HANDLE;
Handle g_OnBombPlanted = INVALID_HANDLE;
Handle g_OnDemoFinished = INVALID_HANDLE;
Handle g_OnDemoUploadEnded = INVALID_HANDLE;
Handle g_OnEvent = INVALID_HANDLE;
Handle g_OnFlashbangDetonated = INVALID_HANDLE;
Handle g_OnHEGrenadeDetonated = INVALID_HANDLE;
Handle g_OnSmokeGrenadeDetonated = INVALID_HANDLE;
Handle g_OnDecoyStarted = INVALID_HANDLE;
Handle g_OnMolotovDetonated = INVALID_HANDLE;
Handle g_OnGameStateChanged = INVALID_HANDLE;
Handle g_OnGoingLive = INVALID_HANDLE;
Handle g_OnGrenadeThrown = INVALID_HANDLE;
Handle g_OnLoadMatchConfigFailed = INVALID_HANDLE;
Handle g_OnMapPicked = INVALID_HANDLE;
Handle g_OnMapResult = INVALID_HANDLE;
Handle g_OnMapVetoed = INVALID_HANDLE;
Handle g_OnTeamReadyStatusChanged = INVALID_HANDLE;
Handle g_OnKnifeRoundStarted = INVALID_HANDLE;
Handle g_OnKnifeRoundWon = INVALID_HANDLE;
Handle g_OnMatchPaused = INVALID_HANDLE;
Handle g_OnMatchUnpaused = INVALID_HANDLE;
Handle g_OnPauseBegan = INVALID_HANDLE;
Handle g_OnPlayerConnected = INVALID_HANDLE;
Handle g_OnPlayerDisconnected = INVALID_HANDLE;
Handle g_OnPlayerDeath = INVALID_HANDLE;
Handle g_OnPlayerBecameMVP = INVALID_HANDLE;
Handle g_OnPlayerSay = INVALID_HANDLE;
Handle g_OnRoundEnd = INVALID_HANDLE;
Handle g_OnRoundStart = INVALID_HANDLE;
Handle g_OnPreLoadMatchConfig = INVALID_HANDLE;
Handle g_OnRoundStatsUpdated = INVALID_HANDLE;
Handle g_OnSeriesInit = INVALID_HANDLE;
Handle g_OnSeriesResult = INVALID_HANDLE;
Handle g_OnSidePicked = INVALID_HANDLE;
#include "get5/version.sp"
// Space required, or clang will alphabetize this and put version.sp at the end which breaks compilation.
#include "get5/backups.sp"
#include "get5/chatcommands.sp"
#include "get5/debug.sp"
#include "get5/events.sp"
#include "get5/get5menu.sp"
#include "get5/goinglive.sp"
#include "get5/http.sp"
#include "get5/jsonhelpers.sp"
#include "get5/kniferounds.sp"
#include "get5/maps.sp"
#include "get5/mapveto.sp"
#include "get5/matchconfig.sp"
#include "get5/natives.sp"
#include "get5/pausing.sp"
#include "get5/readysystem.sp"
#include "get5/recording.sp"
#include "get5/stats.sp"
#include "get5/surrender.sp"
#include "get5/teamlogic.sp"
#include "get5/tests.sp"
// clang-format off
public Plugin myinfo = {
name = "Get5",
author = "splewis, nickdnk & PhlexPlexico",
description = "",
version = PLUGIN_VERSION,
url = "https://github.com/splewis/get5"
};
// clang-format on
/**
* Core SourceMod forwards,
*/
public void OnAllPluginsLoaded() {
Handle h = FindPluginByFile("basebans.smx");
if (h != INVALID_HANDLE) {
LogMessage("Basebans plugin detected. You should remove this plugin as it conflicts with Get5. Unloading...");
ServerCommand("sm plugins unload basebans");
LogMessage("Unloaded basebans.smx.");
}
}
public void OnPluginStart() {
InitDebugLog(DEBUG_CVAR, "get5");
LogDebug("OnPluginStart version=%s", PLUGIN_VERSION);
// Make JSON payloads smaller by using 2 spaces instead of 4 to pretty print.
JSON_PP_INDENT = " ";
// Because we use SDKHooks for damage, we need to re-hook clients that are already on the server
// in case the plugin is reloaded. This includes bots.
LOOP_CLIENTS(i) {
if (IsValidClient(i)) {
Stats_HookDamageForClient(i);
}
}
/** Translations **/
LoadTranslations("get5.phrases");
LoadTranslations("common.phrases");
/** ConVars **/
// clang-format off
// Pauses
g_AllowPauseCancellationCvar = CreateConVar("get5_allow_pause_cancellation", "1", "Whether requests for pauses can be canceled by the pausing team using !unpause before freezetime begins.");
g_AllowTechPauseCvar = CreateConVar("get5_allow_technical_pause", "1", "Whether technical pauses are allowed by players.");
g_AllowUnpausingFixedPausesCvar = CreateConVar("get5_allow_unpausing_fixed_pauses", "1", "Whether fixed-length tactical pauses can be stopped early if both teams !unpause.");
g_AutoTechPauseMissingPlayersCvar = CreateConVar("get5_auto_tech_pause_missing_players", "0", "The number of players that must leave a team to trigger an automatic technical pause. Set to 0 to disable.");
g_FixedPauseTimeCvar = CreateConVar("get5_fixed_pause_time", "60", "The fixed duration of tactical pauses in seconds. Cannot be set lower than 15 if non-zero.");
g_MaxTacticalPausesCvar = CreateConVar("get5_max_pauses", "0", "Number of tactical pauses a team can use. 0 = unlimited.");
g_MaxPauseTimeCvar = CreateConVar("get5_max_pause_time", "0", "Maximum number of seconds a game can spend under tactical pause for each team. 0 = unlimited.");
g_MaxTechPausesCvar = CreateConVar("get5_max_tech_pauses", "0", "Number of technical pauses a team can use. 0 = unlimited.");
g_PausingEnabledCvar = CreateConVar("get5_pausing_enabled", "1", "Whether tactical pauses are allowed by players.");
g_ResetPausesEachHalfCvar = CreateConVar("get5_reset_pauses_each_half", "1", "Whether tactical pause limits will be reset on halftime.");
g_MaxTechPauseDurationCvar = CreateConVar("get5_tech_pause_time", "0", "Number of seconds before anyone can call !unpause during a technical timeout. 0 = unlimited.");
// Backups
g_RoundBackupPathCvar = CreateConVar("get5_backup_path", "", "The folder to save backup files in, relative to the csgo directory. If defined, it must not start with a slash and must end with a slash. Set to empty string to use the csgo root.");
g_BackupSystemEnabledCvar = CreateConVar("get5_backup_system_enabled", "1", "Whether the Get5 backup system is enabled.");
g_MaxBackupAgeCvar = CreateConVar("get5_max_backup_age", "172800", "Number of seconds before a backup file is automatically deleted. Set to 0 to disable. Default is 2 days.");
g_StopCommandEnabledCvar = CreateConVar("get5_stop_command_enabled", "1", "Whether clients can use the !stop command to restore to the beginning of the current round.");
g_StopCommandNoDamageCvar = CreateConVar("get5_stop_command_no_damage", "0", "Whether the stop command becomes unavailable if a player damages a player from the opposing team.");
g_StopCommandTimeLimitCvar = CreateConVar("get5_stop_command_time_limit", "0", "The number of seconds into a round after which a team can no longer request/confirm to stop and restart the round.");
g_RemoteBackupURLCvar = CreateConVar("get5_remote_backup_url", "", "A URL to send backup files to over HTTP. Leave empty to disable.");
g_RemoteBackupURLHeaderKeyCvar = CreateConVar("get5_remote_backup_header_key", "Authorization", "If defined, a custom HTTP header with this name is added to the backup HTTP request.", FCVAR_DONTRECORD);
g_RemoteBackupURLHeaderValueCvar = CreateConVar("get5_remote_backup_header_value", "", "If defined, the value of the custom header added to the backup HTTP request.", FCVAR_DONTRECORD | FCVAR_PROTECTED);
// Demos
g_DemoUploadDeleteAfterCvar = CreateConVar("get5_demo_delete_after_upload", "0", "Whether to delete the demo from the game server after a successful upload.");
g_DemoNameFormatCvar = CreateConVar("get5_demo_name_format", "{TIME}_{MATCHID}_map{MAPNUMBER}_{MAPNAME}", "The format to use for demo files. Do not remove the {TIME} placeholder if you use the backup system. Set to empty string to disable automatic demo recording.");
g_DemoPathCvar = CreateConVar("get5_demo_path", "", "The folder to save demo files in, relative to the csgo directory. If defined, it must not start with a slash and must end with a slash. Set to empty string to use the csgo root.");
g_DemoUploadHeaderKeyCvar = CreateConVar("get5_demo_upload_header_key", "Authorization", "If defined, a custom HTTP header with this name is added to the demo upload HTTP request.", FCVAR_DONTRECORD);
g_DemoUploadHeaderValueCvar = CreateConVar("get5_demo_upload_header_value", "", "If defined, the value of the custom header added to the demo upload HTTP request.", FCVAR_DONTRECORD | FCVAR_PROTECTED);
g_DemoUploadURLCvar = CreateConVar("get5_demo_upload_url", "", "If defined, recorded demos will be uploaded to this URL over HTTP. If no protocol is provided, 'http://' is prepended to this value.", FCVAR_DONTRECORD);
// Surrender/Forfeit
g_ForfeitCountdownTimeCvar = CreateConVar("get5_forfeit_countdown", "180", "The grace-period (in seconds) for rejoining the server to avoid a loss by forfeit.", 0, true, 30.0);
g_ForfeitEnabledCvar = CreateConVar("get5_forfeit_enabled", "1", "Whether the forfeit feature is enabled.");
g_SurrenderCooldownCvar = CreateConVar("get5_surrender_cooldown", "60", "The number of seconds before a vote to surrender can be retried if it fails.");
g_SurrenderEnabledCvar = CreateConVar("get5_surrender_enabled", "0", "Whether the surrender command is enabled.");
g_MinimumRoundDeficitForSurrenderCvar = CreateConVar("get5_surrender_minimum_round_deficit", "8", "The minimum number of rounds a team must be behind in order to surrender.", 0, true, 0.0);
g_VotesRequiredForSurrenderCvar = CreateConVar("get5_surrender_required_votes", "3", "The number of votes required for a team to surrender.", 0, true, 1.0);
g_SurrenderVoteTimeLimitCvar = CreateConVar("get5_surrender_time_limit", "15", "The number of seconds before a vote to surrender fails.", 0, true, 10.0);
// Events
g_EventLogFormatCvar = CreateConVar("get5_event_log_format", "", "Path to use when writing match event logs to disk. Use \"\" to disable.");
g_EventLogRemoteHeaderKeyCvar = CreateConVar("get5_remote_log_header_key", "Authorization", "If defined, a custom HTTP header with this name is added to the HTTP requests for events.", FCVAR_DONTRECORD);
g_EventLogRemoteHeaderValueCvar = CreateConVar("get5_remote_log_header_value", "", "If defined, the value of the custom header added to the events sent over HTTP.", FCVAR_DONTRECORD | FCVAR_PROTECTED);
g_EventLogRemoteURLCvar = CreateConVar("get5_remote_log_url", "", "If defined, all events are sent to this URL over HTTP. If no protocol is provided, 'http://' is prepended to this value.", FCVAR_DONTRECORD);
// Damage info
g_DamagePrintCvar = CreateConVar("get5_print_damage", "1", "Whether damage reports are printed to chat on round end.");
g_DamagePrintExcessCvar = CreateConVar("get5_print_damage_excess", "0", "Prints full damage given in the damage report on round end. With this disabled, a player cannot take more than 100 damage.");
g_DamagePrintFormatCvar = CreateConVar("get5_damageprint_format", "- [{KILL_TO}] ({DMG_TO} in {HITS_TO}) to [{KILL_FROM}] ({DMG_FROM} in {HITS_FROM}) from {NAME} ({HEALTH} HP)", "Format of the damage output string. Available tags are in the default, color tags such as {LIGHT_RED} and {GREEN} also work. {KILL_TO} and {KILL_FROM} indicate kills, assists and flash assists as booleans, all of which are mutually exclusive.");
// Date/time formats
g_DateFormatCvar = CreateConVar("get5_date_format", "%Y-%m-%d", "Date format to use when creating file names. Don't tweak this unless you know what you're doing! Avoid using spaces or colons.");
g_TimeFormatCvar = CreateConVar("get5_time_format", "%Y-%m-%d_%H-%M-%S", "Time format to use when creating file names. Don't tweak this unless you know what you're doing! Avoid using spaces or colons.");
// Ready system
g_AllowForceReadyCvar = CreateConVar("get5_allow_force_ready", "1", "Allows players to use the !forceready command.");
g_AutoReadyActivePlayersCvar = CreateConVar("get5_auto_ready_active_players", "0", "Whether to automatically mark players as ready if they kill anyone in the warmup or map selection phase.");
g_ReadyTeamTagCvar = CreateConVar("get5_ready_team_tag", "1", "Adds [READY]/[NOT READY] tags to team names.");
g_SetClientClanTagCvar = CreateConVar("get5_set_client_clan_tags", "1", "Whether to set client clan tags to player ready status.");
// Chat/color
g_MessagePrefixCvar = CreateConVar("get5_message_prefix", DEFAULT_TAG, "The tag printed before each chat message.");
g_PhaseAnnouncementCountCvar = CreateConVar("get5_phase_announcement_count", "5", "The number of times 'Knife' or 'Match is LIVE' is printed to chat when the game starts.");
g_SpecNameColorCvar = CreateConVar("get5_spec_color", "{NORMAL}", "The color used for the name of spectators in chat messages.");
g_Team1NameColorCvar = CreateConVar("get5_team1_color", "{LIGHT_GREEN}", "The color used for the name of team 1 in chat messages.");
g_Team2NameColorCvar = CreateConVar("get5_team2_color", "{PINK}", "The color used for the name of team 2 in chat messages.");
// Countdown/timers
g_LiveCountdownTimeCvar = CreateConVar("get5_live_countdown_time", "10", "Number of seconds used to count down when a match is going live.", 0, true, 5.0, true, 60.0);
g_TimeToStartCvar = CreateConVar("get5_time_to_start", "0", "Time (in seconds) teams have to ready up for live/knife before forfeiting the match. 0 = unlimited.");
g_TimeToStartVetoCvar = CreateConVar("get5_time_to_start_veto", "0", "Time (in seconds) teams have to ready up for map selection before forfeiting the match. 0 = unlimited.");
g_TeamTimeToKnifeDecisionCvar = CreateConVar("get5_time_to_make_knife_decision", "60", "Time (in seconds) a team has to make a !stay/!swap decision after winning knife round. 0 = unlimited.");
g_VetoCountdownCvar = CreateConVar("get5_veto_countdown", "5", "Seconds to countdown before veto process commences. 0 to skip countdown.");
// Veto
g_MuteAllChatDuringMapSelectionCvar = CreateConVar("get5_mute_allchat_during_map_selection", "1", "If enabled, only the team captains can type in all-chat during map selection.");
g_PauseOnVetoCvar = CreateConVar("get5_pause_on_veto", "1", "Whether the game pauses during map selection.");
g_DisplayGotvVetoCvar = CreateConVar("get5_display_gotv_veto", "0", "Whether to wait for map selection to be broadcast to GOTV before changing map.");
// Server config
g_AutoLoadConfigCvar = CreateConVar("get5_autoload_config", "", "The path/name of a match config file to automatically load when the server loads or when the first player joins.");
g_CheckAuthsCvar = CreateConVar("get5_check_auths", "1", "Whether players are forced onto the correct teams based on their Steam IDs.");
g_SetHostnameCvar = CreateConVar("get5_hostname_format", "Get5: {TEAM1} vs {TEAM2}", "The server hostname to use when a match is loaded. Set to \"\" to disable/use existing.");
g_KickClientImmunityCvar = CreateConVar("get5_kick_immunity", "1", "Whether admins with the 'changemap' flag will be immune to kicks from \"get5_kick_when_no_match_loaded\".");
g_KickClientsWithNoMatchCvar = CreateConVar("get5_kick_when_no_match_loaded", "0", "Whether the plugin kicks players when no match is loaded and when a match ends.");
g_KickOnForceEndCvar = CreateConVar("get5_kick_on_force_end", "0", "Whether players are kicked from the server when a match is forcefully ended. Requires get5_kick_when_no_match_loaded to be enabled also.");
g_KnifeCfgCvar = CreateConVar("get5_knife_cfg", "get5/knife.cfg", "Config file to execute for the knife round.");
g_LiveCfgCvar = CreateConVar("get5_live_cfg", "get5/live.cfg", "Config file to execute when the game goes live.");
g_LiveWingmanCfgCvar = CreateConVar("get5_live_wingman_cfg", "get5/live_wingman.cfg", "Config file to execute when the game goes live, but for wingman mode.");
g_TeamsFileCvar = CreateConVar("get5_teams_file", "get5/teams.json", "The JSON file that contains teams selectable in the Get5 setup menu.");
g_MapsFileCvar = CreateConVar("get5_maps_file", "get5/maps.json", "The JSON file that contains maps selectable in the Get5 setup menu.");
g_CvarsFileCvar = CreateConVar("get5_cvars_file", "get5/cvars.json", "The JSON file that contains sets of ConVars selectable when using the get5_creatematch CLI.");
g_PrettyPrintJsonCvar = CreateConVar("get5_pretty_print_json", "1", "Whether all JSON output is in pretty-print format.");
g_PrintUpdateNoticeCvar = CreateConVar("get5_print_update_notice", "1", "Whether to print to chat when the game goes live if a new version of Get5 is available.");
g_ServerIdCvar = CreateConVar("get5_server_id", "0", "A string that identifies your server. This is used in temporary files to prevent collisions and added as an HTTP header for network requests made by Get5.");
g_StatsPathFormatCvar = CreateConVar("get5_stats_path_format", "get5_matchstats_{MATCHID}.cfg", "Where match stats are saved (updated each map end). Set to \"\" to disable.");
g_WarmupCfgCvar = CreateConVar("get5_warmup_cfg", "get5/warmup.cfg", "Config file to execute during warmup periods.");
g_ResetCvarsOnEndCvar = CreateConVar("get5_reset_cvars_on_end", "1", "Whether parameters from the \"cvars\" section of a match configuration and the Get5-determined hostname are restored to their original values when a series ends.");
// clang-format on
/** Create and exec plugin's configuration file **/
AutoExecConfig(true, "get5");
g_GameStateCvar = CreateConVar("get5_game_state", "0", "Current game state (see get5.inc)", FCVAR_DONTRECORD);
g_LastGet5BackupCvar = CreateConVar("get5_last_backup_file", "", "Last get5 backup file written", FCVAR_DONTRECORD);
g_VersionCvar = CreateConVar("get5_version", PLUGIN_VERSION, "Current get5 version",
FCVAR_SPONLY | FCVAR_REPLICATED | FCVAR_NOTIFY | FCVAR_DONTRECORD);
g_VersionCvar.SetString(PLUGIN_VERSION);
g_CoachingEnabledCvar = FindConVar("sv_coaching_enabled");
g_CoachingEnabledCvar.AddChangeHook(CoachingChangedHook); // used to move people off coaching if it gets disabled.
/** Client commands **/
g_ChatAliases = new ArrayList(ByteCountToCells(ALIAS_LENGTH));
g_ChatAliasesCommands = new ArrayList(ByteCountToCells(COMMAND_LENGTH));
g_ChatCommands = new StringMap();
// Default chat mappings.
MapChatCommand(Get5ChatCommand_Ready, "r");
MapChatCommand(Get5ChatCommand_Ready, "ready");
MapChatCommand(Get5ChatCommand_Unready, "notready");
MapChatCommand(Get5ChatCommand_Unready, "unready");
MapChatCommand(Get5ChatCommand_ForceReady, "forceready");
MapChatCommand(Get5ChatCommand_Tech, "tech");
MapChatCommand(Get5ChatCommand_Pause, "tac");
MapChatCommand(Get5ChatCommand_Pause, "pause");
MapChatCommand(Get5ChatCommand_Unpause, "unpause");
MapChatCommand(Get5ChatCommand_Coach, "coach");
MapChatCommand(Get5ChatCommand_Stay, "stay");
MapChatCommand(Get5ChatCommand_Swap, "switch");
MapChatCommand(Get5ChatCommand_Swap, "swap");
MapChatCommand(Get5ChatCommand_T, "t");
MapChatCommand(Get5ChatCommand_CT, "ct");
MapChatCommand(Get5ChatCommand_Stop, "stop");
MapChatCommand(Get5ChatCommand_Surrender, "gg");
MapChatCommand(Get5ChatCommand_Surrender, "surrender");
MapChatCommand(Get5ChatCommand_FFW, "ffw");
MapChatCommand(Get5ChatCommand_CancelFFW, "cancelffw");
MapChatCommand(Get5ChatCommand_Pick, "pick");
MapChatCommand(Get5ChatCommand_Ban, "ban");
LoadCustomChatAliases("addons/sourcemod/configs/get5/commands.cfg");
/** Admin/server commands **/
RegAdminCmd("get5_loadmatch", Command_LoadMatch, ADMFLAG_CHANGEMAP,
"Loads a match config file (json or keyvalues) from a file relative to the csgo/ directory");
RegAdminCmd("get5_loadmatch_url", Command_LoadMatchUrl, ADMFLAG_CHANGEMAP,
"Loads a JSON config file by sending a GET request to download it. Requires the SteamWorks extension.");
RegAdminCmd("get5_loadteam", Command_LoadTeam, ADMFLAG_CHANGEMAP, "Loads a team data from a file into a team");
RegAdminCmd("get5_endmatch", Command_EndMatch, ADMFLAG_CHANGEMAP, "Force ends the current match");
RegAdminCmd("get5_addplayer", Command_AddPlayer, ADMFLAG_CHANGEMAP, "Adds a steamid to a match team");
RegAdminCmd("get5_addcoach", Command_AddCoach, ADMFLAG_CHANGEMAP, "Adds a steamid to a match teams coach slot");
RegAdminCmd("get5_removeplayer", Command_RemovePlayer, ADMFLAG_CHANGEMAP, "Removes a steamid from a match team");
RegAdminCmd("get5_addkickedplayer", Command_AddKickedPlayer, ADMFLAG_CHANGEMAP,
"Adds the last kicked steamid to a match team");
RegAdminCmd("get5_removekickedplayer", Command_RemoveKickedPlayer, ADMFLAG_CHANGEMAP,
"Removes the last kicked steamid from a match team");
RegAdminCmd("get5_creatematch", Command_CreateMatch, ADMFLAG_CHANGEMAP,
"Creates and loads a match using the provided command-line parameters.");
RegAdminCmd(
"get5_add_ready_time", Command_AddReadyTime, ADMFLAG_CHANGEMAP,
"Adds additional ready-time by deducting the provided seconds from the time already used during a ready-phase.");
RegAdminCmd("get5_scrim", Command_CreateScrim, ADMFLAG_CHANGEMAP,
"Creates and loads a match using the scrim template");
RegAdminCmd("sm_scrim", Command_CreateScrim, ADMFLAG_CHANGEMAP, "Creates and loads a match using the scrim template");
RegAdminCmd("get5_ringer", Command_Ringer, ADMFLAG_CHANGEMAP, "Adds/removes a ringer to/from the home scrim team");
RegAdminCmd("sm_ringer", Command_Ringer, ADMFLAG_CHANGEMAP, "Adds/removes a ringer to/from the home scrim team");
RegAdminCmd("sm_get5", Command_Get5AdminMenu, ADMFLAG_CHANGEMAP, "Displays a helper menu");
RegAdminCmd("get5_forceready", Command_AdminForceReady, ADMFLAG_CHANGEMAP, "Force readies all current teams");
RegAdminCmd("get5_forcestart", Command_AdminForceReady, ADMFLAG_CHANGEMAP, "Force readies all current teams");
RegAdminCmd("get5_dumpstats", Command_DumpStats, ADMFLAG_CHANGEMAP, "Dumps match stats to a file");
RegAdminCmd("get5_listbackups", Command_ListBackups, ADMFLAG_CHANGEMAP,
"Lists get5 match backups for the current matchid or a given one");
RegAdminCmd("get5_loadbackup", Command_LoadBackup, ADMFLAG_CHANGEMAP,
"Loads a Get5 match backup from a file relative to the csgo directory.");
RegAdminCmd("get5_loadbackup_url", Command_LoadBackupUrl, ADMFLAG_CHANGEMAP,
"Downloads and loads a Get5 match backup from a URL.");
RegAdminCmd("get5_debuginfo", Command_DebugInfo, ADMFLAG_CHANGEMAP,
"Dumps debug info to a file (addons/sourcemod/logs/get5_debuginfo.txt by default)");
/** Other commands **/
RegConsoleCmd("get5_status", Command_Status, "Prints JSON formatted match state info");
RegServerCmd(
"get5_test", Command_Test,
"Runs get5 tests - should not be used on a live match server since it will reload a match config to test");
/** Hooks **/
HookEvent("cs_win_panel_match", Event_MatchOver);
HookEvent("cs_win_panel_round", Event_RoundWinPanel, EventHookMode_Pre);
HookEvent("player_connect_full", Event_PlayerConnectFull);
HookEvent("player_disconnect", Event_PlayerDisconnect);
HookEvent("player_spawn", Event_PlayerSpawn);
HookEvent("round_end", Event_RoundEnd, EventHookMode_Pre);
HookEvent("round_freeze_end", Event_FreezeEnd);
HookEvent("round_prestart", Event_RoundPreStart);
HookEvent("round_start", Event_RoundStart);
HookEvent("server_cvar", Event_CvarChanged, EventHookMode_Pre);
Stats_PluginStart();
Stats_InitSeries();
AddCommandListener(Command_Coach, "coach");
AddCommandListener(Command_JoinTeam, "jointeam");
AddCommandListener(Command_JoinGame, "joingame");
AddCommandListener(Command_PauseOrUnpauseMatch, "mp_pause_match");
AddCommandListener(Command_PauseOrUnpauseMatch, "mp_unpause_match");
AddCommandListener(Command_BlockSuicide, "explode");
AddCommandListener(Command_BlockSuicide, "kill");
/** Setup data structures **/
g_MapPoolList = new ArrayList(PLATFORM_MAX_PATH);
g_MapsLeftInVetoPool = new ArrayList(PLATFORM_MAX_PATH);
g_MapsToPlay = new ArrayList(PLATFORM_MAX_PATH);
g_MapSides = new ArrayList();
g_CvarNames = new ArrayList(MAX_CVAR_LENGTH);
g_CvarValues = new ArrayList(MAX_CVAR_LENGTH);
g_TeamScoresPerMap = new ArrayList(MATCHTEAM_COUNT);
g_MapBanOrder = new ArrayList();
for (int i = 0; i < sizeof(g_TeamPlayers); i++) {
g_TeamPlayers[i] = new ArrayList(AUTH_LENGTH);
// Same length.
g_TeamCoaches[i] = new ArrayList(AUTH_LENGTH);
}
g_PlayerNames = new StringMap();
g_FlashbangContainer = new StringMap();
g_HEGrenadeContainer = new StringMap();
g_MolotovContainer = new StringMap();
/** Create forwards **/
g_OnBackupRestore = CreateGlobalForward("Get5_OnBackupRestore", ET_Ignore, Param_Cell);
g_OnDemoFinished = CreateGlobalForward("Get5_OnDemoFinished", ET_Ignore, Param_Cell);
g_OnDemoUploadEnded = CreateGlobalForward("Get5_OnDemoUploadEnded", ET_Ignore, Param_Cell);
g_OnEvent = CreateGlobalForward("Get5_OnEvent", ET_Ignore, Param_Cell, Param_String);
g_OnFlashbangDetonated = CreateGlobalForward("Get5_OnFlashbangDetonated", ET_Ignore, Param_Cell);
g_OnHEGrenadeDetonated = CreateGlobalForward("Get5_OnHEGrenadeDetonated", ET_Ignore, Param_Cell);
g_OnDecoyStarted = CreateGlobalForward("Get5_OnDecoyStarted", ET_Ignore, Param_Cell);
g_OnSmokeGrenadeDetonated = CreateGlobalForward("Get5_OnSmokeGrenadeDetonated", ET_Ignore, Param_Cell);
g_OnMolotovDetonated = CreateGlobalForward("Get5_OnMolotovDetonated", ET_Ignore, Param_Cell);
g_OnGameStateChanged = CreateGlobalForward("Get5_OnGameStateChanged", ET_Ignore, Param_Cell);
g_OnGoingLive = CreateGlobalForward("Get5_OnGoingLive", ET_Ignore, Param_Cell);
g_OnGrenadeThrown = CreateGlobalForward("Get5_OnGrenadeThrown", ET_Ignore, Param_Cell);
g_OnMapResult = CreateGlobalForward("Get5_OnMapResult", ET_Ignore, Param_Cell);
g_OnPlayerConnected = CreateGlobalForward("Get5_OnPlayerConnected", ET_Ignore, Param_Cell);
g_OnPlayerDisconnected = CreateGlobalForward("Get5_OnPlayerDisconnected", ET_Ignore, Param_Cell);
g_OnPlayerDeath = CreateGlobalForward("Get5_OnPlayerDeath", ET_Ignore, Param_Cell);
g_OnPlayerSay = CreateGlobalForward("Get5_OnPlayerSay", ET_Ignore, Param_Cell);
g_OnPlayerBecameMVP = CreateGlobalForward("Get5_OnPlayerBecameMVP", ET_Ignore, Param_Cell);
g_OnBombDefused = CreateGlobalForward("Get5_OnBombDefused", ET_Ignore, Param_Cell);
g_OnBombPlanted = CreateGlobalForward("Get5_OnBombPlanted", ET_Ignore, Param_Cell);
g_OnBombExploded = CreateGlobalForward("Get5_OnBombExploded", ET_Ignore, Param_Cell);
g_OnRoundStart = CreateGlobalForward("Get5_OnRoundStart", ET_Ignore, Param_Cell);
g_OnRoundEnd = CreateGlobalForward("Get5_OnRoundEnd", ET_Ignore, Param_Cell);
g_OnLoadMatchConfigFailed = CreateGlobalForward("Get5_OnLoadMatchConfigFailed", ET_Ignore, Param_Cell);
g_OnMapPicked = CreateGlobalForward("Get5_OnMapPicked", ET_Ignore, Param_Cell);
g_OnMapVetoed = CreateGlobalForward("Get5_OnMapVetoed", ET_Ignore, Param_Cell);
g_OnSidePicked = CreateGlobalForward("Get5_OnSidePicked", ET_Ignore, Param_Cell);
g_OnTeamReadyStatusChanged = CreateGlobalForward("Get5_OnTeamReadyStatusChanged", ET_Ignore, Param_Cell);
g_OnKnifeRoundStarted = CreateGlobalForward("Get5_OnKnifeRoundStarted", ET_Ignore, Param_Cell);
g_OnKnifeRoundWon = CreateGlobalForward("Get5_OnKnifeRoundWon", ET_Ignore, Param_Cell);
g_OnRoundStatsUpdated = CreateGlobalForward("Get5_OnRoundStatsUpdated", ET_Ignore, Param_Cell);
g_OnPreLoadMatchConfig = CreateGlobalForward("Get5_OnPreLoadMatchConfig", ET_Ignore, Param_Cell);
g_OnSeriesInit = CreateGlobalForward("Get5_OnSeriesInit", ET_Ignore, Param_Cell);
g_OnSeriesResult = CreateGlobalForward("Get5_OnSeriesResult", ET_Ignore, Param_Cell);
g_OnMatchPaused = CreateGlobalForward("Get5_OnMatchPaused", ET_Ignore, Param_Cell);
g_OnMatchUnpaused = CreateGlobalForward("Get5_OnMatchUnpaused", ET_Ignore, Param_Cell);
g_OnPauseBegan = CreateGlobalForward("Get5_OnPauseBegan", ET_Ignore, Param_Cell);
/** Start any repeating timers **/
CreateTimer(CHECK_READY_TIMER_INTERVAL, Timer_CheckReady, _, TIMER_REPEAT);
RestartInfoTimer();
CheckForLatestVersion();
}
static Action Timer_InfoMessages(Handle timer) {
if (g_GameState == Get5State_Live || g_GameState == Get5State_None) {
return Plugin_Continue;
}
char readyCommandFormatted[64];
GetChatAliasForCommand(Get5ChatCommand_Ready, readyCommandFormatted, sizeof(readyCommandFormatted), true);
char unreadyCommandFormatted[64];
GetChatAliasForCommand(Get5ChatCommand_Unready, unreadyCommandFormatted, sizeof(unreadyCommandFormatted), true);
char coachCommandFormatted[64];
GetChatAliasForCommand(Get5ChatCommand_Coach, coachCommandFormatted, sizeof(coachCommandFormatted), true);
if (g_GameState == Get5State_PendingRestore) {
if (!IsTeamsReady() && !IsDoingRestoreOrMapChange()) {
Get5_MessageToAll("%t", "ReadyToRestoreBackupInfoMessage", readyCommandFormatted);
}
} else if (g_GameState == Get5State_Warmup || g_GameState == Get5State_PreVeto) {
if (!g_MapChangePending) {
// Find out what we're waiting for
if (IsTeamsReady() && !IsSpectatorsReady()) {
Get5_MessageToAll("%t", "WaitingForCastersReadyInfoMessage", g_FormattedTeamNames[Get5Team_Spec],
readyCommandFormatted);
} else {
// g_MapSides empty if we veto, so make sure to only check this during warmup.
bool knifeRound = g_GameState == Get5State_Warmup && g_MapSides.Get(g_MapNumber) == SideChoice_KnifeRound;
bool coachingEnabled = g_CoachingEnabledCvar.BoolValue && g_CoachesPerTeam > 0;
LOOP_CLIENTS(i) {
if (!IsPlayer(i)) {
continue;
}
Get5Team team = GetClientMatchTeam(i);
if (team == Get5Team_None) {
continue;
}
bool coach = IsClientCoaching(i);
if ((!coach || g_CoachesMustReady) && (team != Get5Team_Spec || g_MinSpectatorsToReady > 0)) {
if (IsClientReady(i)) {
Get5_Message(i, "%t", "TypeUnreadyIfNotReady", unreadyCommandFormatted);
} else {
Get5_Message(i, "%t",
g_GameState == Get5State_PreVeto
? "ReadyForMapSelectionInfoMessage"
: (knifeRound ? "ReadyToKnifeInfoMessage" : "ReadyToStartInfoMessage"),
readyCommandFormatted);
}
}
if (team == Get5Team_Spec) {
// Spectators cannot coach.
continue;
}
if (coach) {
Get5_Message(i, "%t", "ExitCoachSlotHelp", coachCommandFormatted);
} else if (coachingEnabled) {
Get5_Message(i, "%t", "EnterCoachSlotHelp", coachCommandFormatted);
}
}
}
MissingPlayerInfoMessage();
} else if (g_GameState == Get5State_Warmup && g_DisplayGotvVetoCvar.BoolValue && GetTvDelay() > 0) {
Get5_MessageToAll("%t", "WaitingForGOTVMapSelection");
}
} else if (g_GameState == Get5State_Veto) {
PrintVetoHelpMessage();
} else if (g_GameState == Get5State_WaitingForKnifeRoundDecision) {
PromptForKnifeDecision();
} else if (g_GameState == Get5State_PostGame) {
if (g_ResetCvarsTimer == INVALID_HANDLE && GetTvDelay() > 0) {
// Only print this if the reset timer isn't running, which would mean it's the last map.
Get5_MessageToAll("%t", "WaitingForGOTVBroadcastEnding");
}
}
return Plugin_Continue;
}
public void OnClientAuthorized(int client, const char[] auth) {
SetClientReady(client, false);
if (StrEqual(auth, "BOT", false)) {
return;
}
if (g_GameState != Get5State_None && g_CheckAuthsCvar.BoolValue) {
Get5Team team = GetClientMatchTeam(client);
if (team == Get5Team_None) {
RememberAndKickClient(client, "%t", "YouAreNotAPlayerInfoMessage");
} else if (CountPlayersOnTeam(team, client) >= g_PlayersPerTeam &&
(!g_CoachingEnabledCvar.BoolValue || CountCoachesOnTeam(team, client) >= g_CoachesPerTeam)) {
KickClient(client, "%t", "TeamIsFullInfoMessage");
}
}
}
void RememberAndKickClient(int client, const char[] format, const char[] translationPhrase) {
GetAuth(client, g_LastKickedPlayerAuth, sizeof(g_LastKickedPlayerAuth));
KickClient(client, format, translationPhrase);
}
public void OnClientPutInServer(int client) {
LogDebug("OnClientPutInServer");
Stats_HookDamageForClient(client); // Also needed for bots!
if (IsFakeClient(client)) {
return;
}
// If a player joins during freezetime, ensure their round stats are 0, as there will be no
// round-start event to do it. Maybe this could just be freezetime end?
Stats_ResetClientRoundValues(client);
// Because OnConfigsExecuted may run before a client is on the server, we have to repeat the
// start-logic here when the first client connects.
SetServerStateOnStartup(false);
}
public void OnClientPostAdminCheck(int client) {
if (IsPlayer(client)) {
if (g_GameState == Get5State_None && g_KickClientsWithNoMatchCvar.BoolValue) {
if (!g_KickClientImmunityCvar.BoolValue || !CheckCommandAccess(client, "get5_kickcheck", ADMFLAG_CHANGEMAP)) {
KickClient(client, "%t", "NoMatchSetupInfoMessage");
}
}
}
}
public Action OnClientSayCommand(int client, const char[] command, const char[] sArgs) {
if (client > 0 && g_GameState == Get5State_Veto && g_MuteAllChatDuringMapSelectionCvar.BoolValue &&
StrEqual(command, "say") && client != g_VetoCaptains[Get5Team_1] && client != g_VetoCaptains[Get5Team_2]) {
Get5_Message(client, "%t", "MapSelectionTeamChatOnly");
return Plugin_Stop;
}
return Plugin_Continue;
}
public void OnClientSayCommand_Post(int client, const char[] command, const char[] sArgs) {
if (g_GameState != Get5State_None && (StrEqual(command, "say") || StrEqual(command, "say_team"))) {
Get5PlayerSayEvent event = new Get5PlayerSayEvent(g_MatchID, g_MapNumber, g_RoundNumber, GetRoundTime(),
GetPlayerObject(client), command, sArgs);
LogDebug("Calling Get5_OnPlayerSay()");
Call_StartForward(g_OnPlayerSay);
Call_PushCell(event);
Call_Finish();
EventLogger_LogAndDeleteEvent(event);
}
CheckForChatAlias(client, sArgs);
}
/**
* Full connect event right when a player joins.
* This sets the auto-pick time to a high value because mp_forcepicktime is broken and
* if a player does not select a team but leaves their mouse over one, they are
* put on that team and spawned, so we can't allow that.
*/
static Action Event_PlayerConnectFull(Event event, const char[] name, bool dontBroadcast) {
if (g_GameState == Get5State_None) {
return Plugin_Continue;
}
int client = GetClientOfUserId(event.GetInt("userid"));
if (IsPlayer(client)) {
char ipAddress[32];
GetClientIP(client, ipAddress, sizeof(ipAddress));
Get5PlayerConnectedEvent connectEvent = new Get5PlayerConnectedEvent(g_MatchID, GetPlayerObject(client), ipAddress);
LogDebug("Calling Get5_OnPlayerConnected()");
Call_StartForward(g_OnPlayerConnected);
Call_PushCell(connectEvent);
Call_Finish();
EventLogger_LogAndDeleteEvent(connectEvent);
SetEntPropFloat(client, Prop_Send, "m_fForceTeam", 3600.0);
}
return Plugin_Continue;
}
static Action Event_PlayerDisconnect(Event event, const char[] name, bool dontBroadcast) {
int client = GetClientOfUserId(event.GetInt("userid"));
g_ClientPendingTeamCheck[client] = false;
if (g_GameState == Get5State_None || !IsPlayer(client)) {
return Plugin_Continue;
}
Get5PlayerDisconnectedEvent disconnectEvent = new Get5PlayerDisconnectedEvent(g_MatchID, GetPlayerObject(client));
LogDebug("Calling Get5_OnPlayerDisconnected()");
Call_StartForward(g_OnPlayerDisconnected);
Call_PushCell(disconnectEvent);
Call_Finish();
EventLogger_LogAndDeleteEvent(disconnectEvent);
// Because the disconnect event fires before the user leaves the server, we have to put this on a short callback
// to get the right "number of players per team" in CheckForForfeitOnDisconnect().
CreateTimer(0.1, Timer_DisconnectCheck, client, TIMER_FLAG_NO_MAPCHANGE);
return Plugin_Continue;
}
// This runs before OnConfigsExecuted and should not contain anything that reads game state because of the above
// mentioned hibernate race conditions.
public void OnMapStart() {
LogDebug("OnMapStart");
g_MapChangePending = false;
g_DoingBackupRestoreNow = false;
g_ReadyTimeWaitingUsed = 0;
g_KnifeWinnerTeam = Get5Team_None;
g_HasKnifeRoundStarted = false;
g_MapReloadRequired = false;
LOOP_TEAMS(team) {
g_TeamGivenStopCommand[team] = false;
g_TeamReadyForUnpause[team] = false;
if (g_GameState != Get5State_PendingRestore) {
g_TacticalPauseTimeUsed[team] = 0;
g_TacticalPausesUsed[team] = 0;
g_TechnicalPausesUsed[team] = 0;
}
}
// If the map is changed while a map timer is counting down, kill the timer. This could happen if
// a too long mp_match_restart_delay was set and admins decide to manually intervene.
if (g_PendingMapChangeTimer != INVALID_HANDLE) {
delete g_PendingMapChangeTimer;
LogDebug("Killed g_PendingMapChangeTimer as map was changed.");
}
EndSurrenderTimers();
// Always reset ready status on map start
ResetReadyStatus();
}
// This runs every time a map starts *or* when the plugin is reloaded.
public void OnConfigsExecuted() {
LogDebug("OnConfigsExecuted");
// If the server has hibernation enabled, running this without a delay will cause it to frequently
// fail with "Gamerules lookup failed" probably due to some odd internal race-condition where the
// game is not yet running when we attempt to determine its "is paused" or "is in warmup" state.
// Putting it on a 1 second callback seems to solve this problem.
CreateTimer(1.0, Timer_ConfigsExecutedCallback);
}
static Action Timer_ConfigsExecutedCallback(Handle timer) {
LogDebug("OnConfigsExecuted timer callback");
// This is a defensive solution that ensures we don't have lingering forfeit-timers. If everyone leaves and a player
// then joins the server again, the server may change the map, which triggers this. If this happens, we cannot
// recover the game state and must force the series to end if the game has progressed past warmup. If we trigger the
// timer during warmup, it might abruptly end the series when the first player connects to the server due to reloading
// of the map because of "force client reconnect" from the server.
if (g_ForfeitTimer != INVALID_HANDLE) {
if (g_GameState > Get5State_Warmup && g_GameState < Get5State_PendingRestore && !g_MapChangePending) {
LogDebug("Triggering forfeit timer immediately as map was changed post-warmup.");
TriggerTimer(g_ForfeitTimer);
} else {
LogDebug("Stopped forfeit timer as the map was changed in non-live state.");
ResetForfeitTimer();
}
}
// Recording is always automatically stopped on map change, and
// since there are no hooks to detect tv_stoprecord, we reset
// our recording var if a map change is performed unexpectedly.
g_DemoFilePath = "";
g_DemoFileName = "";
DeleteOldBackups();
if (CheckAutoLoadConfig()) {
// If gamestate is none and a config was autoloaded, a match config will set all of the below
// state.
return Plugin_Handled;
}
// On map start, always put the game in warmup mode.
// When executing a backup load, the live config is loaded and warmup ends after players ready-up
// again.
SetServerStateOnStartup(true);
// This must not be called when waiting for a backup, as it will set the sides incorrectly if the
// team swapped in knife or if the backup target is the second half.
if (g_GameState != Get5State_PendingRestore) {
SetStartingTeams();
}
return Plugin_Handled;
}
static Action Timer_CheckReady(Handle timer) {
if (g_GameState == Get5State_None) {
return Plugin_Continue;
}
if (IsDoingRestoreOrMapChange()) {
LogDebug("Timer_CheckReady: Waiting for restore or map change");
return Plugin_Continue;
}
CheckTeamNameStatus(Get5Team_1);
CheckTeamNameStatus(Get5Team_2);
UpdateClanTags();
// Handle ready checks for pre-veto state
if (g_GameState == Get5State_PreVeto) {
if (CheckReadyWaitingTimes()) {
// We don't wait for spectators when initiating veto
LogDebug("Timer_CheckReady: starting veto");
ChangeState(Get5State_Veto);
RestartGame();
CreateVeto();
SetMatchTeamCvars(); // Removes ready status.
}
} else if (g_GameState == Get5State_PendingRestore) {
// We don't wait for spectators when restoring backups
if (IsTeamsReady()) {
LogDebug("Timer_CheckReady: restoring from backup");
RestoreGet5Backup(true);
}
} else if (g_GameState == Get5State_Warmup) {
// Wait for both players and spectators before going live
if (CheckReadyWaitingTimes() && IsSpectatorsReady()) {
LogDebug("Timer_CheckReady: all teams ready to start");
StartGame(g_MapSides.Get(g_MapNumber) == SideChoice_KnifeRound);
StartRecording();
}
}
return Plugin_Continue;