forked from jeremyvillanuevar/scripting
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathacs.sp
More file actions
2137 lines (1814 loc) · 79.9 KB
/
acs.sp
File metadata and controls
2137 lines (1814 loc) · 79.9 KB
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
//////////////////////////////////////////
// Automatic Campaign Switcher for L4D2 //
// Version 2.0.0 //
// Compiled Oct 7, 2018 //
// Programmed by Rikka //
//////////////////////////////////////////
/*==================================================================================================
*** REQUIRES l4d2_mission_manager ***
This plugin was written in response to the server kicking everyone if the vote is not passed
at the end of the campaign. It will automatically switch to the appropriate map at all the
points a vote would be automatically called, by the game, to go to the lobby or play again.
ACS also includes a voting system in which people can vote for their favorite campaign/map
on a finale or scavenge map. The winning campaign/map will become the next map the server
loads.
Supported Game Modes in Left 4 Dead 2
Coop
Versus
Scavenge
Survival
Untested Game Modes in Left 4 Dead 2
Realism
Team Versus
Team Scavenge
Mutation 1-20
Community 1-5
Change Log
----- Rikka's upgraded version -----
v2.3.0 (Oct 24, 2020) - Add randomizer and other customizations to the next map voting menu
v2.2.0 (Oct 1, 2020) - Use a new SDKCall method for checking if the current map is finale or not
v2.1.1 (Dec 15, 2019) - Allow server admins to set custom finales in coop mode
v2.1.0 (Oct 19, 2019) - Applied Lux's patch, map switching is now more safe and memory leakage is eliminated
v2.0.0 (Oct 7, 2018) - Applied Lux's patch, players should see the next map voting menu anyway
v1.9.9 (Sep 5, 2018) - Transformed to new SourcePawn syntax
- Fixed incorrect reading of CVars
- Removed hardcoded map lists
- Added "sm_chmap" and "sm_chmap2" commands
- Colorized chat messages
----- Chris Pringle's original version -----
v1.2.2 (May 21, 2011) - Added message for new vote winner when a player disconnects
- Fixed the sound to play to all the players in the game
- Added a max amount of coop finale map failures cvar
- Changed the wait time for voting ad from round_start to the
player_left_start_area event
- Added the voting sound when the vote menu pops up
v1.2.1 (May 18, 2011) - Fixed mutation 15 (Versus Survival)
v1.2.0 (May 16, 2011) - Changed some of the text to be more clear
- Added timed notifications for the next map
- Added a cvar for how to advertise the next map
- Added a cvar for the next map advertisement interval
- Added a sound to help notify players of a new vote winner
- Added a cvar to enable/disable sound notification
- Added a custom wait time for coop game modes
v1.1.0 (May 12, 2011) - Added a voting system
- Added error checks if map is not found when switching
- Added a cvar for enabling/disabling voting system
- Added a cvar for how to advertise the voting system
- Added a cvar for time to wait for voting advertisement
- Added all current Mutation and Community game modes
v1.0.0 (May 5, 2011) - Initial Release
===================================================================================================*/
#include <sourcemod>
#include <sdktools>
#include <l4d2_mission_manager>
#pragma semicolon 1
#pragma newdecls required
#define PLUGIN_VERSION "v2.3.0"
#define DEBUG 1
//Define the wait time after round before changing to the next map in each game mode
#define WAIT_TIME_BEFORE_SWITCH_COOP 5.0
#define WAIT_TIME_BEFORE_SWITCH_VERSUS 5.0
#define WAIT_TIME_BEFORE_SWITCH_SCAVENGE 9.0
#define WAIT_TIME_BEFORE_SWITCH_SURVIVAL 5.0
//Define Game Modes
#define GAMEMODE_UNKNOWN LMM_GAMEMODE_UNKNOWN
#define GAMEMODE_COOP LMM_GAMEMODE_COOP
#define GAMEMODE_VERSUS LMM_GAMEMODE_VERSUS
#define GAMEMODE_SCAVENGE LMM_GAMEMODE_SCAVENGE
#define GAMEMODE_SURVIVAL LMM_GAMEMODE_SURVIVAL
#define DISPLAY_MODE_DISABLED 0
#define DISPLAY_MODE_HINT 1
#define DISPLAY_MODE_CHAT 2
#define DISPLAY_MODE_MENU 3
#define SOUND_NEW_VOTE_START "ui/Beep_SynthTone01.wav"
#define SOUND_NEW_VOTE_WINNER "ui/alert_clink.wav"
//Global Variables
LMM_GAMEMODE g_iGameMode; //Integer to store the gamemode
int g_iRoundEndCounter; //Round end event counter for versus
int g_iCoopFinaleFailureCount; //Number of times the Survivors have lost the current finale
bool g_bFinaleWon; //Indicates whether a finale has be beaten or not
//Voting Variables
bool g_bClientShownVoteAd[MAXPLAYERS + 1]; //If the client has seen the ad already
bool g_bClientVoted[MAXPLAYERS + 1]; //If the client has voted on a map
// For Coop/Versus: missionIndex of the winning campaign
// For Scavenge/Survival: uniqueID of the winning map
int g_iClientVote[MAXPLAYERS + 1]; //The value of the clients vote
// Only updated by findVoteWinner()
int g_iWinningMapIndices[MAXPLAYERS + 1]; //Winning map/campaigns' indices
int g_iWinningMapIndices_Len;
int g_iWinningMapVotes; //Winning map/campaign's number of votes, 0 = no one voted yet
//Console Variables (CVars)
ConVar g_hCVar_VotingEnabled; //Tells if the voting system is on
ConVar g_hCVar_VoteWinnerSoundEnabled; //Sound plays when vote winner changes
ConVar g_hCVar_VotingAdMode; //The way to advertise the voting system
ConVar g_hCVar_VotingAdDelayTime; //Time to wait before showing advertising
ConVar g_hCVar_NextMapMenuOptions; // Controls what maps will be shown in the next maps menu
ConVar g_hCVar_NextMapMenuExcludes; // Excludes certain maps from the next maps menu
ConVar g_hCVar_NextMapMenuOrder; // Controls the order of maps shown in the next maps menu
ConVar g_hCVar_NextMapAdMode; //The way to advertise the next map
ConVar g_hCVar_NextMapAdInterval; //Interval for ACS next map advertisement
ConVar g_hCVar_MaxFinaleFailures; //Amount of times Survivors can fail before ACS switches in coop
ConVar g_hCVar_ChMapBroadcastInterval; //The interval for advertising "!chmap"
ConVar g_hCVar_ChMapPolicy; //The behavior of "!chmap"
ConVar g_hCVar_PreventEmptyServer; //If enabled, the server automatically switch to the first available official map when no one is playing a 3-rd map
Handle g_hTimer_ChMapBroadcast;
Handle g_hTimer_CheckEmpty;
/*========================================================
######### Mission Change SDKCall Method #######
========================================================*/
bool g_bMapChanger = false;
native void L4D2_ChangeLevel(const char[] sMap);
public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
MarkNativeAsOptional("L4D2_ChangeLevel");
return APLRes_Success;
}
public void OnLibraryAdded(const char[] sName)
{
if(StrEqual(sName, "l4d2_changelevel")) {
g_bMapChanger = true;
}
}
public void OnLibraryRemoved(const char[] sName)
{
if(StrEqual(sName, "l4d2_changelevel")) {
g_bMapChanger = false;
}
}
/*=========================================================
######### Mission Cycle Data Storage #########
=========================================================*/
#define LEN_CFG_LINE 256
#define LEN_CFG_SEGMENT 128
#define CHAR_CYCLE_SEPARATOR "// 3-rd maps(Do not delete/modify this line!)"
ArrayList g_hInt_MapIndexes[COUNT_LMM_GAMEMODE];
int g_int_CyclingCount[COUNT_LMM_GAMEMODE];
ArrayList g_hStr_MyCoopFinales;
void ACS_InitLists() {
for (int gamemode=0; gamemode<COUNT_LMM_GAMEMODE; gamemode++) {
g_hInt_MapIndexes[gamemode] = new ArrayList(1);
g_int_CyclingCount[gamemode] = 0;
}
g_hStr_MyCoopFinales = new ArrayList(LEN_MAP_FILENAME);
}
void ACS_FreeLists() {
for (int gamemode=0; gamemode<COUNT_LMM_GAMEMODE; gamemode++) {
delete g_hInt_MapIndexes[gamemode];
}
delete g_hStr_MyCoopFinales;
}
ArrayList ACS_GetMissionIndexList(LMM_GAMEMODE gamemode) {
return g_hInt_MapIndexes[view_as<int>(gamemode)];
}
void ACS_SetCyclingCount(LMM_GAMEMODE gamemode, int count) {
g_int_CyclingCount[view_as<int>(gamemode)] = count;
}
// Used by the ACS
int ACS_GetCycledMissionCount(LMM_GAMEMODE gamemode) {
return g_int_CyclingCount[view_as<int>(gamemode)];
}
int ACS_GetMissionCount(LMM_GAMEMODE gamemode){
return ACS_GetMissionIndexList(gamemode).Length;
}
int ACS_GetMissionIndex(LMM_GAMEMODE gamemode, int cycleIndex) {
ArrayList missionIndexList = ACS_GetMissionIndexList(gamemode);
if (missionIndexList == null) {
return -1;
}
return missionIndexList.Get(cycleIndex);
}
int ACS_GetCycleIndex(LMM_GAMEMODE gamemode, int missionIndex) {
ArrayList missionIndexList = ACS_GetMissionIndexList(gamemode);
if (missionIndexList == null) {
return -1;
}
return missionIndexList.FindValue(missionIndex);
}
int ACS_GetFirstMapName(LMM_GAMEMODE gamemode, int cycleIndex, char[] mapname, int length){
return LMM_GetMapName(gamemode, ACS_GetMissionIndex(gamemode, cycleIndex), 0, mapname, length);
}
int ACS_GetLastMapName(LMM_GAMEMODE gamemode, int cycleIndex, char[] mapname, int length){
int iMission = ACS_GetMissionIndex(gamemode, cycleIndex);
int mapCount = LMM_GetNumberOfMaps(gamemode, iMission);
return LMM_GetMapName(gamemode, iMission, mapCount-1, mapname, length);
}
int ACS_GetCycleIndexFromMapName(LMM_GAMEMODE gamemode, const char[] mapname) {
int missionIndex = -1;
int mapIndex = LMM_FindMapIndexByName(gamemode, missionIndex, mapname);
if (mapIndex == -1)
return -1;
return ACS_GetCycleIndex(gamemode, missionIndex);
}
bool ACS_GetLocalizedMissionName(LMM_GAMEMODE gamemode, int cycleIndex, int client, char[] localizedName, int length) {
ArrayList missionIndexList = ACS_GetMissionIndexList(gamemode);
if (missionIndexList == null)
return false;
int missionIndex = missionIndexList.Get(cycleIndex);
return LMM_GetMissionLocalizedName(gamemode, missionIndex, localizedName, length, client) > 0;
}
/*====================================================
######### Mission Cycle Parsing #########
====================================================*/
// Get the path of the mission cycle file, max length of char[] path = PLATFORM_MAX_PATH
// Return the actual length of the path, -1 if failed
int GetMissionCycleFilePath(LMM_GAMEMODE gamemode, char[] path) {
switch (gamemode) {
case LMM_GAMEMODE_COOP: {
return BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/missioncycle.coop.txt");
}
case LMM_GAMEMODE_VERSUS: {
return BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/missioncycle.versus.txt");
}
case LMM_GAMEMODE_SCAVENGE: {
return BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/missioncycle.scavenge.txt");
}
case LMM_GAMEMODE_SURVIVAL: {
return BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "configs/missioncycle.survival.txt");
}
default: {
return -1;
}
}
}
bool HasMissionCycleFile(LMM_GAMEMODE gamemode) {
char path[PLATFORM_MAX_PATH];
if (GetMissionCycleFilePath(gamemode, path) == -1)
return false;
return FileExists(path);
}
File OpenMissionCycleFile(LMM_GAMEMODE gamemode, const char[] mode) {
char path[PLATFORM_MAX_PATH];
if (GetMissionCycleFilePath(gamemode, path) == -1)
return null;
return OpenFile(path, mode);
}
void PopulateDefaultMissionCycle(LMM_GAMEMODE gamemode, File missionCycleFile) {
switch (gamemode) {
case LMM_GAMEMODE_COOP, LMM_GAMEMODE_VERSUS: {
missionCycleFile.WriteLine("L4D2C1");
missionCycleFile.WriteLine("L4D2C2");
missionCycleFile.WriteLine("L4D2C3");
missionCycleFile.WriteLine("L4D2C4");
missionCycleFile.WriteLine("L4D2C5");
missionCycleFile.WriteLine("L4D2C6");
missionCycleFile.WriteLine("L4D2C7");
missionCycleFile.WriteLine("L4D2C8");
missionCycleFile.WriteLine("L4D2C9");
missionCycleFile.WriteLine("L4D2C10");
missionCycleFile.WriteLine("L4D2C11");
missionCycleFile.WriteLine("L4D2C12");
missionCycleFile.WriteLine("L4D2C13");
missionCycleFile.WriteLine("L4D2C14");
}
case LMM_GAMEMODE_SCAVENGE: {
missionCycleFile.WriteLine("L4D2C1");
missionCycleFile.WriteLine("L4D2C2");
missionCycleFile.WriteLine("L4D2C3");
missionCycleFile.WriteLine("L4D2C4");
missionCycleFile.WriteLine("L4D2C5");
missionCycleFile.WriteLine("L4D2C6");
missionCycleFile.WriteLine("L4D2C7");
missionCycleFile.WriteLine("L4D2C8");
missionCycleFile.WriteLine("L4D2C10");
missionCycleFile.WriteLine("L4D2C11");
missionCycleFile.WriteLine("L4D2C12");
missionCycleFile.WriteLine("L4D2C14");
}
case LMM_GAMEMODE_SURVIVAL: {
missionCycleFile.WriteLine("L4D2C1");
missionCycleFile.WriteLine("L4D2C2");
missionCycleFile.WriteLine("L4D2C3");
missionCycleFile.WriteLine("L4D2C4");
missionCycleFile.WriteLine("L4D2C5");
missionCycleFile.WriteLine("L4D2C6");
missionCycleFile.WriteLine("L4D2C7");
missionCycleFile.WriteLine("L4D2C8");
missionCycleFile.WriteLine("L4D2C14");
}
}
}
void LoadMissionList(LMM_GAMEMODE gamemode) {
ArrayList missionIndexList = ACS_GetMissionIndexList(gamemode);
char buffer[LEN_CFG_LINE];
char buffer_split[3][LEN_CFG_SEGMENT];
File missionCycleFile;
char missionName[LEN_MISSION_NAME];
char gamemodeName[LEN_GAMEMODE_NAME];
LMM_GamemodeToString(gamemode, gamemodeName, sizeof(gamemodeName));
// Create default mission cycle file if not existed yet
if (!HasMissionCycleFile(gamemode)){
missionCycleFile = OpenMissionCycleFile(gamemode, "w+");
missionCycleFile.WriteLine("// Do not delete this line! format: <Mission Name(see txt files in missions.cache folder)>");
PopulateDefaultMissionCycle(gamemode, missionCycleFile);
missionCycleFile.WriteLine(CHAR_CYCLE_SEPARATOR);
delete missionCycleFile;
}
missionCycleFile = OpenMissionCycleFile(gamemode, "r");
missionCycleFile.ReadLine(buffer, sizeof(buffer));
while(!missionCycleFile.EndOfFile() && missionCycleFile.ReadLine(buffer, sizeof(buffer))) {
ReplaceString(buffer, sizeof(buffer), "\n", "");
TrimString(buffer);
if (StrContains(buffer, "//") == 0) {
if (StrContains(buffer, CHAR_CYCLE_SEPARATOR) == 0) {
ACS_SetCyclingCount(gamemode, missionIndexList.Length);
}
// Ignore comments
} else {
int numOfStrings = ExplodeString(buffer, ",", buffer_split, LEN_CFG_LINE, LEN_CFG_SEGMENT);
TrimString(buffer_split[0]); // Mission name
if (numOfStrings > 1) {
// For future use
}
int iMission = LMM_FindMissionIndexByName(gamemode, buffer_split[0]);
if (iMission >= 0) { // The mission is valid
missionIndexList.Push(iMission);
} else {
LogError("Mission \"%s\" (Gamemode: %s) is not in the mission cache or no longer exists!\n", buffer_split[0], gamemodeName);
}
}
}
delete missionCycleFile;
// Missions in missionIndexList are in the cyclic order and all valid
// But l4d2_mission_manager may have some new missions
// Then append new missions to the end of mission cycle and store the new mission cycle!
missionCycleFile = OpenMissionCycleFile(gamemode, "a");
for (int iMission=0; iMission<LMM_GetNumberOfMissions(gamemode); iMission++) {
if (missionIndexList.FindValue(iMission) < 0) {
// Found a new mission
LMM_GetMissionName(gamemode, iMission, missionName, sizeof(missionName));
LogMessage("Found new %s mission \"%s\" !", gamemodeName, missionName);
missionCycleFile.WriteLine(missionName);
}
}
delete missionCycleFile;
// Mission list is complete and finalized
}
void DumpMissionInfo(int client, LMM_GAMEMODE gamemode) {
char gamemodeName[LEN_GAMEMODE_NAME];
LMM_GamemodeToString(gamemode, gamemodeName, sizeof(gamemodeName));
ArrayList missionIndexList = ACS_GetMissionIndexList(gamemode);
int missionCount = ACS_GetMissionCount(gamemode);
char missionName[LEN_MISSION_NAME];
char firstMap[LEN_MAP_FILENAME];
char lastMap[LEN_MAP_FILENAME];
char localizedName[LEN_MISSION_NAME];
ReplyToCommand(client, "Gamemode = %s (%d missions, %d in cycle)", gamemodeName, missionCount, ACS_GetCycledMissionCount(gamemode));
for (int cycleIndex=0; cycleIndex<missionCount; cycleIndex++) {
int iMission = missionIndexList.Get(cycleIndex);
int mapCount = LMM_GetNumberOfMaps(gamemode, iMission);
LMM_GetMissionName(gamemode, iMission, missionName, sizeof(missionName));
ACS_GetFirstMapName(gamemode, cycleIndex, firstMap, sizeof(firstMap));
ACS_GetLastMapName(gamemode, cycleIndex, lastMap, sizeof(lastMap));
if (ACS_GetLocalizedMissionName(gamemode, cycleIndex, client, localizedName, sizeof(localizedName))) {
ReplyToCommand(client, "%d.%s (%s) = %s -> %s (%d maps)", cycleIndex+1 , localizedName, missionName, firstMap, lastMap, mapCount);
} else {
ReplyToCommand(client, "%d.%s <Missing localization> = %s -> %s (%d maps)", cycleIndex+1, missionName, firstMap, lastMap, mapCount);
}
}
ReplyToCommand(client, "-------------------");
}
/*====================================================
######### Custom Coop Finale List Parsing #########
====================================================*/
bool LoadCustomCoopFinaleList() {
char path[PLATFORM_MAX_PATH];
int path_len = BuildPath(Path_SM, path, sizeof(path), "configs/finale.coop.txt");
if (path_len == -1)
return false;
File finaleListFile;
if (!FileExists(path)){
finaleListFile = OpenFile(path, "w+");
if (finaleListFile == null)
return false;
finaleListFile.WriteLine("// The following maps will be treated as finale maps in Coop mode. Example: c1m1_hotel. Do not delete this line!");
delete finaleListFile;
return true;
}
finaleListFile = OpenFile(path, "r");
if (finaleListFile == null)
return false;
// Start parsing the file
char buffer[128];
finaleListFile.ReadLine(buffer, sizeof(buffer));
while(!finaleListFile.EndOfFile() && finaleListFile.ReadLine(buffer, sizeof(buffer))) {
ReplaceString(buffer, sizeof(buffer), "\n", "");
TrimString(buffer);
int missionIndex;
int iMap = LMM_FindMapIndexByName(LMM_GAMEMODE_COOP, missionIndex, buffer);
if (iMap > -1) {
g_hStr_MyCoopFinales.PushString(buffer);
} else {
LogError("Map \"%s\" (From finale.coop.txt) is invalid!\n", buffer);
}
}
delete finaleListFile;
return true;
}
void DumpCustomCoopFinaleList(int client) {
char buffer[LEN_MAP_FILENAME];
int len = GetArraySize(g_hStr_MyCoopFinales);
ReplyToCommand(client, "%d valid custom coop finales from finale.coop.txt:", len);
for (int i=0; i<len; i++) {
g_hStr_MyCoopFinales.GetString(i, buffer, sizeof(buffer));
ReplyToCommand(client, "%d. %s", i+1, buffer);
}
}
/*===========================================
######### Menu Systems #########
===========================================*/
#define MMC_ITEM_LEN_INFO 16
#define MMC_ITEM_LEN_NAME 16
#define MMC_ITEM_IDONTCARE_TEXT "I dont care"
#define MMC_ITEM_ALLMAPS_TEXT "All maps"
#define MMC_ITEM_MISSION_TEXT "Mission"
#define MMC_ITEM_MAP_TEXT "Map"
bool ShowMissionChooser(int iClient, bool isMap, bool isVote, int prevLevelMenuPage=0) {
if(iClient < 1 || IsClientInGame(iClient) == false || IsFakeClient(iClient) == true)
return false;
//Create the menu
Menu chooser = CreateMenu(MissionChooserMenuHandler, MenuAction_Select | MenuAction_DisplayItem | MenuAction_End);
// Setup the title and "I dont care" option
if (isMap) {
chooser.SetTitle("%T", "Choose a Map", iClient);
if (isVote) {
chooser.AddItem(MMC_ITEM_IDONTCARE_TEXT, "N/A");
}
chooser.AddItem(MMC_ITEM_ALLMAPS_TEXT, "N/A");
} else {
chooser.SetTitle("%T", "Choose a Mission", iClient);
if (isVote) {
chooser.AddItem(MMC_ITEM_IDONTCARE_TEXT, "N/A");
}
}
// Determine the map list shown in the menu
char curMapName[LEN_MAP_FILENAME];
GetCurrentMap(curMapName,sizeof(curMapName)); //Get the current map from the game
int curCycleIndex = ACS_GetCycleIndexFromMapName(g_iGameMode, curMapName); // -1 if not found
int cycledCount = ACS_GetCycledMissionCount(g_iGameMode);
int cycleIndices_maxlen = ACS_GetMissionCount(g_iGameMode);
int[] cycleIndices = new int[cycleIndices_maxlen];
int cycleIndices_len = 0;
// Go through each mission, add valid options to int[] cycleIndices
for(int cycleIndex = 0; cycleIndex < cycleIndices_maxlen; cycleIndex++) {
if (isVote) {
// Exclude the current map (g_hCVar_NextMapMenuExcludes)
if (g_hCVar_NextMapMenuExcludes.IntValue == 1 && curCycleIndex == cycleIndex) continue;
// Exclude addon maps (g_hCVar_NextMapMenuOptions)
if (g_hCVar_NextMapMenuOptions.IntValue == 1 && !(cycleIndex < cycledCount)) continue;
// Exclude maps with different types (g_hCVar_NextMapMenuOptions)
if (g_hCVar_NextMapMenuOptions.IntValue == 2 && (cycleIndex < cycledCount) != (curCycleIndex < cycledCount)) continue;
}
cycleIndices[cycleIndices_len] = cycleIndex;
cycleIndices_len++;
}
// Randomize (g_hCVar_NextMapMenuOrder)
if (isVote && g_hCVar_NextMapMenuOrder.IntValue == 1) {
for (int i = 0; i<cycleIndices_len; i++) {
int iSwap = i + GetRandomInt(0, cycleIndices_len-i-1);
int temp = cycleIndices[i];
cycleIndices[i] = cycleIndices[iSwap];
cycleIndices[iSwap] = temp;
}
}
char menuName[20];
for (int i = 0; i<cycleIndices_len; i++) {
IntToString(cycleIndices[i], menuName, sizeof(menuName));
chooser.AddItem(MMC_ITEM_MISSION_TEXT, menuName);
}
//Add an exit button
chooser.ExitButton = true;
//And finally, show the menu to the client
chooser.DisplayAt(iClient, prevLevelMenuPage, MENU_TIME_FOREVER);
return true;
}
public int MissionChooserMenuHandler(Menu menu, MenuAction action, int client, int item) {
if (action == MenuAction_End) {
delete menu;
return 0;
}
char menuInfo[MMC_ITEM_LEN_INFO];
char menuName[MMC_ITEM_LEN_NAME];
char localizedName[LEN_LOCALIZED_NAME];
// Change the map to the selected item.
if(action == MenuAction_Select) {
if (item < 0) { // Not a valid map option
return 0;
}
// Find out the current menu mode
menu.GetItem(0, menuInfo, sizeof(menuInfo));
if (StrEqual(menuInfo, MMC_ITEM_IDONTCARE_TEXT, false)) {
// Voting mode
if (item == 0) {
// "I dont care" is selected
VoteMenuHandler(client, true, -1, -1);
//PrintToServer("\"I dont care\" is selected");
return 0;
} else {
// Other vote mode menu items
menu.GetItem(1, menuInfo, sizeof(menuInfo));
if (StrEqual(menuInfo, MMC_ITEM_ALLMAPS_TEXT, false)) {
// Voting for a map
if (item == 1) {
// "All map" is selected, prepare map list for all missions
ShowMapChooser(client, true, -1, menu.Selection);
} else {
// A mission is selected, prepare a map list for the selected mission
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
int cycleIndex = StringToInt(menuName);
ShowMapChooser(client, true, cycleIndex, menu.Selection);
}
} else {
// Voting for a mission
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
int cycleIndex = StringToInt(menuName);
int missionIndex = ACS_GetMissionIndex(g_iGameMode, cycleIndex);
VoteMenuHandler(client, false, missionIndex, -1);
//ACS_GetLocalizedMissionName(g_iGameMode, cycleIndex, client, localizedName, sizeof(localizedName));
//PrintToServer("ACS: a mission \"%s\" is chosen", localizedName);
}
}
} else {
// Chmap mode
if (StrEqual(menuInfo, MMC_ITEM_ALLMAPS_TEXT, false)) {
if (item == 0) {
// "All map" is selected, prepare map list for all missions
ShowMapChooser(client, false, -1, menu.Selection);
} else {
// A mission is selected, prepare a map list for the selected mission
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
int cycleIndex = StringToInt(menuName);
ShowMapChooser(client, false, cycleIndex, menu.Selection);
}
// Browse map list
} else {
if (IsVoteInProgress()) {
ReplyToCommand(client, "\x03[ACS]\x04 %t", "Vote in Progress");
return 0;
}
// A mission is chosen
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
int cycleIndex = StringToInt(menuName);
ShowChmapVoteToAll(ACS_GetMissionIndex(g_iGameMode, cycleIndex), -1);
}
}
} else if (action == MenuAction_DisplayItem) {
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
if (StrEqual(menuInfo, MMC_ITEM_MISSION_TEXT, false)) {
int cycleIndex = StringToInt(menuName);
// Localize mission name
ACS_GetLocalizedMissionName(g_iGameMode, cycleIndex, client, localizedName, sizeof(localizedName));
} else {
// Localize other menu items
Format(localizedName, sizeof(localizedName), "%T", menuInfo, client);
}
RedrawMenuItem(localizedName);
}
return 0;
}
bool ShowMapChooser(int iClient, bool isVote, int cycleIndex, int prevLevelMenuPage) {
if(iClient < 1 || IsClientInGame(iClient) == false || IsFakeClient(iClient) == true)
return false;
char menuInfo[MMC_ITEM_LEN_INFO];
// Use menu info to store value of isVote and prevLevelMenuPage
Format(menuInfo, sizeof(menuInfo), "%d,%d", (isVote ? 1 : 0), prevLevelMenuPage);
//Create the menu
Menu chooser = CreateMenu(MapChooserMenuHandler, MenuAction_Select | MenuAction_DisplayItem | MenuAction_End | MenuAction_Cancel);
chooser.SetTitle("%T", "Choose a Map", iClient);
char menuName[MMC_ITEM_LEN_NAME];
if (cycleIndex < 0) {
// Show all maps at once
for (cycleIndex = 0; cycleIndex<ACS_GetMissionCount(g_iGameMode); cycleIndex++) {
int missionIndex = ACS_GetMissionIndex(g_iGameMode, cycleIndex);
for (int mapIndex=0; mapIndex<LMM_GetNumberOfMaps(g_iGameMode, missionIndex); mapIndex++) {
Format(menuName, sizeof(menuName), "%d,%d", missionIndex, mapIndex);
chooser.AddItem(menuInfo, menuName);
}
}
} else {
int missionIndex = ACS_GetMissionIndex(g_iGameMode, cycleIndex);
for (int mapIndex=0; mapIndex<LMM_GetNumberOfMaps(g_iGameMode, missionIndex); mapIndex++) {
Format(menuName, sizeof(menuName), "%d,%d", missionIndex, mapIndex);
chooser.AddItem(menuInfo, menuName);
}
}
//Add an exitBack button
chooser.ExitBackButton = true;
//And finally, show the menu to the client
chooser.Display(iClient, MENU_TIME_FOREVER);
//Play a sound to indicate that the user can vote on a map
EmitSoundToClient(iClient, SOUND_NEW_VOTE_START);
return true;
}
public int MapChooserMenuHandler(Menu menu, MenuAction action, int client, int item) {
if (action == MenuAction_End) {
delete menu;
return 0;
}
char menuInfo[MMC_ITEM_LEN_INFO];
char menuName[MMC_ITEM_LEN_NAME];
char localizedName[LEN_LOCALIZED_NAME];
char buffer_split[3][MMC_ITEM_LEN_NAME];
if (action == MenuAction_Cancel) {
if (item == MenuCancel_ExitBack) {
// Open main menu
menu.GetItem(0, menuInfo, sizeof(menuInfo));
ExplodeString(menuInfo, ",", buffer_split, 3, MMC_ITEM_LEN_NAME);
bool isVote = StringToInt(buffer_split[0]) == 1;
int prevLevelMenuPage = StringToInt(buffer_split[1]);
ShowMissionChooser(client, true, isVote, prevLevelMenuPage);
}
} else if (action == MenuAction_DisplayItem) {
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
ExplodeString(menuName, ",", buffer_split, 3, MMC_ITEM_LEN_NAME);
int missionIndex = StringToInt(buffer_split[0]);
int mapIndex = StringToInt(buffer_split[1]);
LMM_GetMapLocalizedName(g_iGameMode, missionIndex, mapIndex, localizedName, sizeof(localizedName), client);
RedrawMenuItem(localizedName);
} else if (action == MenuAction_Select) {
if (item < 0) { // Not a valid map option
return 0;
}
if (IsVoteInProgress()) {
ReplyToCommand(client, "\x03[ACS]\x04 %t", "Vote in Progress");
return 0;
}
menu.GetItem(item, menuInfo, sizeof(menuInfo), _, menuName, sizeof(menuName));
ExplodeString(menuName, ",", buffer_split, 3, MMC_ITEM_LEN_NAME);
int missionIndex = StringToInt(buffer_split[0]);
int mapIndex = StringToInt(buffer_split[1]);
if (StrEqual(menuInfo, "1", false)) {
// Vote mode
VoteMenuHandler(client, false, missionIndex, mapIndex);
} else {
// Chmap mode
ShowChmapVoteToAll(missionIndex, mapIndex);
}
}
return 0;
}
bool ShowChmapVoteToAll(int missionIndex, int mapIndex) {
Menu menuVote = CreateMenu(ChampVoteHandler,
MenuAction_Display|MenuAction_DisplayItem|MenuAction_VoteCancel|MenuAction_VoteEnd|MenuAction_End);
menuVote.SetTitle("To be translated");
char menuInfo[MMC_ITEM_LEN_INFO];
IntToString(missionIndex, menuInfo, sizeof(menuInfo));
menuVote.AddItem(menuInfo, "Yes");
IntToString(mapIndex, menuInfo, sizeof(menuInfo));
menuVote.AddItem(menuInfo, "No");
menuVote.ExitButton = false;
menuVote.DisplayVoteToAll(20);
}
public int ChampVoteHandler(Menu menu, MenuAction action, int param1, int param2) {
if (action == MenuAction_Display) {
// Localize title
char localizedName[LEN_LOCALIZED_NAME];
char menuInfo[MMC_ITEM_LEN_INFO];
menu.GetItem(0, menuInfo, sizeof(menuInfo));
int missionIndex = StringToInt(menuInfo);
menu.GetItem(1, menuInfo, sizeof(menuInfo));
int mapIndex = StringToInt(menuInfo);
if (mapIndex < 0) {
LMM_GetMissionLocalizedName(g_iGameMode, missionIndex, localizedName, sizeof(localizedName), param1);
} else {
LMM_GetMapLocalizedName(g_iGameMode, missionIndex, mapIndex, localizedName, sizeof(localizedName), param1);
}
char buffer[255];
Format(buffer, sizeof(buffer), "%T", "Change Map To", param1, localizedName);
Panel panel = view_as<Panel>(param2);
panel.SetTitle(buffer);
} else if (action == MenuAction_DisplayItem) {
char menuName[MMC_ITEM_LEN_NAME];
char buffer[MMC_ITEM_LEN_NAME];
menu.GetItem(param2, "", 0, _, menuName, sizeof(menuName));
Format(buffer, sizeof(buffer), "%T", menuName, param1); // param1 = clientIndex
RedrawMenuItem(buffer);
} else if (action == MenuAction_VoteCancel && param1 == VoteCancel_NoVotes) {
PrintToChatAll("\x03[ACS]\x04 %t", "No Votes Cast");
} else if (action == MenuAction_VoteEnd) {
// param1: The winning item, param2: vote result
int votes, totalVotes; // totalVotes != numOfPlayers
GetMenuVoteInfo(param2, votes, totalVotes);
int playerCount = 0;
for(int iPlayer = 1; iPlayer <= MaxClients; iPlayer++)
if(IsClientInGame(iPlayer) && !IsFakeClient(iPlayer))
playerCount++;
int abstention = playerCount - totalVotes;
int yesVotes, noVotes;
if (param1 == 1) { // "No" is winning
yesVotes = totalVotes - votes; // Reverse the votes to be in relation to the Yes option.
noVotes = votes;
} else { // "Yes" is winning
yesVotes = votes;
noVotes = totalVotes - votes;
}
float percent, limit;
if (g_hCVar_ChMapPolicy.IntValue == 1) {
// Treat abstention as NO
percent = float(yesVotes) / float(playerCount);
} else if (g_hCVar_ChMapPolicy.IntValue == 2) {
// Treat abstention as YES, highly not recommended
percent = float(yesVotes + abstention) / float(playerCount);
} else {
// Disabled
return 0;
}
ConVar limitConVar = FindConVar("sm_vote_map");
if (limitConVar == null) {
limit = 0.6;
} else {
limit = limitConVar.FloatValue;
}
// A multi-argument vote is "always successful", but have to check if its a Yes/No vote.
if (percent < limit) {
LogAction(-1, -1, "Vote failed.");
PrintToChatAll("\x03[ACS]\x04 %t \x01[%d,%d,%d]", "Vote Failed", RoundToNearest(100.0*limit), RoundToNearest(100.0*percent), playerCount, yesVotes, noVotes, abstention);
} else {
PrintToChatAll("\x03[ACS]\x04 %t \x01[%d,%d,%d]", "Vote Successful", RoundToNearest(100.0*percent), playerCount, yesVotes, noVotes, abstention);
char menuInfo[MMC_ITEM_LEN_INFO];
menu.GetItem(0, menuInfo, sizeof(menuInfo));
int missionIndex = StringToInt(menuInfo);
menu.GetItem(1, menuInfo, sizeof(menuInfo));
int mapIndex = StringToInt(menuInfo);
char colorizedName[LEN_MISSION_NAME];
char localizedName[LEN_MISSION_NAME];
char mapName[LEN_MAP_FILENAME];
if (mapIndex < 0) {
// Vote for mission, switch to its first map
LMM_GetMapName(g_iGameMode, missionIndex, 0, mapName, sizeof(mapName));
for (int client = 1; client <= MaxClients; client++) {
if (IsClientInGame(client)) {
LMM_GetMissionLocalizedName(g_iGameMode, missionIndex, localizedName, sizeof(localizedName), client);
Format(colorizedName, sizeof(colorizedName), "\x04%s\x01", localizedName);
PrintToChat(client,"\x03[ACS]\x01 %t\x01", "Mission is now winning the vote", colorizedName);
}
}
} else {
// Vote for a map, switch to that map
LMM_GetMapName(g_iGameMode, missionIndex, mapIndex, mapName, sizeof(mapName));
for (int client = 1; client <= MaxClients; client++) {
if (IsClientInGame(client)) {
LMM_GetMapLocalizedName(g_iGameMode, missionIndex, mapIndex, localizedName, sizeof(localizedName), client);
Format(colorizedName, sizeof(colorizedName), "\x04%s\x01", localizedName);
PrintToChat(client,"\x03[ACS]\x01 %t", "Map is now winning the vote", colorizedName);
}
}
}
Format(colorizedName, sizeof(colorizedName), "\x04%s\x01", mapName);
PrintToChatAll("\x03[ACS]\x01 %t", "Changing map", colorizedName);
CreateChangeMapTimer(mapName);
}
} else if (action == MenuAction_End) {
delete menu;
}
return 0;
}
void CreateChangeMapTimer(const char[] mapName) {
float delay = 5.0;
switch (g_iGameMode) {
case LMM_GAMEMODE_COOP: {delay=WAIT_TIME_BEFORE_SWITCH_COOP;}
case LMM_GAMEMODE_VERSUS: {delay=WAIT_TIME_BEFORE_SWITCH_VERSUS;}
case LMM_GAMEMODE_SCAVENGE: {delay=WAIT_TIME_BEFORE_SWITCH_SCAVENGE;}
case LMM_GAMEMODE_SURVIVAL: {delay=WAIT_TIME_BEFORE_SWITCH_SURVIVAL;}
}
DataPack dp;
CreateDataTimer(delay, Timer_ChangeMap, dp);
dp.WriteString(mapName);
}
public Action Timer_ChangeMap(Handle timer, DataPack dp) {
char mapName[LEN_MAP_FILENAME];
dp.Reset();
dp.ReadString(mapName, sizeof(mapName));
if(g_bMapChanger)
{
L4D2_ChangeLevel(mapName);
}
else
{
ShutDownScriptedMode();
ForceChangeLevel(mapName, "sm_votemap Result");
}
return Plugin_Stop;
}
public void OnAllPluginsLoaded()
{
if (!LibraryExists("l4d2_mission_manager")) {
SetFailState("l4d2_mission_manager was not found.");
}
ACS_InitLists();
LoadMissionList(LMM_GAMEMODE_COOP);
LoadMissionList(LMM_GAMEMODE_VERSUS);
LoadMissionList(LMM_GAMEMODE_SCAVENGE);
LoadMissionList(LMM_GAMEMODE_SURVIVAL);
DumpMissionInfo(0, LMM_GAMEMODE_COOP);
DumpMissionInfo(0, LMM_GAMEMODE_VERSUS);
DumpMissionInfo(0, LMM_GAMEMODE_SCAVENGE);
DumpMissionInfo(0, LMM_GAMEMODE_SURVIVAL);
LoadCustomCoopFinaleList();
DumpCustomCoopFinaleList(0);
g_bMapChanger = LibraryExists("l4d2_changelevel");
}
public void OnPluginEnd() {
ACS_FreeLists();
}
/*======================================================================================
##################### P L U G I N I N F O ####################
======================================================================================*/
public Plugin myinfo = {
name = "Automatic Campaign Switcher (ACS)",
author = "Rikka0w0, Chris Pringle",
description = "Automatically switches to the next campaign when the previous campaign is over",
version = PLUGIN_VERSION,
url = "http://forums.alliedmods.net/showthread.php?t=308708"
}
/*======================================================================================
################# O N P L U G I N S T A R T #################
======================================================================================*/
public void OnPluginStart() {
LoadTranslations("acs.phrases");
LoadTranslations("common.phrases");
LoadTranslations("basevotes.phrases");
char game_name[64];
GetGameFolderName(game_name, sizeof(game_name));
if (!StrEqual(game_name, "left4dead", false) && !StrEqual(game_name, "left4dead2", false)) {
SetFailState("Use this in Left 4 Dead or Left 4 Dead 2 only.");
}
//Create custom console variables
CreateConVar("acs_version", PLUGIN_VERSION, "Version of Automatic Campaign Switcher (ACS) on this server", FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD);
g_hCVar_VotingEnabled = CreateConVar("acs_voting_system_enabled", "1", "Enables players to vote for the next map or campaign [0 = DISABLED, 1 = ENABLED]", _, true, 0.0, true, 1.0);
g_hCVar_VoteWinnerSoundEnabled = CreateConVar("acs_voting_sound_enabled", "1", "Determines if a sound plays when a new map is winning the vote [0 = DISABLED, 1 = ENABLED]", _, true, 0.0, true, 1.0);
g_hCVar_VotingAdMode = CreateConVar("acs_voting_ad_mode", "3", "Sets how to advertise voting at the start of the map [0 = DISABLED, 1 = HINT TEXT, 2 = CHAT TEXT, 3 = OPEN VOTE MENU]\n * Note: This is only displayed once during a finale or scavenge map *", _, true, 0.0, true, 3.0);
g_hCVar_VotingAdDelayTime = CreateConVar("acs_voting_ad_delay_time", "10.0", "Time, in seconds, to wait after survivors leave the start area to advertise voting as defined in acs_voting_ad_mode\n * Note: If the server is up, changing this in the .cfg file takes two map changes before the change takes place *", _, true, 0.1, false);
g_hCVar_NextMapMenuOptions = CreateConVar("acs_next_map_menu_options", "0", "Controls the maps shown in the next map voting menu [0 = Official and addon maps(Default), 1 = Official maps only, 2 = Depending on the type of the current map ]");
g_hCVar_NextMapMenuExcludes = CreateConVar("acs_next_map_menu_excludes", "0", "Excludes certain map(s) from the map voting menu [0 = Nothing(Default), 1 = Current map ]");
g_hCVar_NextMapMenuOrder= CreateConVar("acs_next_map_menu_order", "0", "Controls the order of maps shown in the next map voting menu [0 = Official then addon maps(Default), 1 = Random ]");
g_hCVar_NextMapAdMode = CreateConVar("acs_next_map_ad_mode", "2", "Sets how the next campaign/map is advertised during a finale or scavenge map [0 = DISABLED, 1 = HINT TEXT, 2 = CHAT TEXT]", _, true, 0.0, true, 2.0);
g_hCVar_NextMapAdInterval = CreateConVar("acs_next_map_ad_interval", "60.0", "The time, in seconds, between advertisements for the next campaign/map on finales and scavenge maps", _, true, 60.0, false);
g_hCVar_MaxFinaleFailures = CreateConVar("acs_max_coop_finale_failures", "0", "The amount of times the survivors can fail a finale in Coop before it switches to the next campaign [0 = INFINITE FAILURES]", _, true, 0.0, false);
g_hCVar_ChMapPolicy = CreateConVar("acs_chmap_policy", "0", "Enable or disable \"!chmap\" and \"!chmap2\" commands\n 0 - Disabled (by default) \n 1 - Enabled, abstention is treated as NO \n 2 - Enabled, abstention is treated as YES. \n Option 2 can be used by trolls and griefers to force a map change as long as nobody else votes!");
g_hCVar_ChMapBroadcastInterval = CreateConVar("acs_chmap_broadcast_interval", "180.0", "Controls the frequency of the \"!chmap\" advertisement, in second.");
g_hCVar_PreventEmptyServer = CreateConVar("acs_prevent_empty_server", "1", "If enabled, the server automatically switch to the first available official map when no one is playing a 3-rd map [0 = DISABLED, 1 = ENABLED]", _, true, 0.0, true, 1.0);
//Hook console variable changes