· 6 years ago · Sep 29, 2019, 01:02 PM
1/*
2-----------------------------------------------------------------------------
3LEFT 4 DEAD STATS - SOURCEMOD PLUGIN
4-----------------------------------------------------------------------------
5Initial Code for Left 4 Dead 1 Written By msleeper (c) 2009
6-----------------------------------------------------------------------------
7Major Part Of The Code Written By muukis (c) 2010-2012
8-----------------------------------------------------------------------------
9This is a ranking/stat tracking system for Left 4 Dead Co-op. It will track
10certain actions, such as giving a teammate Pills or rescuing them from a
11Hunter, as well as tracking kills of the types of Infected. The goal of the
12stats is both to rank players against one another, but also to promote
13teamwork by awarding more points for completing team-specific goals rather
14than simply basing on kills.
15
16You can access your basic rank information by typing "rank" or "/rank" in
17the chat area. You can access the Top 10 Players by typing "top10" or
18"/top10" in the chat area.
19
20The plugin ONLY works in Co-op mode, in every difficulty but Easy. Stats
21will automatically stop tracking if any of these conditions are met:
22 . Game is in Easy difficulty
23 . sv_cheats is set to "1"
24 . There are not enough Human players, as determined by a Cvar
25 . The Database connection has failed
26
27The webstats portion provides more in-depth stat information, both for
28individual players as well as the server as a whole, with full campaign and
29map stat info. More information about webstats can be found in the webstats
30ZIP file.
31
32Special thanks to DopeFish, Icettiflow, jasonfrog, and liv3d for helping me
33beta test prior to full public release.
34
35Thank you and enjoy!
36- msleeper
37-----------------------------------------------------------------------------
38A little notice on my behalf as well:
39
40I'd like to send my special thanks to Harm and Titan for the testing done
41when adding support to L4D2. This would not have been possible to accomplish
42in this timeframe, if it wasn't for them! Planetsize thanks guys!
43
44- muukis
45-----------------------------------------------------------------------------
46To Do List (Minor)
47 . Fix minor bug with Campaign tracking
48 . Add multilingual support
49
50To Do List (Major)
51 . Add "Squad" system
52 . Add grace period and cooldown to Friendly Fire
53 . Add achievement system
54 . Add Survival support
55 . Versus statistics
56 . Smoker pull award (length)
57 . Tank incapacitated or killed all players
58-----------------------------------------------------------------------------
59Version History
60
61-- 0.1.0 (1/8/09)
62 . Initial closed beta release!
63
64-- 0.1.1 (1/9/09)
65 . Silenced plugin disable alerts, except "not enough Human players" alert.
66 . Removed misc debug message.
67 . Fixed misc error log messages.
68
69-- 0.1.2 (1/12/09)
70 . Testing new interstitial SQL method for Common Infected kill tracking.
71 Instead of sending a SQL transaction after each kill, only send SQL during
72 the update period when Common Infected points are displayed. The high
73 amount of SQL traffic causes noticible lag during high combat periods,
74 such as when a mob attacks.
75
76-- 0.1.6 (1/13/09)
77 . Fully implimented interstitial SQL update for Common Infected. Added
78 check to send update when a player disconnects, so no points are lost if
79 they disconnect between interstitial updates.
80 . Cleaned up code a bit.
81 . Improved player name sanitation.
82 . Changed new players playtime to init at 1 instead of 0.
83 . Changed point amounts from static values to Cvar values.
84 . Added Cvar to control how stats messages are displayed to players:
85 0 = Stats messages are off
86 1 = Messages sent to the player who earned them only
87 2 = Same as 1, but Headshots on Special Infected are globally anounced
88 3 = All messages are global. Warning: This is VERY annoying!
89 . Added Cvar to control whether Medkit points are given based on the amount
90 healed, or a static amount set by Cvar. Amount healed is 0.5x in Normal,
91 1x in Advanced, and 2x in Expert.
92 . Added check to disable stats if the Database connection has failed.
93
94-- 0.1.8 (1/15/09)
95 . Further cleaned up code.
96 . Optimized UTF8 character support.
97 . Removed log message on successful database connection.
98 . Added threaded query to player inserting, to check if the player already
99 exists and if so, don't attempt to INSERT IGNORE them.
100 . Reformatted rank panels.
101 . Added Cvar to list community site for more information in "rank" panel.
102 . Removed table generation from the plugin. This will be handled by a
103 setup script provided with webstats.
104
105-- 0.1.9 (1/16/09)
106 . Changed all updates to threaded queries, to fix lag caused by updates and
107 server timeouts in rare cases.
108
109-- 1.0.0 (1/18/09)
110 . Initial public release!
111
112-- 1.1.0 (1/25/09)
113 . Fixed change in update/Common Infected announcement timer not obeying
114 changes to the cvar, except when in the config file and the plugin/server
115 is restarted.
116 . Fixed team chat not picking up chat triggers.
117 . Added invalid database connection checking to rank/top10 panel display.
118 . Fixed bug where players would be inserted into the database, but their
119 user data would not get updated and they would appear blank.
120 . Removed plugin version from showing up in the config file.
121 . Removed "Not enough Humans" message when in Versus.
122 . Made rank panel display after client connect at the start of each map,
123 and added cvar to enable/disable this.
124 . Made "Playtime" display hours if the playtime is longer than 60 minutes.
125 . Added cvar to hide the display of public chat triggers.
126-- 1.1.1 (4/22/09)
127 . Changed "IsVersus()" function to "InvalidGameMode()" to fix deadstop bug
128 with the Survival update. This is part of paving the way to Survival
129 and Versus stats in a future release.
130 . Fixed various error messages in error logs.
131 . Fixed stats panel to now work properly for people with certain characters
132 in their name not making it display.
133 . Fixed (again) a certain case where blank users would be inserted.
134 . Added cvar to enable/disable showing of the rank panel when not in a valid
135 gamemode, showing of disabled messages, and letting players use the chat
136 commands.
137 . Added some stat whoring checks to the plugin:
138 . A maximum amount of points can be earned in a single map
139 . Only 3 Tanks may be awarded during a single map
140 . Fixed minor bug with Healthpack point award not giving full amount.
141 . Added a few currently unused cvars for future features:
142 . sm_l4dstats_dbprefix -- Prefix to be used for database tables
143 . sm_l4dstats_enablecoop -- Enable stats for Coop mode
144 . sm_l4dstats_enablesv -- Enable stats for Survival mode
145 . sm_l4dstats_enableversus -- Enable stats for Versus mode
146 . sm_l4dstats_leaderboardtime -- Duration in days to show players top
147 times on the Survival leaderboards
148
149-- 1.1.1C (8/19/09) Customized by muukis
150 . Added support for custom maps.
151
152-- 1.2AXXX (9/23/09) Alpha versions from Versus support
153 . Started implementing the Versus support. (IT COMPILES!!!1)
154
155-- 1.2BXXX (10/16/09) Beta versions from Versus support
156 . All new major features that I could come up with are now implemeted:
157 . Survivor friendly fire cooldown mode.
158 . Survivor medkit use penalty (score reduction.)
159 . Spam protection allowing team gain and loss information to be shown
160 for the team only.
161 . Infected score:
162 . General: Damage done to the survivors.
163 . General: Damage done by normal infected is forwarded to the specials
164 that have influence over the victim (blinded, lunged or paralyzed.)
165 . General: Incapacitate and kill a survivor.
166 . Hunter pounces.
167 . Boomer blindings.
168 . Tank rock sniper.
169
170-- 1.2B90 (12/07/09) "Conversion" to Left 4 Dead 2
171
172-- 1.3AXXX (12/11/09) Alpha versions from L4D2 support
173
174-- 1.3BXXX (12/11/09) Beta versions from L4D2 support
175 . Support for L4D2:
176 . Support for Realism and Team Versus gamemodes.
177 . Adrenalines given.
178 . Defibrillators used.
179 . New stats for every new Special Infected (in addition to spawned counter and damage counter):
180 . Jockey:
181 . Ride length (time)
182 . Survivor damage based friendly fire mode.
183 . "Player Stats" object in Admin Menu.
184 . New console command "sm_rank_clear" to clear database.
185
186-- 1.4AXXX (12/11/09) Alpha versions from Survival and Scavenge support
187
188-- 1.4BXXX (X/X/10) Beta versions from Survival and Scavenge support
189 . Support for all gamemodes!
190 . Support for L4D2:
191 . Gas canister poured.
192 . Ammo upgrade deployed.
193 . New console commands:
194 . sm_rank_shuffle -- Shuffle teams (Versus / Scavenge) with player PPM (Points Per Minute).
195 . sm_rankvote -- Initiate team shuffle vote ("rankvote").
196 . sm_top10ppm -- Show Top10 players with highest PPM (Points Per Minute).
197 . sm_showrank -- Show currently playing players stats.
198 . sm_showppm -- Show currently playing players PPM (Points Per Minute).
199 . sm_timedmaps -- Show all map timings.
200 . sm_maptimes -- Show current map timings.
201 . sm_rankmenu -- Show rank menu.
202 . sm_rankmute <0|1> -- Set client rank mute (hide plugin messages)
203 . sm_rankmutetoggle -- Toggle client rank mute (hide plugin messages)
204
205-----------------------------------------------------------------------------
206*/
207
208#pragma semicolon 1
209
210#include <sourcemod>
211#include <clientprefs>
212#include <sdktools>
213#undef REQUIRE_PLUGIN
214#include <adminmenu>
215
216#define PLUGIN_NAME "Custom Player Stats"
217#define PLUGIN_VERSION "1.4B121"
218#define PLUGIN_DESCRIPTION "Player Stats and Ranking for Left 4 Dead and Left 4 Dead 2."
219
220#define MAX_LINE_WIDTH 64
221#define MAX_MESSAGE_WIDTH 256
222#define MAX_QUERY_COUNTER 256
223#define DB_CONF_NAME "l4dstats"
224
225#define GAMEMODE_UNKNOWN -1
226#define GAMEMODE_COOP 0
227#define GAMEMODE_VERSUS 1
228#define GAMEMODE_REALISM 2
229#define GAMEMODE_SURVIVAL 3
230#define GAMEMODE_SCAVENGE 4
231#define GAMEMODE_REALISMVERSUS 5
232#define GAMEMODE_OTHERMUTATIONS 6
233#define GAMEMODES 7
234
235#define INF_ID_SMOKER 1
236#define INF_ID_BOOMER 2
237#define INF_ID_HUNTER 3
238#define INF_ID_SPITTER_L4D2 4
239#define INF_ID_JOCKEY_L4D2 5
240#define INF_ID_CHARGER_L4D2 6
241#define INF_ID_WITCH_L4D1 4
242#define INF_ID_WITCH_L4D2 7
243#define INF_ID_TANK_L4D1 5
244#define INF_ID_TANK_L4D2 8
245
246#define TEAM_UNDEFINED 0
247#define TEAM_SPECTATORS 1
248#define TEAM_SURVIVORS 2
249#define TEAM_INFECTED 3
250
251#define INF_WEAROFF_TIME 0.5
252
253#define SERVER_VERSION_L4D1 40
254#define SERVER_VERSION_L4D2 50
255
256#define CLEAR_DATABASE_CONFIRMTIME 10.0
257
258#define CM_UNKNOWN -1
259#define CM_RANK 0
260#define CM_TOP10 1
261#define CM_NEXTRANK 2
262#define CM_NEXTRANKFULL 3
263
264#define SOUND_RANKVOTE "items/suitchargeok1.wav"
265#define SOUND_MAPTIME_START_L4D1 "UI/Beep23.wav"
266#define SOUND_MAPTIME_START_L4D2 "level/countdown.wav"
267#define SOUND_MAPTIME_IMPROVE_L4D1 "UI/Pickup_Secret01.wav"
268#define SOUND_MAPTIME_IMPROVE_L4D2 "level/bell_normal.wav"
269#define SOUND_RANKMENU_SHOW_L4D1 "UI/Menu_Horror01.wav"
270#define SOUND_RANKMENU_SHOW_L4D2 "ui/menu_horror01.wav"
271#define SOUND_BOOMER_VOMIT_L4D1 "player/Boomer/fall/boomer_dive_01.wav"
272#define SOUND_BOOMER_VOMIT_L4D2 "player/Boomer/fall/boomer_dive_01.wav"
273#define SOUND_HUNTER_PERFECT_L4D1 "player/hunter/voice/pain/lunge_attack_3.wav"
274#define SOUND_HUNTER_PERFECT_L4D2 "player/hunter/voice/pain/lunge_attack_3.wav"
275#define SOUND_TANK_BULLDOZER_L4D1 "player/tank/voice/yell/hulk_yell_8.wav"
276#define SOUND_TANK_BULLDOZER_L4D2 "player/tank/voice/yell/tank_throw_11.wav"
277#define SOUND_CHARGER_RAM "player/charger/voice/alert/charger_alert_02.wav"
278
279#define RANKVOTE_NOVOTE -1
280#define RANKVOTE_NO 0
281#define RANKVOTE_YES 1
282
283new String:TM_MENU_CURRENT[4] = " <<";
284
285new String:DB_PLAYERS_TOTALPOINTS[1024] = "points + points_survivors + points_infected + points_realism + points_survival + points_scavenge_survivors + points_scavenge_infected + points_realism_survivors + points_realism_infected + points_mutations";
286new String:DB_PLAYERS_TOTALPLAYTIME[1024] = "playtime + playtime_versus + playtime_realism + playtime_survival + playtime_scavenge + playtime_realismversus + playtime_mutations";
287
288new String:RANKVOTE_QUESTION[128] = "Do you want to shuffle teams by player PPM?";
289
290// Message of the day
291new String:MOTD_TITLE[32] = "Message Of The Day";
292new String:MessageOfTheDay[1024];
293
294// Set to false when stats seem to work properly
295new bool:DEBUG = true;
296
297new bool:CommandsRegistered = false;
298
299// Sounds
300new bool:EnableSounds_Rankvote = true;
301new bool:EnableSounds_Maptime_Start = true;
302new bool:EnableSounds_Maptime_Improve = true;
303new bool:EnableSounds_Rankmenu_Show = true;
304new bool:EnableSounds_Boomer_Vomit = true;
305new bool:EnableSounds_Hunter_Perfect = true;
306new bool:EnableSounds_Tank_Bulldozer = true;
307new bool:EnableSounds_Charger_Ram = true;
308new String:StatsSound_MapTime_Start[32];
309new String:StatsSound_MapTime_Improve[32];
310new String:StatsSound_Rankmenu_Show[32];
311new String:StatsSound_Boomer_Vomit[32];
312new String:StatsSound_Hunter_Perfect[32];
313new String:StatsSound_Tank_Bulldozer[32];
314
315// Server version
316new ServerVersion = SERVER_VERSION_L4D1;
317
318// Database handle
319new Handle:db = INVALID_HANDLE;
320new String:DbPrefix[MAX_LINE_WIDTH] = "";
321
322// Update Timer handle
323new Handle:UpdateTimer = INVALID_HANDLE;
324
325// Gamemode
326new String:CurrentGamemode[MAX_LINE_WIDTH];
327new String:CurrentGamemodeLabel[MAX_LINE_WIDTH];
328new CurrentGamemodeID = GAMEMODE_UNKNOWN;
329new String:CurrentMutation[MAX_LINE_WIDTH];
330
331// Disable check Cvar handles
332new Handle:cvar_Difficulty = INVALID_HANDLE;
333new Handle:cvar_Gamemode = INVALID_HANDLE;
334new Handle:cvar_Cheats = INVALID_HANDLE;
335
336new Handle:cvar_SurvivorLimit = INVALID_HANDLE;
337new Handle:cvar_InfectedLimit = INVALID_HANDLE;
338
339// Game event booleans
340new bool:PlayerVomited = false;
341new bool:PlayerVomitedIncap = false;
342new bool:PanicEvent = false;
343new bool:PanicEventIncap = false;
344new bool:CampaignOver = false;
345new bool:WitchExists = false;
346new bool:WitchDisturb = false;
347
348// Anti-Stat Whoring vars
349new CurrentPoints[MAXPLAYERS + 1];
350new TankCount = 0;
351
352new bool:ClientRankMute[MAXPLAYERS + 1];
353
354// Cvar handles
355new Handle:cvar_EnableRankVote = INVALID_HANDLE;
356new Handle:cvar_HumansNeeded = INVALID_HANDLE;
357new Handle:cvar_UpdateRate = INVALID_HANDLE;
358//new Handle:cvar_AnnounceRankMinChange = INVALID_HANDLE;
359new Handle:cvar_AnnounceRankChange = INVALID_HANDLE;
360new Handle:cvar_AnnouncePlayerJoined = INVALID_HANDLE;
361new Handle:cvar_AnnounceMotd = INVALID_HANDLE;
362new Handle:cvar_AnnounceMode = INVALID_HANDLE;
363new Handle:cvar_AnnounceRankChangeIVal = INVALID_HANDLE;
364new Handle:cvar_AnnounceToTeam = INVALID_HANDLE;
365//new Handle:cvar_AnnounceSpecial = INVALID_HANDLE;
366new Handle:cvar_MedkitMode = INVALID_HANDLE;
367new Handle:cvar_SiteURL = INVALID_HANDLE;
368new Handle:cvar_RankOnJoin = INVALID_HANDLE;
369new Handle:cvar_SilenceChat = INVALID_HANDLE;
370new Handle:cvar_DisabledMessages = INVALID_HANDLE;
371//new Handle:cvar_MaxPoints = INVALID_HANDLE;
372new Handle:cvar_DbPrefix = INVALID_HANDLE;
373//new Handle:cvar_LeaderboardTime = INVALID_HANDLE;
374new Handle:cvar_EnableNegativeScore = INVALID_HANDLE;
375new Handle:cvar_FriendlyFireMode = INVALID_HANDLE;
376new Handle:cvar_FriendlyFireMultiplier = INVALID_HANDLE;
377new Handle:cvar_FriendlyFireCooldown = INVALID_HANDLE;
378new Handle:cvar_FriendlyFireCooldownMode = INVALID_HANDLE;
379new Handle:FriendlyFireTimer[MAXPLAYERS + 1][MAXPLAYERS + 1];
380new bool:FriendlyFireCooldown[MAXPLAYERS + 1][MAXPLAYERS + 1];
381new FriendlyFirePrm[MAXPLAYERS][2];
382new Handle:FriendlyFireDamageTrie = INVALID_HANDLE;
383new FriendlyFirePrmCounter = 0;
384
385new Handle:cvar_Enable = INVALID_HANDLE;
386new Handle:cvar_EnableCoop = INVALID_HANDLE;
387new Handle:cvar_EnableSv = INVALID_HANDLE;
388new Handle:cvar_EnableVersus = INVALID_HANDLE;
389new Handle:cvar_EnableTeamVersus = INVALID_HANDLE;
390new Handle:cvar_EnableRealism = INVALID_HANDLE;
391new Handle:cvar_EnableScavenge = INVALID_HANDLE;
392new Handle:cvar_EnableTeamScavenge = INVALID_HANDLE;
393new Handle:cvar_EnableRealismVersus = INVALID_HANDLE;
394new Handle:cvar_EnableTeamRealismVersus = INVALID_HANDLE;
395new Handle:cvar_EnableMutations = INVALID_HANDLE;
396
397new Handle:cvar_RealismMultiplier = INVALID_HANDLE;
398new Handle:cvar_RealismVersusSurMultiplier = INVALID_HANDLE;
399new Handle:cvar_RealismVersusInfMultiplier = INVALID_HANDLE;
400new Handle:cvar_EnableSvMedicPoints = INVALID_HANDLE;
401
402new Handle:cvar_Infected = INVALID_HANDLE;
403new Handle:cvar_Hunter = INVALID_HANDLE;
404new Handle:cvar_Smoker = INVALID_HANDLE;
405new Handle:cvar_Boomer = INVALID_HANDLE;
406new Handle:cvar_Spitter = INVALID_HANDLE;
407new Handle:cvar_Jockey = INVALID_HANDLE;
408new Handle:cvar_Charger = INVALID_HANDLE;
409
410new Handle:cvar_Pills = INVALID_HANDLE;
411new Handle:cvar_Adrenaline = INVALID_HANDLE;
412new Handle:cvar_Medkit = INVALID_HANDLE;
413new Handle:cvar_Defib = INVALID_HANDLE;
414new Handle:cvar_SmokerDrag = INVALID_HANDLE;
415new Handle:cvar_ChokePounce = INVALID_HANDLE;
416new Handle:cvar_JockeyRide = INVALID_HANDLE;
417new Handle:cvar_ChargerPlummel = INVALID_HANDLE;
418new Handle:cvar_ChargerCarry = INVALID_HANDLE;
419new Handle:cvar_Revive = INVALID_HANDLE;
420new Handle:cvar_Rescue = INVALID_HANDLE;
421new Handle:cvar_Protect = INVALID_HANDLE;
422
423new Handle:cvar_Tank = INVALID_HANDLE;
424new Handle:cvar_Panic = INVALID_HANDLE;
425new Handle:cvar_BoomerMob = INVALID_HANDLE;
426new Handle:cvar_SafeHouse = INVALID_HANDLE;
427new Handle:cvar_Witch = INVALID_HANDLE;
428new Handle:cvar_WitchCrowned = INVALID_HANDLE;
429new Handle:cvar_VictorySurvivors = INVALID_HANDLE;
430new Handle:cvar_VictoryInfected = INVALID_HANDLE;
431
432new Handle:cvar_FFire = INVALID_HANDLE;
433new Handle:cvar_FIncap = INVALID_HANDLE;
434new Handle:cvar_FKill = INVALID_HANDLE;
435new Handle:cvar_InSafeRoom = INVALID_HANDLE;
436new Handle:cvar_Restart = INVALID_HANDLE;
437new Handle:cvar_CarAlarm = INVALID_HANDLE;
438new Handle:cvar_BotScoreMultiplier = INVALID_HANDLE;
439
440new Handle:cvar_SurvivorDeath = INVALID_HANDLE;
441new Handle:cvar_SurvivorIncap = INVALID_HANDLE;
442
443// L4D2 misc
444new Handle:cvar_AmmoUpgradeAdded = INVALID_HANDLE;
445new Handle:cvar_GascanPoured = INVALID_HANDLE;
446
447new MaxPounceDistance;
448new MinPounceDistance;
449new MaxPounceDamage;
450new Handle:cvar_HunterDamageCap = INVALID_HANDLE;
451new Float:HunterPosition[MAXPLAYERS + 1][3];
452new Handle:cvar_HunterPerfectPounceDamage = INVALID_HANDLE;
453new Handle:cvar_HunterPerfectPounceSuccess = INVALID_HANDLE;
454new Handle:cvar_HunterNicePounceDamage = INVALID_HANDLE;
455new Handle:cvar_HunterNicePounceSuccess = INVALID_HANDLE;
456
457new BoomerHitCounter[MAXPLAYERS + 1];
458new bool:BoomerVomitUpdated[MAXPLAYERS + 1];
459new Handle:cvar_BoomerSuccess = INVALID_HANDLE;
460new Handle:cvar_BoomerPerfectHits = INVALID_HANDLE;
461new Handle:cvar_BoomerPerfectSuccess = INVALID_HANDLE;
462new Handle:TimerBoomerPerfectCheck[MAXPLAYERS + 1];
463
464new InfectedDamageCounter[MAXPLAYERS + 1];
465new Handle:cvar_InfectedDamage = INVALID_HANDLE;
466new Handle:TimerInfectedDamageCheck[MAXPLAYERS + 1];
467
468new Handle:cvar_TankDamageCap = INVALID_HANDLE;
469new Handle:cvar_TankDamageTotal = INVALID_HANDLE;
470new Handle:cvar_TankDamageTotalSuccess = INVALID_HANDLE;
471
472new ChargerCarryVictim[MAXPLAYERS + 1];
473new ChargerPlummelVictim[MAXPLAYERS + 1];
474new JockeyVictim[MAXPLAYERS + 1];
475new JockeyRideStartTime[MAXPLAYERS + 1];
476
477new SmokerDamageCounter[MAXPLAYERS + 1];
478new SpitterDamageCounter[MAXPLAYERS + 1];
479new JockeyDamageCounter[MAXPLAYERS + 1];
480new ChargerDamageCounter[MAXPLAYERS + 1];
481new ChargerImpactCounter[MAXPLAYERS + 1];
482new Handle:ChargerImpactCounterTimer[MAXPLAYERS + 1];
483new Handle:cvar_ChargerRamHits = INVALID_HANDLE;
484new Handle:cvar_ChargerRamSuccess = INVALID_HANDLE;
485new TankDamageCounter[MAXPLAYERS + 1];
486new TankDamageTotalCounter[MAXPLAYERS + 1];
487new TankPointsCounter[MAXPLAYERS + 1];
488new TankSurvivorKillCounter[MAXPLAYERS + 1];
489new Handle:cvar_TankThrowRockSuccess = INVALID_HANDLE;
490
491new Handle:cvar_PlayerLedgeSuccess = INVALID_HANDLE;
492new Handle:cvar_Matador = INVALID_HANDLE;
493
494new ClientInfectedType[MAXPLAYERS + 1];
495
496new PlayerBlinded[MAXPLAYERS + 1][2];
497new PlayerParalyzed[MAXPLAYERS + 1][2];
498new PlayerLunged[MAXPLAYERS + 1][2];
499new PlayerPlummeled[MAXPLAYERS + 1][2];
500new PlayerCarried[MAXPLAYERS + 1][2];
501new PlayerJockied[MAXPLAYERS + 1][2];
502
503// Rank panel vars
504new RankTotal = 0;
505new ClientRank[MAXPLAYERS + 1];
506new ClientNextRank[MAXPLAYERS + 1];
507new ClientPoints[MAXPLAYERS + 1];
508new GameModeRankTotal = 0;
509new ClientGameModeRank[MAXPLAYERS + 1];
510new ClientGameModePoints[MAXPLAYERS + 1][GAMEMODES];
511
512// Misc arrays
513new TimerPoints[MAXPLAYERS + 1];
514new TimerKills[MAXPLAYERS + 1];
515new TimerHeadshots[MAXPLAYERS + 1];
516new Pills[4096];
517new Adrenaline[4096];
518
519new String:QueryBuffer[MAX_QUERY_COUNTER][MAX_QUERY_COUNTER];
520new QueryCounter = 0;
521
522new AnnounceCounter[MAXPLAYERS + 1];
523new PostAdminCheckRetryCounter[MAXPLAYERS + 1];
524
525// For every medkit used the points earned by the Survivor team is calculated with this formula:
526// NormalPointsEarned * (1 - MedkitsUsedCounter * cvar_MedkitUsedPointPenalty)
527// Minimum formula result = 0 (Cannot be negative)
528new MedkitsUsedCounter = 0;
529new Handle:cvar_MedkitUsedPointPenalty = INVALID_HANDLE;
530new Handle:cvar_MedkitUsedPointPenaltyMax = INVALID_HANDLE;
531new Handle:cvar_MedkitUsedFree = INVALID_HANDLE;
532new Handle:cvar_MedkitUsedRealismFree = INVALID_HANDLE;
533new Handle:cvar_MedkitBotMode = INVALID_HANDLE;
534
535new ProtectedFriendlyCounter[MAXPLAYERS + 1];
536new Handle:TimerProtectedFriendly[MAXPLAYERS + 1];
537
538// Announce rank
539new Handle:TimerRankChangeCheck[MAXPLAYERS + 1];
540new RankChangeLastRank[MAXPLAYERS + 1];
541new bool:RankChangeFirstCheck[MAXPLAYERS + 1];
542
543// MapTiming
544new Float:MapTimingStartTime = -1.0;
545new bool:MapTimingBlocked = false;
546new Handle:MapTimingSurvivors = INVALID_HANDLE; // Survivors at the beginning of the map
547new Handle:MapTimingInfected = INVALID_HANDLE; // Survivors at the beginning of the map
548new String:MapTimingMenuInfo[MAXPLAYERS + 1][MAX_LINE_WIDTH];
549
550// When an admin calls for clear database, the client id is stored here for a period of time.
551// The admin must then call the clear command again to confirm the call. After the second call
552// the database is cleared. The confirm must be done in the time set by CLEAR_DATABASE_CONFIRMTIME.
553new ClearDatabaseCaller = -1;
554new Handle:ClearDatabaseTimer = INVALID_HANDLE;
555//new Handle:ClearPlayerMenu = INVALID_HANDLE;
556
557// Create handle for the admin menu
558new Handle:RankAdminMenu = INVALID_HANDLE;
559new TopMenuObject:MenuClear = INVALID_TOPMENUOBJECT;
560new TopMenuObject:MenuClearPlayers = INVALID_TOPMENUOBJECT;
561new TopMenuObject:MenuClearMaps = INVALID_TOPMENUOBJECT;
562new TopMenuObject:MenuClearAll = INVALID_TOPMENUOBJECT;
563new TopMenuObject:MenuRemoveCustomMaps = INVALID_TOPMENUOBJECT;
564new TopMenuObject:MenuCleanPlayers = INVALID_TOPMENUOBJECT;
565new TopMenuObject:MenuClearTimedMaps = INVALID_TOPMENUOBJECT;
566
567// Administrative Cvars
568new Handle:cvar_AdminPlayerCleanLastOnTime = INVALID_HANDLE;
569new Handle:cvar_AdminPlayerCleanPlatime = INVALID_HANDLE;
570
571// Players can request a vote for team shuffle based on the player ranks ONCE PER MAP
572new PlayerRankVote[MAXPLAYERS + 1];
573new Handle:RankVoteTimer = INVALID_HANDLE;
574new Handle:PlayerRankVoteTrie = INVALID_HANDLE; // Survivors at the beginning of the map
575new Handle:cvar_RankVoteTime = INVALID_HANDLE;
576
577new Handle:cvar_Top10PPMMin = INVALID_HANDLE;
578
579new bool:SurvivalStarted = false;
580
581new Handle:L4DStatsConf = INVALID_HANDLE;
582new Handle:L4DStatsSHS = INVALID_HANDLE;
583new Handle:L4DStatsTOB = INVALID_HANDLE;
584
585new Float:ClientMapTime[MAXPLAYERS + 1];
586
587new Handle:cvar_Lan = INVALID_HANDLE;
588new Handle:cvar_SoundsEnabled = INVALID_HANDLE;
589
590new Handle:MeleeKillTimer[MAXPLAYERS + 1];
591new MeleeKillCounter[MAXPLAYERS + 1];
592
593// Plugin Info
594public Plugin:myinfo =
595{
596 name = PLUGIN_NAME,
597 author = "Mikko Andersson (muukis)",
598 description = PLUGIN_DESCRIPTION,
599 version = PLUGIN_VERSION,
600 url = "http://www.sourcemod.com/"
601};
602
603// Here we go!
604public OnPluginStart()
605{
606 CommandsRegistered = false;
607
608 // Require Left 4 Dead (2)
609 decl String:game_name[64];
610 GetGameFolderName(game_name, sizeof(game_name));
611
612 if (!StrEqual(game_name, "left4dead", false) &&
613 !StrEqual(game_name, "left4dead2", false))
614 {
615 SetFailState("Plugin supports Left 4 Dead and Left 4 Dead 2 only.");
616 return;
617 }
618
619 ServerVersion = GuessSDKVersion();
620
621 if (ServerVersion == SERVER_VERSION_L4D1)
622 {
623 strcopy(StatsSound_MapTime_Start, sizeof(StatsSound_MapTime_Start), SOUND_MAPTIME_START_L4D1);
624 strcopy(StatsSound_MapTime_Improve, sizeof(StatsSound_MapTime_Improve), SOUND_MAPTIME_IMPROVE_L4D1);
625 strcopy(StatsSound_Rankmenu_Show, sizeof(StatsSound_Rankmenu_Show), SOUND_RANKMENU_SHOW_L4D1);
626 strcopy(StatsSound_Boomer_Vomit, sizeof(StatsSound_Boomer_Vomit), SOUND_BOOMER_VOMIT_L4D1);
627 strcopy(StatsSound_Hunter_Perfect, sizeof(StatsSound_Hunter_Perfect), SOUND_HUNTER_PERFECT_L4D1);
628 strcopy(StatsSound_Tank_Bulldozer, sizeof(StatsSound_Tank_Bulldozer), SOUND_TANK_BULLDOZER_L4D1);
629 }
630 else
631 {
632 strcopy(StatsSound_MapTime_Start, sizeof(StatsSound_MapTime_Start), SOUND_MAPTIME_START_L4D2);
633 strcopy(StatsSound_MapTime_Improve, sizeof(StatsSound_MapTime_Improve), SOUND_MAPTIME_IMPROVE_L4D2);
634 strcopy(StatsSound_Rankmenu_Show, sizeof(StatsSound_Rankmenu_Show), SOUND_RANKMENU_SHOW_L4D2);
635 strcopy(StatsSound_Boomer_Vomit, sizeof(StatsSound_Boomer_Vomit), SOUND_BOOMER_VOMIT_L4D2);
636 strcopy(StatsSound_Hunter_Perfect, sizeof(StatsSound_Hunter_Perfect), SOUND_HUNTER_PERFECT_L4D2);
637 strcopy(StatsSound_Tank_Bulldozer, sizeof(StatsSound_Tank_Bulldozer), SOUND_TANK_BULLDOZER_L4D2);
638 }
639
640 // Plugin version public Cvar
641 CreateConVar("l4d_stats_version", PLUGIN_VERSION, "Custom Player Stats Version", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY|FCVAR_DONTRECORD);
642
643 // Disable setting Cvars
644 cvar_Difficulty = FindConVar("z_difficulty");
645 cvar_Gamemode = FindConVar("mp_gamemode");
646 cvar_Cheats = FindConVar("sv_cheats");
647
648 cvar_Lan = FindConVar("sv_lan");
649 if (GetConVarInt(cvar_Lan))
650 LogMessage("ATTENTION! %s in LAN environment is based on IP address rather than Steam ID. The statistics are not reliable when they are base on IP!", PLUGIN_NAME);
651
652 HookConVarChange(cvar_Lan, action_LanChanged);
653
654 cvar_SurvivorLimit = FindConVar("survivor_limit");
655 cvar_InfectedLimit = FindConVar("z_max_player_zombies");
656
657 // Administrative Cvars
658 cvar_AdminPlayerCleanLastOnTime = CreateConVar("l4d_stats_adm_cleanoldplayers", "2", "How many months old players (last online time) will be cleaned. 0 = Disabled", FCVAR_PLUGIN, true, 0.0);
659 cvar_AdminPlayerCleanPlatime = CreateConVar("l4d_stats_adm_cleanplaytime", "30", "How many minutes of playtime to not get cleaned from stats. 0 = Disabled", FCVAR_PLUGIN, true, 0.0);
660
661 // Config/control Cvars
662 cvar_EnableRankVote = CreateConVar("l4d_stats_enablerankvote", "1", "Enable voting of team shuffle by player PPM (Points Per Minute)", FCVAR_PLUGIN, true, 0.0, true, 1.0);
663 cvar_HumansNeeded = CreateConVar("l4d_stats_minhumans", "2", "Minimum Human players before stats will be enabled", FCVAR_PLUGIN, true, 1.0, true, 4.0);
664 cvar_UpdateRate = CreateConVar("l4d_stats_updaterate", "90", "Number of seconds between Common Infected point earn announcement/update", FCVAR_PLUGIN, true, 30.0);
665 //cvar_AnnounceRankMinChange = CreateConVar("l4d_stats_announcerankminpoint", "500", "Minimum change to points before rank change announcement", FCVAR_PLUGIN, true, 0.0);
666 cvar_AnnounceRankChange = CreateConVar("l4d_stats_announcerank", "1", "Chat announcment for rank change", FCVAR_PLUGIN, true, 0.0, true, 1.0);
667 cvar_AnnounceRankChangeIVal = CreateConVar("l4d_stats_announcerankinterval", "60", "Rank change check interval", FCVAR_PLUGIN, true, 10.0);
668 cvar_AnnouncePlayerJoined = CreateConVar("l4d_stats_announceplayerjoined", "1", "Chat announcment for player joined.", FCVAR_PLUGIN, true, 0.0, true, 1.0);
669 cvar_AnnounceMotd = CreateConVar("l4d_stats_announcemotd", "1", "Chat announcment for the message of the day.", FCVAR_PLUGIN, true, 0.0, true, 1.0);
670 cvar_AnnounceMode = CreateConVar("l4d_stats_announcemode", "1", "Chat announcment mode. 0 = Off, 1 = Player Only, 2 = Player Only w/ Public Headshots, 3 = All Public", FCVAR_PLUGIN, true, 0.0, true, 3.0);
671 cvar_AnnounceToTeam = CreateConVar("l4d_stats_announceteam", "2", "Chat announcment team messages to the team only mode. 0 = Print messages to all teams, 1 = Print messages to own team only, 2 = Print messages to own team and spectators only", FCVAR_PLUGIN, true, 0.0, true, 2.0);
672 //cvar_AnnounceSpecial = CreateConVar("l4d_stats_announcespecial", "1", "Chat announcment mode for special events. 0 = Off, 1 = Player Only, 2 = Print messages to all teams, 3 = Print messages to own team only, 4 = Print messages to own team and spectators only", FCVAR_PLUGIN, true, 0.0, true, 4.0);
673 cvar_MedkitMode = CreateConVar("l4d_stats_medkitmode", "0", "Medkit point award mode. 0 = Based on amount healed, 1 = Static amount", FCVAR_PLUGIN, true, 0.0, true, 1.0);
674 cvar_SiteURL = CreateConVar("l4d_stats_siteurl", "", "Community site URL, for rank panel display", FCVAR_PLUGIN);
675 cvar_RankOnJoin = CreateConVar("l4d_stats_rankonjoin", "1", "Display player's rank when they connect. 0 = Disable, 1 = Enable", FCVAR_PLUGIN, true, 0.0, true, 1.0);
676 cvar_SilenceChat = CreateConVar("l4d_stats_silencechat", "0", "Silence chat triggers. 0 = Show chat triggers, 1 = Silence chat triggers", FCVAR_PLUGIN, true, 0.0, true, 1.0);
677 cvar_DisabledMessages = CreateConVar("l4d_stats_disabledmessages", "1", "Show 'Stats Disabled' messages, allow chat commands to work when stats disabled. 0 = Hide messages/disable chat, 1 = Show messages/allow chat", FCVAR_PLUGIN, true, 0.0, true, 1.0);
678 //cvar_MaxPoints = CreateConVar("l4d_stats_maxpoints", "500", "Maximum number of points that can be earned in a single map. Normal = x1, Adv = x2, Expert = x3", FCVAR_PLUGIN, true, 500.0);
679 cvar_DbPrefix = CreateConVar("l4d_stats_dbprefix", "", "Prefix for your stats tables", FCVAR_PLUGIN);
680 //cvar_LeaderboardTime = CreateConVar("l4d_stats_leaderboardtime", "14", "Time in days to show Survival Leaderboard times", FCVAR_PLUGIN, true, 1.0);
681 cvar_EnableNegativeScore = CreateConVar("l4d_stats_enablenegativescore", "1", "Enable point losses (negative score)", FCVAR_PLUGIN, true, 0.0, true, 1.0);
682 cvar_FriendlyFireMode = CreateConVar("l4d_stats_ffire_mode", "2", "Friendly fire mode. 0 = Normal, 1 = Cooldown, 2 = Damage based", FCVAR_PLUGIN, true, 0.0, true, 2.0);
683 cvar_FriendlyFireMultiplier = CreateConVar("l4d_stats_ffire_multiplier", "1.5", "Friendly fire damage multiplier (Formula: Score = Damage * Multiplier)", FCVAR_PLUGIN, true, 0.0);
684 cvar_FriendlyFireCooldown = CreateConVar("l4d_stats_ffire_cooldown", "10.0", "Time in seconds for friendly fire cooldown", FCVAR_PLUGIN, true, 1.0);
685 cvar_FriendlyFireCooldownMode = CreateConVar("l4d_stats_ffire_cooldownmode", "1", "Friendly fire cooldown mode. 0 = Disable, 1 = Player specific, 2 = General", FCVAR_PLUGIN, true, 0.0, true, 2.0);
686
687 // Game mode Cvars
688 cvar_Enable = CreateConVar("l4d_stats_enable", "1", "Enable/Disable all stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
689 cvar_EnableCoop = CreateConVar("l4d_stats_enablecoop", "1", "Enable/Disable coop stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
690 cvar_EnableSv = CreateConVar("l4d_stats_enablesv", "1", "Enable/Disable survival stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
691 cvar_EnableVersus = CreateConVar("l4d_stats_enableversus", "1", "Enable/Disable versus stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
692 cvar_EnableTeamVersus = CreateConVar("l4d_stats_enableteamversus", "1", "[L4D2] Enable/Disable team versus stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
693 cvar_EnableRealism = CreateConVar("l4d_stats_enablerealism", "1", "[L4D2] Enable/Disable realism stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
694 cvar_EnableScavenge = CreateConVar("l4d_stats_enablescavenge", "1", "[L4D2] Enable/Disable scavenge stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
695 cvar_EnableTeamScavenge = CreateConVar("l4d_stats_enableteamscavenge", "1", "[L4D2] Enable/Disable team scavenge stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
696 cvar_EnableRealismVersus = CreateConVar("l4d_stats_enablerealismvs", "1", "[L4D2] Enable/Disable realism versus stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
697 cvar_EnableTeamRealismVersus = CreateConVar("l4d_stats_enableteamrealismvs", "1", "[L4D2] Enable/Disable team realism versus stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
698 cvar_EnableMutations = CreateConVar("l4d_stats_enablemutations", "1", "[L4D2] Enable/Disable mutations stat tracking", FCVAR_PLUGIN, true, 0.0, true, 1.0);
699
700 // Game mode depended Cvars
701 cvar_RealismMultiplier = CreateConVar("l4d_stats_realismmultiplier", "1.4", "[L4D2] Realism score multiplier for coop score", FCVAR_PLUGIN, true, 1.0);
702 cvar_RealismVersusSurMultiplier = CreateConVar("l4d_stats_realismvsmultiplier_s", "1.4", "[L4D2] Realism score multiplier for survivors versus score", FCVAR_PLUGIN, true, 1.0);
703 cvar_RealismVersusInfMultiplier = CreateConVar("l4d_stats_realismvsmultiplier_i", "0.6", "[L4D2] Realism score multiplier for infected versus score", FCVAR_PLUGIN, true, 0.0, true, 1.0);
704 cvar_EnableSvMedicPoints = CreateConVar("l4d_stats_medicpointssv", "0", "Survival medic points enabled", FCVAR_PLUGIN, true, 0.0, true, 1.0);
705
706 // Infected point Cvars
707 cvar_Infected = CreateConVar("l4d_stats_infected", "1", "Base score for killing a Common Infected", FCVAR_PLUGIN, true, 1.0);
708 cvar_Hunter = CreateConVar("l4d_stats_hunter", "2", "Base score for killing a Hunter", FCVAR_PLUGIN, true, 1.0);
709 cvar_Smoker = CreateConVar("l4d_stats_smoker", "3", "Base score for killing a Smoker", FCVAR_PLUGIN, true, 1.0);
710 cvar_Boomer = CreateConVar("l4d_stats_boomer", "5", "Base score for killing a Boomer", FCVAR_PLUGIN, true, 1.0);
711 cvar_Spitter = CreateConVar("l4d_stats_spitter", "5", "[L4D2] Base score for killing a Spitter", FCVAR_PLUGIN, true, 1.0);
712 cvar_Jockey = CreateConVar("l4d_stats_jockey", "5", "[L4D2] Base score for killing a Jockey", FCVAR_PLUGIN, true, 1.0);
713 cvar_Charger = CreateConVar("l4d_stats_charger", "5", "[L4D2] Base score for killing a Charger", FCVAR_PLUGIN, true, 1.0);
714 cvar_InfectedDamage = CreateConVar("l4d_stats_infected_damage", "2", "The amount of damage inflicted to Survivors to earn 1 point", FCVAR_PLUGIN, true, 1.0);
715
716 // Misc personal gain Cvars
717 cvar_Pills = CreateConVar("l4d_stats_pills", "15", "Base score for giving Pills to a friendly", FCVAR_PLUGIN, true, 1.0);
718 cvar_Adrenaline = CreateConVar("l4d_stats_adrenaline", "15", "[L4D2] Base score for giving Adrenaline to a friendly", FCVAR_PLUGIN, true, 1.0);
719 cvar_Medkit = CreateConVar("l4d_stats_medkit", "20", "Base score for using a Medkit on a friendly", FCVAR_PLUGIN, true, 1.0);
720 cvar_Defib = CreateConVar("l4d_stats_defib", "20", "[L4D2] Base score for using a Defibrillator on a friendly", FCVAR_PLUGIN, true, 1.0);
721 cvar_SmokerDrag = CreateConVar("l4d_stats_smokerdrag", "5", "Base score for saving a friendly from a Smoker Tongue Drag", FCVAR_PLUGIN, true, 1.0);
722 cvar_JockeyRide = CreateConVar("l4d_stats_jockeyride", "10", "[L4D2] Base score for saving a friendly from a Jockey Ride", FCVAR_PLUGIN, true, 1.0);
723 cvar_ChargerPlummel = CreateConVar("l4d_stats_chargerplummel", "10", "[L4D2] Base score for saving a friendly from a Charger Plummel", FCVAR_PLUGIN, true, 1.0);
724 cvar_ChargerCarry = CreateConVar("l4d_stats_chargercarry", "15", "[L4D2] Base score for saving a friendly from a Charger Carry", FCVAR_PLUGIN, true, 1.0);
725 cvar_ChokePounce = CreateConVar("l4d_stats_chokepounce", "10", "Base score for saving a friendly from a Hunter Pounce / Smoker Choke", FCVAR_PLUGIN, true, 1.0);
726 cvar_Revive = CreateConVar("l4d_stats_revive", "15", "Base score for Revive a friendly from Incapacitated state", FCVAR_PLUGIN, true, 1.0);
727 cvar_Rescue = CreateConVar("l4d_stats_rescue", "10", "Base score for Rescue a friendly from a closet", FCVAR_PLUGIN, true, 1.0);
728 cvar_Protect = CreateConVar("l4d_stats_protect", "3", "Base score for Protect a friendly in combat", FCVAR_PLUGIN, true, 1.0);
729 cvar_PlayerLedgeSuccess = CreateConVar("l4d_stats_ledgegrap", "15", "Base score for causing a survivor to grap a ledge", FCVAR_PLUGIN, true, 1.0);
730 cvar_Matador = CreateConVar("l4d_stats_matador", "30", "[L4D2] Base score for killing a charging Charger with a melee weapon", FCVAR_PLUGIN, true, 1.0);
731 cvar_WitchCrowned = CreateConVar("l4d_stats_witchcrowned", "30", "Base score for Crowning a Witch", FCVAR_PLUGIN, true, 1.0);
732
733 // Team gain Cvars
734 cvar_Tank = CreateConVar("l4d_stats_tank", "25", "Base team score for killing a Tank", FCVAR_PLUGIN, true, 1.0);
735 cvar_Panic = CreateConVar("l4d_stats_panic", "25", "Base team score for surviving a Panic Event with no Incapacitations", FCVAR_PLUGIN, true, 1.0);
736 cvar_BoomerMob = CreateConVar("l4d_stats_boomermob", "10", "Base team score for surviving a Boomer Mob with no Incapacitations", FCVAR_PLUGIN, true, 1.0);
737 cvar_SafeHouse = CreateConVar("l4d_stats_safehouse", "10", "Base score for reaching a Safe House", FCVAR_PLUGIN, true, 1.0);
738 cvar_Witch = CreateConVar("l4d_stats_witch", "10", "Base score for Not Disturbing a Witch", FCVAR_PLUGIN, true, 1.0);
739 cvar_VictorySurvivors = CreateConVar("l4d_stats_campaign", "5", "Base score for Completing a Campaign", FCVAR_PLUGIN, true, 1.0);
740 cvar_VictoryInfected = CreateConVar("l4d_stats_infected_win", "30", "Base victory score for Infected Team", FCVAR_PLUGIN, true, 1.0);
741
742 // Point loss Cvars
743 cvar_FFire = CreateConVar("l4d_stats_ffire", "25", "Base score for Friendly Fire", FCVAR_PLUGIN, true, 1.0);
744 cvar_FIncap = CreateConVar("l4d_stats_fincap", "75", "Base score for a Friendly Incap", FCVAR_PLUGIN, true, 1.0);
745 cvar_FKill = CreateConVar("l4d_stats_fkill", "250", "Base score for a Friendly Kill", FCVAR_PLUGIN, true, 1.0);
746 cvar_InSafeRoom = CreateConVar("l4d_stats_insaferoom", "5", "Base score for letting Infected in the Safe Room", FCVAR_PLUGIN, true, 1.0);
747 cvar_Restart = CreateConVar("l4d_stats_restart", "100", "Base score for a Round Restart", FCVAR_PLUGIN, true, 1.0);
748 cvar_MedkitUsedPointPenalty = CreateConVar("l4d_stats_medkitpenalty", "0.1", "Score reduction for all Survivor earned points for each used Medkit (Formula: Score = NormalPoints * (1 - MedkitsUsed * MedkitPenalty))", FCVAR_PLUGIN, true, 0.0, true, 0.5);
749 cvar_MedkitUsedPointPenaltyMax = CreateConVar("l4d_stats_medkitpenaltymax", "1.0", "Maximum score reduction (the score reduction will not go over this value when a Medkit is used)", FCVAR_PLUGIN, true, 0.0, true, 1.0);
750 cvar_MedkitUsedFree = CreateConVar("l4d_stats_medkitpenaltyfree", "0", "Team Survivors can use this many Medkits for free without any reduction to the score", FCVAR_PLUGIN, true, 0.0);
751 cvar_MedkitUsedRealismFree = CreateConVar("l4d_stats_medkitpenaltyfree_r", "4", "Team Survivors can use this many Medkits for free without any reduction to the score when playing in Realism gamemodes (-1 = use the value in l4d_stats_medkitpenaltyfree)", FCVAR_PLUGIN, true, -1.0);
752 cvar_MedkitBotMode = CreateConVar("l4d_stats_medkitbotmode", "1", "Add score reduction when bot uses a medkit. 0 = No, 1 = Bot uses a Medkit to a human player, 2 = Bot uses a Medkit to other than itself, 3 = Yes", FCVAR_PLUGIN, true, 0.0, true, 2.0);
753 cvar_CarAlarm = CreateConVar("l4d_stats_caralarm", "50", "[L4D2] Base score for a Triggering Car Alarm", FCVAR_PLUGIN, true, 1.0);
754 cvar_BotScoreMultiplier = CreateConVar("l4d_stats_botscoremultiplier", "1.0", "Multiplier to use when receiving bot related score penalty. 0 = Disable", FCVAR_PLUGIN, true, 0.0);
755
756 // Survivor point Cvars
757 cvar_SurvivorDeath = CreateConVar("l4d_stats_survivor_death", "40", "Base score for killing a Survivor", FCVAR_PLUGIN, true, 1.0);
758 cvar_SurvivorIncap = CreateConVar("l4d_stats_survivor_incap", "15", "Base score for incapacitating a Survivor", FCVAR_PLUGIN, true, 1.0);
759
760 // Hunter point Cvars
761 cvar_HunterPerfectPounceDamage = CreateConVar("l4d_stats_perfectpouncedamage", "25", "The amount of damage from a pounce to earn Perfect Pounce (Death From Above) success points", FCVAR_PLUGIN, true, 1.0);
762 cvar_HunterPerfectPounceSuccess = CreateConVar("l4d_stats_perfectpouncesuccess", "25", "Base score for a successful Perfect Pounce", FCVAR_PLUGIN, true, 1.0);
763 cvar_HunterNicePounceDamage = CreateConVar("l4d_stats_nicepouncedamage", "15", "The amount of damage from a pounce to earn Nice Pounce (Pain From Above) success points", FCVAR_PLUGIN, true, 1.0);
764 cvar_HunterNicePounceSuccess = CreateConVar("l4d_stats_nicepouncesuccess", "10", "Base score for a successful Nice Pounce", FCVAR_PLUGIN, true, 1.0);
765 cvar_HunterDamageCap = CreateConVar("l4d_stats_hunterdamagecap", "25", "Hunter stored damage cap", FCVAR_PLUGIN, true, 25.0);
766
767 if (ServerVersion == SERVER_VERSION_L4D1)
768 {
769 MaxPounceDistance = GetConVarInt(FindConVar("z_pounce_damage_range_max"));
770 MinPounceDistance = GetConVarInt(FindConVar("z_pounce_damage_range_min"));
771 }
772 else
773 {
774 MaxPounceDistance = 1024;
775 MinPounceDistance = 300;
776 }
777 MaxPounceDamage = GetConVarInt(FindConVar("z_hunter_max_pounce_bonus_damage"));
778
779 // Boomer point Cvars
780 cvar_BoomerSuccess = CreateConVar("l4d_stats_boomersuccess", "5", "Base score for a successfully vomiting on survivor", FCVAR_PLUGIN, true, 1.0);
781 cvar_BoomerPerfectHits = CreateConVar("l4d_stats_boomerperfecthits", "4", "The number of survivors that needs to get blinded to earn Boomer Perfect Vomit Award and success points", FCVAR_PLUGIN, true, 4.0);
782 cvar_BoomerPerfectSuccess = CreateConVar("l4d_stats_boomerperfectsuccess", "30", "Base score for a successful Boomer Perfect Vomit", FCVAR_PLUGIN, true, 1.0);
783
784 // Tank point Cvars
785 cvar_TankDamageCap = CreateConVar("l4d_stats_tankdmgcap", "500", "Maximum inflicted damage done by Tank to earn Infected damagepoints", FCVAR_PLUGIN, true, 150.0);
786 cvar_TankDamageTotal = CreateConVar("l4d_stats_bulldozer", "200", "Damage inflicted by Tank to earn Bulldozer Award and success points", FCVAR_PLUGIN, true, 200.0);
787 cvar_TankDamageTotalSuccess = CreateConVar("l4d_stats_bulldozersuccess", "50", "Base score for Bulldozer Award", FCVAR_PLUGIN, true, 1.0);
788 cvar_TankThrowRockSuccess = CreateConVar("l4d_stats_tankthrowrocksuccess", "5", "Base score for a Tank thrown rock hit", FCVAR_PLUGIN, true, 0.0);
789
790 // Charger point Cvars
791 cvar_ChargerRamSuccess = CreateConVar("l4d_stats_chargerramsuccess", "40", "Base score for a successful Charger Scattering Ram", FCVAR_PLUGIN, true, 1.0);
792 cvar_ChargerRamHits = CreateConVar("l4d_stats_chargerramhits", "4", "The number of impacts on survivors to earn Scattering Ram Award and success points", FCVAR_PLUGIN, true, 2.0);
793
794 // Misc L4D2 Cvars
795 cvar_AmmoUpgradeAdded = CreateConVar("l4d_stats_deployammoupgrade", "10", "[L4D2] Base score for deploying ammo upgrade pack", FCVAR_PLUGIN, true, 0.0);
796 cvar_GascanPoured = CreateConVar("l4d_stats_gascanpoured", "5", "[L4D2] Base score for successfully pouring a gascan", FCVAR_PLUGIN, true, 0.0);
797
798 // Other Cvars
799 cvar_Top10PPMMin = CreateConVar("l4d_stats_top10ppmplaytime", "30", "Minimum playtime (minutes) to show in top10 ppm list", FCVAR_PLUGIN, true, 1.0);
800 cvar_RankVoteTime = CreateConVar("l4d_stats_rankvotetime", "20", "Time to wait people to vote", FCVAR_PLUGIN, true, 10.0);
801
802 cvar_SoundsEnabled = CreateConVar("l4d_stats_soundsenabled", "1", "Play sounds on certain events", FCVAR_PLUGIN, true, 0.0, true, 1.0);
803
804 // Make that config!
805 AutoExecConfig(true, "l4d_stats");
806
807 // Personal Gain Events
808 HookEvent("player_death", event_PlayerDeath);
809 HookEvent("infected_death", event_InfectedDeath);
810 HookEvent("tank_killed", event_TankKilled);
811 if (ServerVersion == SERVER_VERSION_L4D1)
812 {
813 HookEvent("weapon_given", event_GivePills);
814 }
815 else
816 {
817 HookEvent("defibrillator_used", event_DefibPlayer);
818 }
819 HookEvent("heal_success", event_HealPlayer);
820 HookEvent("revive_success", event_RevivePlayer);
821 HookEvent("tongue_pull_stopped", event_TongueSave);
822 HookEvent("choke_stopped", event_ChokeSave);
823 HookEvent("pounce_stopped", event_PounceSave);
824 HookEvent("lunge_pounce", event_PlayerPounced);
825 HookEvent("player_ledge_grab", event_PlayerLedge);
826 HookEvent("player_falldamage", event_PlayerFallDamage);
827 HookEvent("melee_kill", event_MeleeKill);
828
829 // Personal Loss Events
830 HookEvent("friendly_fire", event_FriendlyFire);
831 HookEvent("player_incapacitated", event_PlayerIncap);
832
833 // Team Gain Events
834 HookEvent("finale_vehicle_leaving", event_CampaignWin);
835 HookEvent("map_transition", event_MapTransition);
836 HookEvent("create_panic_event", event_PanicEvent);
837 HookEvent("player_now_it", event_PlayerBlind);
838 HookEvent("player_no_longer_it", event_PlayerBlindEnd);
839
840 // Team Loss Events / Misc. Events
841 if (ServerVersion == SERVER_VERSION_L4D1)
842 {
843 HookEvent("award_earned", event_Award_L4D1);
844 }
845 else
846 {
847 HookEvent("award_earned", event_Award_L4D2);
848 }
849 HookEvent("witch_spawn", event_WitchSpawn);
850 HookEvent("witch_killed", event_WitchCrowned);
851 HookEvent("witch_harasser_set", event_WitchDisturb);
852 HookEvent("round_start", event_RoundStart);
853
854 // Record player positions when an ability is used
855 HookEvent("ability_use", event_AbilityUse);
856
857 // Set player specific counters (BoomerHitCounter etc)
858 HookEvent("player_spawn", event_PlayerSpawn);
859
860 // Set player specific counters (BoomerHitCounter etc)
861 HookEvent("player_hurt", event_PlayerHurt);
862
863 // Smoker stats
864 HookEvent("tongue_grab", event_SmokerGrap);
865 HookEvent("tongue_release", event_SmokerRelease);
866 if (ServerVersion == SERVER_VERSION_L4D1)
867 {
868 HookEvent("tongue_broke_victim_died", event_SmokerRelease);
869 }
870 HookEvent("choke_end", event_SmokerRelease);
871 HookEvent("tongue_broke_bent", event_SmokerRelease);
872 // Hooked previously ^
873 //HookEvent("choke_stopped", event_SmokerRelease);
874 //HookEvent("tongue_pull_stopped", event_SmokerRelease);
875
876 // Hunter stats
877 HookEvent("pounce_end", event_HunterRelease);
878
879 if (ServerVersion != SERVER_VERSION_L4D1)
880 {
881 // Spitter stats
882 //HookEvent("spitter_killed", event_SpitterKilled);
883
884 // Jockey stats
885 HookEvent("jockey_ride", event_JockeyStart);
886 HookEvent("jockey_ride_end", event_JockeyRelease);
887 HookEvent("jockey_killed", event_JockeyKilled);
888
889 // Charger stats
890 HookEvent("charger_impact", event_ChargerImpact);
891 HookEvent("charger_killed", event_ChargerKilled);
892 HookEvent("charger_carry_start", event_ChargerCarryStart);
893 HookEvent("charger_carry_end", event_ChargerCarryRelease);
894 HookEvent("charger_pummel_start", event_ChargerPummelStart);
895 HookEvent("charger_pummel_end", event_ChargerPummelRelease);
896
897 // Misc L4D2 events
898 HookEvent("upgrade_pack_used", event_UpgradePackAdded);
899 HookEvent("gascan_pour_completed", event_GascanPoured);
900 HookEvent("triggered_car_alarm", event_CarAlarm);
901 HookEvent("survival_round_start", event_SurvivalStart); // Timed Maps event
902 HookEvent("scavenge_round_halftime", event_ScavengeHalftime);
903 HookEvent("scavenge_round_start", event_ScavengeRoundStart);
904 }
905
906 // Achievements
907 HookEvent("achievement_earned", event_Achievement);
908
909 // Timed Maps events
910 HookEvent("door_open", event_DoorOpen, EventHookMode_Post); // When the saferoom door opens...
911 HookEvent("player_left_start_area", event_StartArea, EventHookMode_Post); // When a survivor leaves the start area...
912 HookEvent("player_team", event_PlayerTeam, EventHookMode_Post); // When a survivor changes team...
913
914 // Startup the plugin's timers
915 //CreateTimer(1.0, InitPlayers); // Called in OnMapStart
916 CreateTimer(60.0, timer_UpdatePlayers, INVALID_HANDLE, TIMER_REPEAT);
917 UpdateTimer = CreateTimer(GetConVarFloat(cvar_UpdateRate), timer_ShowTimerScore, INVALID_HANDLE, TIMER_REPEAT);
918 HookConVarChange(cvar_UpdateRate, action_TimerChanged);
919 HookConVarChange(cvar_DbPrefix, action_DbPrefixChanged);
920
921 // Gamemode
922 GetConVarString(cvar_Gamemode, CurrentGamemode, sizeof(CurrentGamemode));
923 CurrentGamemodeID = GetCurrentGamemodeID();
924 SetCurrentGamemodeName();
925 HookConVarChange(cvar_Gamemode, action_GamemodeChanged);
926 HookConVarChange(cvar_Difficulty, action_DifficultyChanged);
927
928 //RegConsoleCmd("l4d_stats_test", cmd_StatsTest);
929
930 MapTimingSurvivors = CreateTrie();
931 MapTimingInfected = CreateTrie();
932 FriendlyFireDamageTrie = CreateTrie();
933 PlayerRankVoteTrie = CreateTrie();
934
935 new Handle:topmenu;
936 if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE))
937 OnAdminMenuReady(topmenu);
938
939 if (FileExists("addons/sourcemod/gamedata/l4d_stats.txt"))
940 {
941 // SDK handles for team shuffle
942 L4DStatsConf = LoadGameConfigFile("l4d_stats");
943 if (L4DStatsConf == INVALID_HANDLE)
944 {
945 LogError("Could not load gamedata/l4d_stats.txt");
946 }
947 else
948 {
949 StartPrepSDKCall(SDKCall_Player);
950 PrepSDKCall_SetFromConf(L4DStatsConf, SDKConf_Signature, "SetHumanSpec");
951 PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);
952 L4DStatsSHS = EndPrepSDKCall();
953
954 StartPrepSDKCall(SDKCall_Player);
955 PrepSDKCall_SetFromConf(L4DStatsConf, SDKConf_Signature, "TakeOverBot");
956 PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain);
957 L4DStatsTOB = EndPrepSDKCall();
958 }
959 }
960 else
961 {
962 LogMessage("Rank Vote is disabled because could not load gamedata/l4d_stats.txt");
963 }
964
965 // Sounds
966 if (!IsSoundPrecached(SOUND_RANKVOTE))
967 {
968 EnableSounds_Rankvote = PrecacheSound(SOUND_RANKVOTE); // Sound from rankvote team switch
969 }
970 else
971 {
972 EnableSounds_Rankvote = true;
973 }
974
975 if (!IsSoundPrecached(StatsSound_MapTime_Start))
976 {
977 EnableSounds_Maptime_Start = PrecacheSound(StatsSound_MapTime_Start); // Sound map timer start
978 }
979 else
980 {
981 EnableSounds_Maptime_Start = true;
982 }
983
984 if (!IsSoundPrecached(StatsSound_MapTime_Improve))
985 {
986 EnableSounds_Maptime_Improve = PrecacheSound(StatsSound_MapTime_Improve); // Sound from improving personal map timing
987 }
988 else
989 {
990 EnableSounds_Maptime_Improve = true;
991 }
992
993 if (!IsSoundPrecached(StatsSound_Rankmenu_Show))
994 {
995 EnableSounds_Rankmenu_Show = PrecacheSound(StatsSound_Rankmenu_Show); // Sound from showing the rankmenu
996 }
997 else
998 {
999 EnableSounds_Rankmenu_Show = true;
1000 }
1001
1002 if (!IsSoundPrecached(StatsSound_Boomer_Vomit))
1003 {
1004 EnableSounds_Boomer_Vomit = PrecacheSound(StatsSound_Boomer_Vomit); // Sound from a successful boomer vomit (Perfect Blindness)
1005 }
1006 else
1007 {
1008 EnableSounds_Boomer_Vomit = true;
1009 }
1010
1011 if (!IsSoundPrecached(StatsSound_Hunter_Perfect))
1012 {
1013 EnableSounds_Hunter_Perfect = PrecacheSound(StatsSound_Hunter_Perfect); // Sound from a hunter perfect pounce (Death From Above)
1014 }
1015 else
1016 {
1017 EnableSounds_Hunter_Perfect = true;
1018 }
1019
1020 if (!IsSoundPrecached(StatsSound_Tank_Bulldozer))
1021 {
1022 EnableSounds_Tank_Bulldozer = PrecacheSound(StatsSound_Tank_Bulldozer); // Sound from a tank bulldozer
1023 }
1024 else
1025 {
1026 EnableSounds_Tank_Bulldozer = true;
1027 }
1028
1029 if (ServerVersion != SERVER_VERSION_L4D1)
1030 {
1031 if (!IsSoundPrecached(SOUND_CHARGER_RAM))
1032 {
1033 EnableSounds_Charger_Ram = PrecacheSound(SOUND_CHARGER_RAM); // Sound from a charger scattering ram
1034 }
1035 else
1036 {
1037 EnableSounds_Charger_Ram = true;
1038 }
1039 }
1040 else
1041 {
1042 EnableSounds_Charger_Ram = false;
1043 }
1044}
1045
1046public OnConfigsExecuted()
1047{
1048 GetConVarString(cvar_DbPrefix, DbPrefix, sizeof(DbPrefix));
1049
1050 // Init MySQL connections
1051 if (!ConnectDB())
1052 {
1053 SetFailState("Connecting to database failed. Read error log for further details.");
1054 return;
1055 }
1056
1057 if (CommandsRegistered)
1058 return;
1059
1060 CommandsRegistered = true;
1061
1062 // Register chat commands for rank panels
1063 RegConsoleCmd("say", cmd_Say);
1064 RegConsoleCmd("say_team", cmd_Say);
1065
1066 // Register console commands for rank panels
1067 RegConsoleCmd("sm_rank", cmd_ShowRank);
1068 RegConsoleCmd("sm_top10", cmd_ShowTop10);
1069 RegConsoleCmd("sm_top10ppm", cmd_ShowTop10PPM);
1070 RegConsoleCmd("sm_nextrank", cmd_ShowNextRank);
1071 RegConsoleCmd("sm_showtimer", cmd_ShowTimedMapsTimer);
1072 RegConsoleCmd("sm_showrank", cmd_ShowRanks);
1073 RegConsoleCmd("sm_showppm", cmd_ShowPPMs);
1074 RegConsoleCmd("sm_rankvote", cmd_RankVote);
1075 RegConsoleCmd("sm_timedmaps", cmd_TimedMaps);
1076 RegConsoleCmd("sm_maptimes", cmd_MapTimes);
1077 RegConsoleCmd("sm_showmaptimes", cmd_ShowMapTimes);
1078 RegConsoleCmd("sm_rankmenu", cmd_ShowRankMenu);
1079 RegConsoleCmd("sm_rankmutetoggle", cmd_ToggleClientRankMute);
1080 RegConsoleCmd("sm_rankmute", cmd_ClientRankMute);
1081 RegConsoleCmd("sm_showmotd", cmd_ShowMotd);
1082
1083 // Register administrator command for clearing all stats (BE CAREFUL)
1084 //RegAdminCmd("sm_rank_admin", cmd_RankAdmin, ADMFLAG_ROOT, "Display admin panel for Rank");
1085 RegAdminCmd("sm_rank_clear", cmd_ClearRank, ADMFLAG_ROOT, "Clear all stats from database (asks a confirmation before clearing the database)");
1086 RegAdminCmd("sm_rank_shuffle", cmd_ShuffleTeams, ADMFLAG_KICK, "Shuffle teams by player PPM (Points Per Minute)");
1087 RegAdminCmd("sm_rank_motd", cmd_SetMotd, ADMFLAG_GENERIC, "Set Message Of The Day");
1088
1089 // Read the settings etc from the database.
1090 ReadDb();
1091}
1092
1093// Load our categories and menus
1094
1095public OnAdminMenuReady(Handle:TopMenu)
1096{
1097 // Block us from being called twice
1098 if (TopMenu == RankAdminMenu)
1099 return;
1100
1101 RankAdminMenu = TopMenu;
1102
1103 // Add a category to the SourceMod menu called "Player Stats"
1104 AddToTopMenu(RankAdminMenu, "Player Stats", TopMenuObject_Category, ClearRankCategoryHandler, INVALID_TOPMENUOBJECT);
1105
1106 // Get a handle for the catagory we just added so we can add items to it
1107 new TopMenuObject:statscommands = FindTopMenuCategory(RankAdminMenu, "Player Stats");
1108
1109 // Don't attempt to add items to the catagory if for some reason the catagory doesn't exist
1110 if (statscommands == INVALID_TOPMENUOBJECT)
1111 return;
1112
1113 // The order that items are added to menus has no relation to the order that they appear. Items are sorted alphabetically automatically
1114 // Assign the menus to global values so we can easily check what a menu is when it is chosen
1115 MenuClearPlayers = AddToTopMenu(RankAdminMenu, "sm_rank_admin_clearplayers", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_clearplayers", ADMFLAG_ROOT);
1116 MenuClearMaps = AddToTopMenu(RankAdminMenu, "sm_rank_admin_clearallmaps", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_clearallmaps", ADMFLAG_ROOT);
1117 MenuClearAll = AddToTopMenu(RankAdminMenu, "sm_rank_admin_clearall", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_clearall", ADMFLAG_ROOT);
1118 MenuClearTimedMaps = AddToTopMenu(RankAdminMenu, "sm_rank_admin_cleartimedmaps", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_cleartimedmaps", ADMFLAG_ROOT);
1119 MenuRemoveCustomMaps = AddToTopMenu(RankAdminMenu, "sm_rank_admin_removecustom", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_removecustom", ADMFLAG_ROOT);
1120 MenuCleanPlayers = AddToTopMenu(RankAdminMenu, "sm_rank_admin_removeplayers", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_removeplayers", ADMFLAG_ROOT);
1121 MenuClear = AddToTopMenu(RankAdminMenu, "sm_rank_admin_clear", TopMenuObject_Item, ClearRankTopItemHandler, statscommands, "sm_rank_admin_clear", ADMFLAG_ROOT);
1122}
1123
1124public OnLibraryRemoved(const String:name[])
1125{
1126 if (StrEqual(name, "adminmenu"))
1127 RankAdminMenu = INVALID_HANDLE;
1128}
1129
1130// This handles the top level "Player Stats" category and how it is displayed on the core admin menu
1131
1132public ClearRankCategoryHandler(Handle:topmenu, TopMenuAction:action, TopMenuObject:object_id, client, String:buffer[], maxlength)
1133{
1134 if (action == TopMenuAction_DisplayOption)
1135 Format(buffer, maxlength, "Player Stats");
1136 else if (action == TopMenuAction_DisplayTitle)
1137 Format(buffer, maxlength, "Player Stats:");
1138}
1139
1140public Action:Menu_CreateClearMenu(client, args)
1141{
1142 new Handle:menu = CreateMenu(Menu_CreateClearMenuHandler);
1143
1144 SetMenuTitle(menu, "Clear:");
1145 SetMenuExitBackButton(menu, true);
1146 SetMenuExitButton(menu, true);
1147
1148 AddMenuItem(menu, "cps", "Clear stats from currently playing player...");
1149 AddMenuItem(menu, "ctm", "Clear timed maps...");
1150
1151 DisplayMenu(menu, client, 30);
1152
1153 return Plugin_Handled;
1154}
1155
1156public Menu_CreateClearMenuHandler(Handle:menu, MenuAction:action, param1, param2)
1157{
1158 switch (action)
1159 {
1160 case MenuAction_Select:
1161 {
1162 switch (param2)
1163 {
1164 case 0:
1165 {
1166 DisplayClearPanel(param1);
1167 }
1168 case 1:
1169 {
1170 Menu_CreateClearTMMenu(param1, 0);
1171 }
1172 }
1173 }
1174 case MenuAction_End:
1175 {
1176 CloseHandle(menu);
1177 }
1178 case MenuAction_Cancel:
1179 {
1180 if (param2 == MenuCancel_ExitBack && RankAdminMenu != INVALID_HANDLE)
1181 DisplayTopMenu(RankAdminMenu, param1, TopMenuPosition_LastCategory);
1182 }
1183 }
1184}
1185
1186public Action:Menu_CreateClearTMMenu(client, args)
1187{
1188 new Handle:menu = CreateMenu(Menu_CreateClearTMMenuHandler);
1189
1190 SetMenuTitle(menu, "Clear Timed Maps:");
1191 SetMenuExitBackButton(menu, true);
1192 SetMenuExitButton(menu, true);
1193
1194 AddMenuItem(menu, "ctma", "All");
1195 AddMenuItem(menu, "ctmc", "Coop");
1196 AddMenuItem(menu, "ctmsu", "Survival");
1197 AddMenuItem(menu, "ctmr", "Realism");
1198 AddMenuItem(menu, "ctmm", "Mutations");
1199
1200 DisplayMenu(menu, client, 30);
1201
1202 return Plugin_Handled;
1203}
1204
1205public Menu_CreateClearTMMenuHandler(Handle:menu, MenuAction:action, param1, param2)
1206{
1207 switch (action)
1208 {
1209 case MenuAction_Select:
1210 {
1211 switch (param2)
1212 {
1213 case 0:
1214 {
1215 DisplayYesNoPanel(param1, "Do you really want to clear all map timings?", ClearTMAllPanelHandler);
1216 }
1217 case 1:
1218 {
1219 DisplayYesNoPanel(param1, "Do you really want to clear all Coop map timings?", ClearTMCoopPanelHandler);
1220 }
1221 case 2:
1222 {
1223 DisplayYesNoPanel(param1, "Do you really want to clear all Survival map timings?", ClearTMSurvivalPanelHandler);
1224 }
1225 case 3:
1226 {
1227 DisplayYesNoPanel(param1, "Do you really want to clear all Realism map timings?", ClearTMRealismPanelHandler);
1228 }
1229 case 4:
1230 {
1231 DisplayYesNoPanel(param1, "Do you really want to clear all Mutations map timings?", ClearTMMutationsPanelHandler);
1232 }
1233 }
1234 }
1235 case MenuAction_End:
1236 {
1237 CloseHandle(menu);
1238 }
1239 case MenuAction_Cancel:
1240 {
1241 if (param2 == MenuCancel_ExitBack && RankAdminMenu != INVALID_HANDLE)
1242 {
1243 DisplayTopMenu(RankAdminMenu, param1, TopMenuPosition_LastCategory);
1244 }
1245 }
1246 }
1247}
1248
1249// This deals with what happens someone opens the "Player Stats" category from the menu
1250public ClearRankTopItemHandler(Handle:topmenu, TopMenuAction:action, TopMenuObject:object_id, client, String:buffer[], maxlength)
1251{
1252 // When an item is displayed to a player tell the menu to format the item
1253 if (action == TopMenuAction_DisplayOption)
1254 {
1255 if (object_id == MenuClearPlayers)
1256 {
1257 Format(buffer, maxlength, "Clear players");
1258 }
1259 else if (object_id == MenuClearMaps)
1260 {
1261 Format(buffer, maxlength, "Clear maps");
1262 }
1263 else if (object_id == MenuClearAll)
1264 {
1265 Format(buffer, maxlength, "Clear all");
1266 }
1267 else if (object_id == MenuClearTimedMaps)
1268 {
1269 Format(buffer, maxlength, "Clear timed maps");
1270 }
1271 else if (object_id == MenuRemoveCustomMaps)
1272 {
1273 Format(buffer, maxlength, "Remove custom maps");
1274 }
1275 else if (object_id == MenuCleanPlayers)
1276 {
1277 Format(buffer, maxlength, "Clean players");
1278 }
1279 else if (object_id == MenuClear)
1280 {
1281 Format(buffer, maxlength, "Clear...");
1282 }
1283 }
1284
1285 // When an item is selected do the following
1286 else if (action == TopMenuAction_SelectOption)
1287 {
1288 if (object_id == MenuClearPlayers)
1289 {
1290 DisplayYesNoPanel(client, "Do you really want to clear the player stats?", ClearPlayersPanelHandler);
1291 }
1292 else if (object_id == MenuClearMaps)
1293 {
1294 DisplayYesNoPanel(client, "Do you really want to clear the map stats?", ClearMapsPanelHandler);
1295 }
1296 else if (object_id == MenuClearAll)
1297 {
1298 DisplayYesNoPanel(client, "Do you really want to clear all stats?", ClearAllPanelHandler);
1299 }
1300 else if (object_id == MenuClearTimedMaps)
1301 {
1302 DisplayYesNoPanel(client, "Do you really want to clear all map timings?", ClearTMAllPanelHandler);
1303 }
1304 else if (object_id == MenuRemoveCustomMaps)
1305 {
1306 DisplayYesNoPanel(client, "Do you really want to remove the custom maps?", RemoveCustomMapsPanelHandler);
1307 }
1308 else if (object_id == MenuCleanPlayers)
1309 {
1310 DisplayYesNoPanel(client, "Do you really want to clean the player stats?", CleanPlayersPanelHandler);
1311 }
1312 else if (object_id == MenuClear)
1313 {
1314 Menu_CreateClearMenu(client, 0);
1315 }
1316 }
1317}
1318
1319// Reset all boolean variables when a map changes.
1320
1321public OnMapStart()
1322{
1323 GetConVarString(cvar_Gamemode, CurrentGamemode, sizeof(CurrentGamemode));
1324 CurrentGamemodeID = GetCurrentGamemodeID();
1325 SetCurrentGamemodeName();
1326 ResetVars();
1327}
1328
1329// Init player on connect, and update total rank and client rank.
1330
1331public OnClientPostAdminCheck(client)
1332{
1333 if (db == INVALID_HANDLE)
1334 {
1335 return;
1336 }
1337
1338 InitializeClientInf(client);
1339 PostAdminCheckRetryCounter[client] = 0;
1340
1341 if (IsClientBot(client))
1342 {
1343 return;
1344 }
1345
1346 CreateTimer(1.0, ClientPostAdminCheck, client);
1347}
1348
1349public Action:ClientPostAdminCheck(Handle:timer, any:client)
1350{
1351 if (!IsClientInGame(client))
1352 {
1353 if (PostAdminCheckRetryCounter[client]++ < 10)
1354 {
1355 CreateTimer(3.0, ClientPostAdminCheck, client);
1356 }
1357
1358 return;
1359 }
1360
1361 StartRankChangeCheck(client);
1362
1363 decl String:SteamID[MAX_LINE_WIDTH];
1364 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
1365
1366 CheckPlayerDB(client);
1367
1368 TimerPoints[client] = 0;
1369 TimerKills[client] = 0;
1370 TimerHeadshots[client] = 0;
1371
1372 CreateTimer(10.0, RankConnect, client);
1373 CreateTimer(15.0, AnnounceConnect, client);
1374
1375 AnnouncePlayerConnect(client);
1376}
1377
1378public OnPluginEnd()
1379{
1380 if (db == INVALID_HANDLE)
1381 return;
1382
1383 new maxplayers = GetMaxClients();
1384
1385 for (new i = 1; i <= maxplayers; i++)
1386 {
1387 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
1388 {
1389 switch (GetClientTeam(i))
1390 {
1391 case TEAM_SURVIVORS:
1392 InterstitialPlayerUpdate(i);
1393 case TEAM_INFECTED:
1394 DoInfectedFinalChecks(i);
1395 }
1396 }
1397 }
1398
1399 CloseHandle(db);
1400 db = INVALID_HANDLE;
1401
1402 CommandsRegistered = false;
1403
1404 //if (ClearPlayerMenu != INVALID_HANDLE)
1405 //{
1406 // CloseHandle(ClearPlayerMenu);
1407 // ClearPlayerMenu = INVALID_HANDLE;
1408 //}
1409}
1410
1411// Show rank on connect.
1412
1413public Action:RankConnect(Handle:timer, any:value)
1414{
1415 if (GetConVarBool(cvar_RankOnJoin) && !InvalidGameMode())
1416 {
1417 cmd_ShowRank(value, 0);
1418 }
1419}
1420
1421// Announce on player connect!
1422
1423public Action:AnnounceConnect(Handle:timer, any:client)
1424{
1425 if (!GetConVarBool(cvar_AnnounceMode))
1426 {
1427 return;
1428 }
1429
1430 if (!IsClientConnected(client) || !IsClientInGame(client))
1431 {
1432 if (AnnounceCounter[client] > 10)
1433 {
1434 AnnounceCounter[client] = 0;
1435 }
1436 else
1437 {
1438 AnnounceCounter[client]++;
1439 CreateTimer(5.0, AnnounceConnect, client);
1440 }
1441
1442 return;
1443 }
1444
1445 AnnounceCounter[client]++;
1446
1447 ShowMOTD(client);
1448 StatsPrintToChat2(client, true, "Type \x05RANKMENU \x01to operate \x04%s\x01!", PLUGIN_NAME);
1449}
1450
1451// Update the player's interstitial stats, since they may have
1452// gotten points between the last update and when they disconnect.
1453
1454public OnClientDisconnect(client)
1455{
1456 InitializeClientInf(client);
1457 PlayerRankVote[client] = RANKVOTE_NOVOTE;
1458 ClientRankMute[client] = false;
1459
1460 if (TimerRankChangeCheck[client] != INVALID_HANDLE)
1461 CloseHandle(TimerRankChangeCheck[client]);
1462
1463 TimerRankChangeCheck[client] = INVALID_HANDLE;
1464
1465 if (IsClientBot(client))
1466 return;
1467
1468 if (MapTimingStartTime >= 0.0)
1469 {
1470 decl String:ClientID[MAX_LINE_WIDTH];
1471 GetClientRankAuthString(client, ClientID, sizeof(ClientID));
1472
1473 RemoveFromTrie(MapTimingSurvivors, ClientID);
1474 RemoveFromTrie(MapTimingInfected, ClientID);
1475 }
1476
1477 if (IsClientInGame(client))
1478 {
1479 switch (GetClientTeam(client))
1480 {
1481 case TEAM_SURVIVORS:
1482 InterstitialPlayerUpdate(client);
1483 case TEAM_INFECTED:
1484 DoInfectedFinalChecks(client);
1485 }
1486 }
1487
1488 new maxplayers = GetMaxClients();
1489
1490 for (new i = 1; i <= maxplayers; i++)
1491 {
1492 if (i != client && IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
1493 return;
1494 }
1495
1496 // If we get this far, ALL HUMAN PLAYERS LEFT THE SERVER
1497 CampaignOver = true;
1498
1499 if (RankVoteTimer != INVALID_HANDLE)
1500 {
1501 CloseHandle(RankVoteTimer);
1502 RankVoteTimer = INVALID_HANDLE;
1503 }
1504}
1505
1506public action_LanChanged(Handle:convar, const String:oldValue[], const String:newValue[])
1507{
1508 if (GetConVarInt(cvar_Lan))
1509 LogMessage("ATTENTION! %s in LAN environment is based on IP address rather than Steam ID. The statistics are not reliable when they are base on IP!", PLUGIN_NAME);
1510}
1511
1512// Update the Database prefix when the Cvar is changed.
1513
1514public action_DbPrefixChanged(Handle:convar, const String:oldValue[], const String:newValue[])
1515{
1516 if (convar == cvar_DbPrefix)
1517 {
1518 if (StrEqual(DbPrefix, newValue))
1519 return;
1520
1521 if (db != INVALID_HANDLE && !CheckDatabaseValidity(DbPrefix))
1522 {
1523 strcopy(DbPrefix, sizeof(DbPrefix), oldValue);
1524 SetConVarString(cvar_DbPrefix, DbPrefix);
1525 }
1526 else
1527 strcopy(DbPrefix, sizeof(DbPrefix), newValue);
1528 }
1529}
1530
1531// Update the Update Timer when the Cvar is changed.
1532
1533public action_TimerChanged(Handle:convar, const String:oldValue[], const String:newValue[])
1534{
1535 if (convar == cvar_UpdateRate)
1536 {
1537 CloseHandle(UpdateTimer);
1538
1539 new NewTime = StringToInt(newValue);
1540 UpdateTimer = CreateTimer(float(NewTime), timer_ShowTimerScore, INVALID_HANDLE, TIMER_REPEAT);
1541 }
1542}
1543
1544// Update the CurrentGamemode when the Cvar is changed.
1545
1546public action_DifficultyChanged(Handle:convar, const String:oldValue[], const String:newValue[])
1547{
1548 if (convar == cvar_Difficulty)
1549 {
1550 MapTimingStartTime = -1.0;
1551 MapTimingBlocked = true;
1552 }
1553}
1554
1555// Update the CurrentGamemode when the Cvar is changed.
1556
1557public action_GamemodeChanged(Handle:convar, const String:oldValue[], const String:newValue[])
1558{
1559 if (convar == cvar_Gamemode)
1560 {
1561 GetConVarString(cvar_Gamemode, CurrentGamemode, sizeof(CurrentGamemode));
1562 CurrentGamemodeID = GetCurrentGamemodeID();
1563 SetCurrentGamemodeName();
1564 }
1565}
1566
1567public SetCurrentGamemodeName()
1568{
1569 switch (CurrentGamemodeID)
1570 {
1571 case GAMEMODE_COOP:
1572 {
1573 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Coop");
1574 }
1575 case GAMEMODE_VERSUS:
1576 {
1577 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Versus");
1578 }
1579 case GAMEMODE_REALISM:
1580 {
1581 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Realism");
1582 }
1583 case GAMEMODE_SURVIVAL:
1584 {
1585 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Survival");
1586 }
1587 case GAMEMODE_SCAVENGE:
1588 {
1589 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Scavenge");
1590 }
1591 case GAMEMODE_REALISMVERSUS:
1592 {
1593 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Realism Versus");
1594 }
1595 case GAMEMODE_OTHERMUTATIONS:
1596 {
1597 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Mutations");
1598 }
1599 default:
1600 {
1601 Format(CurrentGamemodeLabel, sizeof(CurrentGamemodeLabel), "Unknown");
1602 }
1603 }
1604
1605 if (CurrentGamemodeID == GAMEMODE_OTHERMUTATIONS)
1606 {
1607 GetConVarString(cvar_Gamemode, CurrentMutation, sizeof(CurrentMutation));
1608 }
1609 else
1610 {
1611 CurrentMutation[0] = 0;
1612 }
1613}
1614
1615// Scavenge round start event (occurs when door opens or players leave the start area)
1616
1617public Action:event_ScavengeRoundStart(Handle:event, const String:name[], bool:dontBroadcast)
1618{
1619 event_RoundStart(event, name, dontBroadcast);
1620
1621 StartMapTiming();
1622}
1623
1624// Called after the connection to the database is established
1625
1626public Action:event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
1627{
1628 ResetVars();
1629 CheckCurrentMapDB();
1630
1631 MapTimingStartTime = 0.0;
1632 MapTimingBlocked = false;
1633
1634 ResetRankChangeCheck();
1635}
1636
1637// Make connection to database.
1638
1639bool:ConnectDB()
1640{
1641 if (db != INVALID_HANDLE)
1642 return true;
1643
1644 if (SQL_CheckConfig(DB_CONF_NAME))
1645 {
1646 new String:Error[256];
1647 db = SQL_Connect(DB_CONF_NAME, true, Error, sizeof(Error));
1648
1649 if (db == INVALID_HANDLE)
1650 {
1651 LogError("Failed to connect to database: %s", Error);
1652 return false;
1653 }
1654 else if (!SQL_FastQuery(db, "SET NAMES 'utf8'"))
1655 {
1656 if (SQL_GetError(db, Error, sizeof(Error)))
1657 LogError("Failed to update encoding to UTF8: %s", Error);
1658 else
1659 LogError("Failed to update encoding to UTF8: unknown");
1660 }
1661
1662 if (!CheckDatabaseValidity(DbPrefix))
1663 {
1664 LogError("Database is missing required table or tables.");
1665 return false;
1666 }
1667 }
1668 else
1669 {
1670 LogError("Databases.cfg missing '%s' entry!", DB_CONF_NAME);
1671 return false;
1672 }
1673
1674 return true;
1675}
1676
1677bool:CheckDatabaseValidity(const String:Prefix[])
1678{
1679 if (!DoFastQuery(0, "SELECT * FROM %splayers WHERE 1 = 2", Prefix) ||
1680 !DoFastQuery(0, "SELECT * FROM %smaps WHERE 1 = 2", Prefix) ||
1681 !DoFastQuery(0, "SELECT * FROM %stimedmaps WHERE 1 = 2", Prefix) ||
1682 !DoFastQuery(0, "SELECT * FROM %ssettings WHERE 1 = 2", Prefix))
1683 {
1684 return false;
1685 }
1686
1687 return true;
1688}
1689
1690public Action:timer_ProtectedFriendly(Handle:timer, any:data)
1691{
1692 TimerProtectedFriendly[data] = INVALID_HANDLE;
1693 new ProtectedFriendlies = ProtectedFriendlyCounter[data];
1694 ProtectedFriendlyCounter[data] = 0;
1695
1696 if (data == 0 || !IsClientConnected(data) || !IsClientInGame(data) || IsClientBot(data))
1697 return;
1698
1699 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Protect) * ProtectedFriendlies, 2, 3, TEAM_SURVIVORS);
1700 AddScore(data, Score);
1701
1702 UpdateMapStat("points", Score);
1703
1704 decl String:UpdatePoints[32];
1705 decl String:UserID[MAX_LINE_WIDTH];
1706 GetClientRankAuthString(data, UserID, sizeof(UserID));
1707 decl String:UserName[MAX_LINE_WIDTH];
1708 GetClientName(data, UserName, sizeof(UserName));
1709
1710 switch (CurrentGamemodeID)
1711 {
1712 case GAMEMODE_VERSUS:
1713 {
1714 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
1715 }
1716 case GAMEMODE_REALISM:
1717 {
1718 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
1719 }
1720 case GAMEMODE_SURVIVAL:
1721 {
1722 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
1723 }
1724 case GAMEMODE_SCAVENGE:
1725 {
1726 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
1727 }
1728 case GAMEMODE_REALISMVERSUS:
1729 {
1730 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
1731 }
1732 case GAMEMODE_OTHERMUTATIONS:
1733 {
1734 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
1735 }
1736 default:
1737 {
1738 Format(UpdatePoints, sizeof(UpdatePoints), "points");
1739 }
1740 }
1741
1742 decl String:query[1024];
1743 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_protect = award_protect + %i WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, ProtectedFriendlies, UserID);
1744 SendSQLUpdate(query);
1745
1746 if (Score > 0)
1747 {
1748 new Mode = GetConVarInt(cvar_AnnounceMode);
1749
1750 if (Mode == 1 || Mode == 2)
1751 StatsPrintToChat(data, "You have earned \x04%i \x01points for Protecting \x05%i friendlies\x01!", Score, ProtectedFriendlies);
1752 else if (Mode == 3)
1753 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Protecting \x05%i friendlies\x01!", UserName, Score, ProtectedFriendlies);
1754 }
1755}
1756// Team infected damage score
1757
1758public Action:timer_InfectedDamageCheck(Handle:timer, any:data)
1759{
1760 TimerInfectedDamageCheck[data] = INVALID_HANDLE;
1761
1762 if (data == 0 || IsClientBot(data))
1763 return;
1764
1765 new InfectedDamage = GetConVarInt(cvar_InfectedDamage);
1766
1767 new Score = 0;
1768 new DamageCounter = 0;
1769
1770 if (InfectedDamage > 1)
1771 {
1772 if (InfectedDamageCounter[data] < InfectedDamage)
1773 return;
1774
1775 new TotalDamage = InfectedDamageCounter[data];
1776
1777 while (TotalDamage >= InfectedDamage)
1778 {
1779 DamageCounter += InfectedDamage;
1780 TotalDamage -= InfectedDamage;
1781 Score++;
1782 }
1783 }
1784 else
1785 {
1786 DamageCounter = InfectedDamageCounter[data];
1787 Score = InfectedDamageCounter[data];
1788 }
1789
1790 Score = ModifyScoreDifficultyFloat(Score, 0.75, 0.5, TEAM_INFECTED);
1791
1792 if (Score > 0)
1793 {
1794 InfectedDamageCounter[data] -= DamageCounter;
1795
1796 new Mode = GetConVarInt(cvar_AnnounceMode);
1797
1798 decl String:query[1024];
1799 decl String:iID[MAX_LINE_WIDTH];
1800
1801 GetClientRankAuthString(data, iID, sizeof(iID));
1802
1803 if (CurrentGamemodeID == GAMEMODE_VERSUS)
1804 {
1805 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i WHERE steamid = '%s'", DbPrefix, Score, iID);
1806 }
1807 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
1808 {
1809 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i WHERE steamid = '%s'", DbPrefix, Score, iID);
1810 }
1811 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
1812 {
1813 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i WHERE steamid = '%s'", DbPrefix, Score, iID);
1814 }
1815 else
1816 {
1817 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i WHERE steamid = '%s'", DbPrefix, Score, iID);
1818 }
1819
1820 SendSQLUpdate(query);
1821
1822 UpdateMapStat("points_infected", Score);
1823
1824 if (Mode == 1 || Mode == 2)
1825 {
1826 if (InfectedDamage > 1)
1827 StatsPrintToChat(data, "You have earned \x04%i \x01points for doing \x04%i \x01points of damage to the Survivors!", Score, DamageCounter);
1828 else
1829 StatsPrintToChat(data, "You have earned \x04%i \x01points for doing damage to the Survivors!", Score, DamageCounter);
1830 }
1831 else if (Mode == 3)
1832 {
1833 decl String:Name[MAX_LINE_WIDTH];
1834 GetClientName(data, Name, sizeof(Name));
1835 if (InfectedDamage > 1)
1836 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for doing \x04%i \x01points of damage to the Survivors!", Name, Score, DamageCounter);
1837 else
1838 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for doing damage to the Survivors!", Name, Score, DamageCounter);
1839 }
1840 }
1841}
1842
1843// Get Boomer points
1844
1845GetBoomerPoints(VictimCount)
1846{
1847 if (VictimCount <= 0)
1848 return 0;
1849
1850 return GetConVarInt(cvar_BoomerSuccess) * VictimCount;
1851}
1852
1853// Calculate Boomer vomit hits and check Boomer Perfect Blindness award
1854
1855public Action:timer_BoomerBlindnessCheck(Handle:timer, any:data)
1856{
1857 TimerBoomerPerfectCheck[data] = INVALID_HANDLE;
1858
1859 if (data > 0 && !IsClientBot(data) && IsClientInGame(data) && GetClientTeam(data) == TEAM_INFECTED && BoomerHitCounter[data] > 0)
1860 {
1861 new HitCounter = BoomerHitCounter[data];
1862 BoomerHitCounter[data] = 0;
1863 new OriginalHitCounter = HitCounter;
1864 new BoomerPerfectHits = GetConVarInt(cvar_BoomerPerfectHits);
1865 new BoomerPerfectSuccess = GetConVarInt(cvar_BoomerPerfectSuccess);
1866 new Score = 0;
1867 new AwardCounter = 0;
1868
1869 //PrintToConsole(0, "timer_BoomerBlindnessCheck -> HitCounter = %i / BoomerPerfectHits = %i", HitCounter, BoomerPerfectHits);
1870
1871 while (HitCounter >= BoomerPerfectHits)
1872 {
1873 HitCounter -= BoomerPerfectHits;
1874 Score += BoomerPerfectSuccess;
1875 AwardCounter++;
1876 //PrintToConsole(0, "timer_BoomerBlindnessCheck -> Score = %i", Score);
1877 }
1878
1879 Score += GetBoomerPoints(HitCounter);
1880 //PrintToConsole(0, "timer_BoomerBlindnessCheck -> Total Score = %i", Score);
1881 Score = ModifyScoreDifficultyFloat(Score, 0.75, 0.5, TEAM_INFECTED);
1882
1883 decl String:query[1024];
1884 decl String:iID[MAX_LINE_WIDTH];
1885 GetClientRankAuthString(data, iID, sizeof(iID));
1886
1887 if (CurrentGamemodeID == GAMEMODE_VERSUS)
1888 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_perfect_blindness = award_perfect_blindness + %i, infected_boomer_blinded = infected_boomer_blinded + %i, infected_boomer_vomits = infected_boomer_vomits + %i WHERE steamid = '%s'", DbPrefix, Score, AwardCounter, OriginalHitCounter, (BoomerVomitUpdated[data] ? 0 : 1), iID);
1889 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
1890 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_perfect_blindness = award_perfect_blindness + %i, infected_boomer_blinded = infected_boomer_blinded + %i, infected_boomer_vomits = infected_boomer_vomits + %i WHERE steamid = '%s'", DbPrefix, Score, AwardCounter, OriginalHitCounter, (BoomerVomitUpdated[data] ? 0 : 1), iID);
1891 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
1892 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_perfect_blindness = award_perfect_blindness + %i, infected_boomer_blinded = infected_boomer_blinded + %i, infected_boomer_vomits = infected_boomer_vomits + %i WHERE steamid = '%s'", DbPrefix, Score, AwardCounter, OriginalHitCounter, (BoomerVomitUpdated[data] ? 0 : 1), iID);
1893 else
1894 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_perfect_blindness = award_perfect_blindness + %i, infected_boomer_blinded = infected_boomer_blinded + %i, infected_boomer_vomits = infected_boomer_vomits + %i WHERE steamid = '%s'", DbPrefix, Score, AwardCounter, OriginalHitCounter, (BoomerVomitUpdated[data] ? 0 : 1), iID);
1895
1896 SendSQLUpdate(query);
1897
1898 if (!BoomerVomitUpdated[data])
1899 UpdateMapStat("infected_boomer_vomits", 1);
1900 UpdateMapStat("infected_boomer_blinded", HitCounter);
1901
1902 BoomerVomitUpdated[data] = false;
1903
1904 if (Score > 0)
1905 {
1906 UpdateMapStat("points_infected", Score);
1907
1908 new Mode = GetConVarInt(cvar_AnnounceMode);
1909
1910 if (Mode == 1 || Mode == 2)
1911 {
1912 if (AwardCounter > 0)
1913 StatsPrintToChat(data, "You have earned \x04%i \x01points from \x05Perfect Blindness\x01!", Score);
1914 else
1915 StatsPrintToChat(data, "You have earned \x04%i \x01points for blinding \x05%i Survivors\x01!", Score, OriginalHitCounter);
1916 }
1917 else if (Mode == 3)
1918 {
1919 decl String:Name[MAX_LINE_WIDTH];
1920 GetClientName(data, Name, sizeof(Name));
1921 if (AwardCounter > 0)
1922 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points from \x05Perfect Blindness\x01!", Name, Score);
1923 else
1924 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for blinding \x05%i Survivors\x01!", Name, Score, OriginalHitCounter);
1925 }
1926 }
1927
1928 if (AwardCounter > 0 && EnableSounds_Boomer_Vomit && GetConVarBool(cvar_SoundsEnabled))
1929 EmitSoundToAll(StatsSound_Boomer_Vomit);
1930 }
1931}
1932
1933
1934// Perform player init.
1935
1936public Action:InitPlayers(Handle:timer)
1937{
1938 if (db == INVALID_HANDLE)
1939 return;
1940
1941 decl String:query[64];
1942 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers", DbPrefix);
1943 SQL_TQuery(db, GetRankTotal, query);
1944
1945 new maxplayers = GetMaxClients();
1946
1947 for (new i = 1; i <= maxplayers; i++)
1948 {
1949 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
1950 {
1951 CheckPlayerDB(i);
1952
1953 QueryClientPoints(i);
1954
1955 TimerPoints[i] = 0;
1956 TimerKills[i] = 0;
1957 }
1958 }
1959}
1960
1961QueryClientPoints(Client, SQLTCallback:callback=INVALID_FUNCTION)
1962{
1963 decl String:SteamID[MAX_LINE_WIDTH];
1964
1965 GetClientRankAuthString(Client, SteamID, sizeof(SteamID));
1966 QueryClientPointsSteamID(Client, SteamID, callback);
1967}
1968
1969QueryClientPointsSteamID(Client, const String:SteamID[], SQLTCallback:callback=INVALID_FUNCTION)
1970{
1971 if (callback == INVALID_FUNCTION)
1972 callback = GetClientPoints;
1973
1974 decl String:query[512];
1975
1976 Format(query, sizeof(query), "SELECT %s FROM %splayers WHERE steamid = '%s'", DB_PLAYERS_TOTALPOINTS, DbPrefix, SteamID);
1977
1978 SQL_TQuery(db, callback, query, Client);
1979}
1980
1981QueryClientPointsDP(Handle:dp, SQLTCallback:callback)
1982{
1983 decl String:query[1024], String:SteamID[MAX_LINE_WIDTH];
1984
1985 ResetPack(dp);
1986
1987 ReadPackCell(dp);
1988 ReadPackString(dp, SteamID, sizeof(SteamID));
1989
1990 Format(query, sizeof(query), "SELECT %s FROM %splayers WHERE steamid = '%s'", DB_PLAYERS_TOTALPOINTS, DbPrefix, SteamID);
1991
1992 SQL_TQuery(db, callback, query, dp);
1993}
1994
1995QueryClientRank(Client, SQLTCallback:callback=INVALID_FUNCTION)
1996{
1997 if (callback == INVALID_FUNCTION)
1998 callback = GetClientRank;
1999
2000 decl String:query[256];
2001
2002 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE %s >= %i", DbPrefix, DB_PLAYERS_TOTALPOINTS, ClientPoints[Client]);
2003
2004 SQL_TQuery(db, callback, query, Client);
2005}
2006
2007QueryClientRankDP(Handle:dp, SQLTCallback:callback)
2008{
2009 decl String:query[256];
2010
2011 ResetPack(dp);
2012
2013 new Client = ReadPackCell(dp);
2014
2015 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE %s >= %i", DbPrefix, DB_PLAYERS_TOTALPOINTS, ClientPoints[Client]);
2016
2017 SQL_TQuery(db, callback, query, dp);
2018}
2019/*
2020QueryClientGameModeRank(Client, SQLTCallback:callback=INVALID_FUNCTION)
2021{
2022 if (!InvalidGameMode())
2023 {
2024 if (callback == INVALID_HANDLE)
2025 callback = GetClientGameModeRank;
2026
2027 decl String:query[256];
2028
2029 switch (CurrentGamemodeID)
2030 {
2031 case GAMEMODE_VERSUS:
2032 {
2033 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_versus > 0 AND points_survivors + points_infected >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_VERSUS]);
2034 }
2035 case GAMEMODE_REALISM:
2036 {
2037 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_realism > 0 AND points_realism >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_REALISM]);
2038 }
2039 case GAMEMODE_SURVIVAL:
2040 {
2041 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_survival > 0 AND points_survival >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_SURVIVAL]);
2042 }
2043 case GAMEMODE_SCAVENGE:
2044 {
2045 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_scavenge > 0 AND points_scavenge_survivors + points_scavenge_infected >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_SCAVENGE]);
2046 }
2047 case GAMEMODE_REALISMVERSUS:
2048 {
2049 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_realismversus > 0 AND points_realism_survivors + points_realism_infected >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_REALISMVERSUS]);
2050 }
2051 case GAMEMODE_OTHERMUTATIONS:
2052 {
2053 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_mutations > 0 AND points_mutations >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_OTHERMUTATIONS]);
2054 }
2055 default:
2056 {
2057 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime > 0 AND points >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_COOP]);
2058 }
2059 }
2060
2061 SQL_TQuery(db, callback, query, Client);
2062 }
2063}
2064*/
2065QueryClientGameModeRankDP(Handle:dp, SQLTCallback:callback)
2066{
2067 if (!InvalidGameMode())
2068 {
2069 decl String:query[1024];
2070
2071 ResetPack(dp);
2072
2073 new Client = ReadPackCell(dp);
2074
2075 switch (CurrentGamemodeID)
2076 {
2077 case GAMEMODE_VERSUS:
2078 {
2079 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_versus > 0 AND points_survivors + points_infected >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_VERSUS]);
2080 }
2081 case GAMEMODE_REALISM:
2082 {
2083 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_realism > 0 AND points_realism >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_REALISM]);
2084 }
2085 case GAMEMODE_SURVIVAL:
2086 {
2087 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_survival > 0 AND points_survival >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_SURVIVAL]);
2088 }
2089 case GAMEMODE_SCAVENGE:
2090 {
2091 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_scavenge > 0 AND points_scavenge_survivors + points_scavenge_infected >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_SCAVENGE]);
2092 }
2093 case GAMEMODE_REALISMVERSUS:
2094 {
2095 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_realismversus > 0 AND points_realism_survivors + points_realism_infected >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_REALISMVERSUS]);
2096 }
2097 case GAMEMODE_OTHERMUTATIONS:
2098 {
2099 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_mutations > 0 AND points_mutations >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_OTHERMUTATIONS]);
2100 }
2101 default:
2102 {
2103 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime > 0 AND points >= %i", DbPrefix, ClientGameModePoints[Client][GAMEMODE_COOP]);
2104 }
2105 }
2106
2107 SQL_TQuery(db, callback, query, dp);
2108 }
2109}
2110/*
2111QueryClientGameModePoints(Client, SQLTCallback:callback=INVALID_FUNCTION)
2112{
2113 decl String:SteamID[MAX_LINE_WIDTH];
2114
2115 GetClientRankAuthString(Client, SteamID, sizeof(SteamID));
2116 QueryClientGameModePointsStmID(Client, SteamID, callback);
2117}
2118
2119QueryClientGameModePointsStmID(Client, const String:SteamID[], SQLTCallback:callback=INVALID_FUNCTION)
2120{
2121 if (cbGetRankTotal == INVALID_HANDLE)
2122 callback = GetClientGameModePoints;
2123
2124 decl String:query[1024];
2125
2126 Format(query, sizeof(query), "SELECT points, points_survivors + points_infected, points_realism, points_survival, points_scavenge_survivors + points_scavenge_infected + points_realism_survivors + points_realism_infected, points_mutations FROM %splayers WHERE steamid = '%s'", DbPrefix, SteamID);
2127
2128 SQL_TQuery(db, callback, query, Client);
2129}
2130*/
2131QueryClientGameModePointsDP(Handle:dp, SQLTCallback:callback)
2132{
2133 decl String:query[1024], String:SteamID[MAX_LINE_WIDTH];
2134
2135 ResetPack(dp);
2136
2137 ReadPackCell(dp);
2138 ReadPackString(dp, SteamID, sizeof(SteamID));
2139
2140 Format(query, sizeof(query), "SELECT points, points_survivors + points_infected, points_realism, points_survival, points_scavenge_survivors + points_scavenge_infected, points_realism_survivors + points_realism_infected, points_mutations FROM %splayers WHERE steamid = '%s'", DbPrefix, SteamID);
2141
2142 SQL_TQuery(db, callback, query, dp);
2143}
2144/*
2145QueryRanks()
2146{
2147 QueryRank_1();
2148 QueryRank_2();
2149}
2150*/
2151QueryRank_1(Handle:dp=INVALID_HANDLE, SQLTCallback:callback=INVALID_FUNCTION)
2152{
2153 if (callback == INVALID_FUNCTION)
2154 callback = GetRankTotal;
2155
2156 decl String:query[1024];
2157
2158 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers", DbPrefix);
2159
2160 SQL_TQuery(db, callback, query, dp);
2161}
2162
2163QueryRank_2(Handle:dp=INVALID_HANDLE, SQLTCallback:callback=INVALID_FUNCTION)
2164{
2165 if (callback == INVALID_FUNCTION)
2166 callback = GetGameModeRankTotal;
2167
2168 decl String:query[1024];
2169
2170 switch (CurrentGamemodeID)
2171 {
2172 case GAMEMODE_VERSUS:
2173 {
2174 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_versus > 0", DbPrefix);
2175 }
2176 case GAMEMODE_REALISM:
2177 {
2178 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_realism > 0", DbPrefix);
2179 }
2180 case GAMEMODE_SURVIVAL:
2181 {
2182 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_survival > 0", DbPrefix);
2183 }
2184 case GAMEMODE_SCAVENGE:
2185 {
2186 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_scavenge > 0", DbPrefix);
2187 }
2188 case GAMEMODE_REALISMVERSUS:
2189 {
2190 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_realismversus > 0", DbPrefix);
2191 }
2192 case GAMEMODE_OTHERMUTATIONS:
2193 {
2194 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime_mutations > 0", DbPrefix);
2195 }
2196 default:
2197 {
2198 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers WHERE playtime > 0", DbPrefix);
2199 }
2200 }
2201
2202 SQL_TQuery(db, callback, query, dp);
2203}
2204
2205QueryClientStats(Client, CallingMethod=CM_UNKNOWN)
2206{
2207 decl String:SteamID[MAX_LINE_WIDTH];
2208
2209 GetClientRankAuthString(Client, SteamID, sizeof(SteamID));
2210 QueryClientStatsSteamID(Client, SteamID, CallingMethod);
2211}
2212
2213QueryClientStatsSteamID(Client, const String:SteamID[], CallingMethod=CM_UNKNOWN)
2214{
2215 new Handle:dp = CreateDataPack();
2216
2217 WritePackCell(dp, Client);
2218 WritePackString(dp, SteamID);
2219 WritePackCell(dp, CallingMethod);
2220
2221 QueryClientStatsDP(dp);
2222}
2223
2224QueryClientStatsDP(Handle:dp)
2225{
2226 QueryClientGameModePointsDP(dp, QueryClientStatsDP_1);
2227}
2228
2229public QueryClientStatsDP_1(Handle:owner, Handle:hndl, const String:error[], any:dp)
2230{
2231 if (hndl == INVALID_HANDLE)
2232 {
2233 LogError("QueryClientStatsDP_1 Query failed: %s", error);
2234 return;
2235 }
2236
2237 ResetPack(dp);
2238 GetClientGameModePoints(owner, hndl, error, ReadPackCell(dp));
2239
2240 QueryClientPointsDP(dp, QueryClientStatsDP_2);
2241}
2242
2243public QueryClientStatsDP_2(Handle:owner, Handle:hndl, const String:error[], any:dp)
2244{
2245 if (hndl == INVALID_HANDLE)
2246 {
2247 LogError("QueryClientStatsDP_2 Query failed: %s", error);
2248 return;
2249 }
2250
2251 ResetPack(dp);
2252 GetClientPoints(owner, hndl, error, ReadPackCell(dp));
2253
2254 QueryClientGameModeRankDP(dp, QueryClientStatsDP_3);
2255}
2256
2257public QueryClientStatsDP_3(Handle:owner, Handle:hndl, const String:error[], any:dp)
2258{
2259 if (hndl == INVALID_HANDLE)
2260 {
2261 LogError("QueryClientStatsDP_3 Query failed: %s", error);
2262 return;
2263 }
2264
2265 ResetPack(dp);
2266 GetClientGameModeRank(owner, hndl, error, ReadPackCell(dp));
2267
2268 QueryClientRankDP(dp, QueryClientStatsDP_4);
2269}
2270
2271public QueryClientStatsDP_4(Handle:owner, Handle:hndl, const String:error[], any:dp)
2272{
2273 if (hndl == INVALID_HANDLE)
2274 {
2275 LogError("QueryClientStatsDP_4 Query failed: %s", error);
2276 return;
2277 }
2278
2279 ResetPack(dp);
2280 GetClientRank(owner, hndl, error, ReadPackCell(dp));
2281
2282 QueryRank_1(dp, QueryClientStatsDP_5);
2283}
2284
2285public QueryClientStatsDP_5(Handle:owner, Handle:hndl, const String:error[], any:dp)
2286{
2287 if (hndl == INVALID_HANDLE)
2288 {
2289 LogError("QueryClientStatsDP_5 Query failed: %s", error);
2290 return;
2291 }
2292
2293 ResetPack(dp);
2294 GetRankTotal(owner, hndl, error, ReadPackCell(dp));
2295
2296 QueryRank_2(dp, QueryClientStatsDP_6);
2297}
2298
2299public QueryClientStatsDP_6(Handle:owner, Handle:hndl, const String:error[], any:dp)
2300{
2301 if (hndl == INVALID_HANDLE)
2302 {
2303 if (dp != INVALID_HANDLE)
2304 CloseHandle(dp);
2305
2306 LogError("QueryClientStatsDP_6 Query failed: %s", error);
2307 return;
2308 }
2309
2310 decl String:SteamID[MAX_LINE_WIDTH];
2311
2312 ResetPack(dp);
2313
2314 new Client = ReadPackCell(dp);
2315 ReadPackString(dp, SteamID, sizeof(SteamID));
2316 new CallingMethod = ReadPackCell(dp);
2317
2318 GetGameModeRankTotal(owner, hndl, error, Client);
2319
2320 // Callback
2321 if (CallingMethod == CM_RANK)
2322 {
2323 QueryClientStatsDP_Rank(Client, SteamID);
2324 }
2325 else if (CallingMethod == CM_TOP10)
2326 {
2327 QueryClientStatsDP_Top10(Client, SteamID);
2328 }
2329 else if (CallingMethod == CM_NEXTRANK)
2330 {
2331 QueryClientStatsDP_NextRank(Client, SteamID);
2332 }
2333 else if (CallingMethod == CM_NEXTRANKFULL)
2334 {
2335 QueryClientStatsDP_NextRankFull(Client, SteamID);
2336 }
2337
2338 // Clean your mess up
2339 CloseHandle(dp);
2340 dp = INVALID_HANDLE;
2341}
2342
2343QueryClientStatsDP_Rank(Client, const String:SteamID[])
2344{
2345 decl String:query[1024];
2346 Format(query, sizeof(query), "SELECT name, %s, %s, kills, versus_kills_survivors + scavenge_kills_survivors + realism_kills_survivors + mutations_kills_survivors, headshots FROM %splayers WHERE steamid = '%s'", DB_PLAYERS_TOTALPLAYTIME, DB_PLAYERS_TOTALPOINTS, DbPrefix, SteamID);
2347 SQL_TQuery(db, DisplayRank, query, Client);
2348}
2349
2350QueryClientStatsDP_Top10(Client, const String:SteamID[])
2351{
2352 decl String:query[1024];
2353 Format(query, sizeof(query), "SELECT name, %s, %s, kills, versus_kills_survivors + scavenge_kills_survivors + realism_kills_survivors + mutations_kills_survivors, headshots FROM %splayers WHERE steamid = '%s'", DB_PLAYERS_TOTALPLAYTIME, DB_PLAYERS_TOTALPOINTS, DbPrefix, SteamID);
2354 SQL_TQuery(db, DisplayRank, query, Client);
2355}
2356
2357QueryClientStatsDP_NextRank(Client, const String:SteamID[])
2358{
2359 decl String:query[1024];
2360 Format(query, sizeof(query), "SELECT (%s + 1) - %i FROM %splayers WHERE (%s) >= %i AND steamid <> '%s' ORDER BY (%s) ASC LIMIT 1", DB_PLAYERS_TOTALPOINTS, ClientPoints[Client], DbPrefix, DB_PLAYERS_TOTALPOINTS, ClientPoints[Client], SteamID, DB_PLAYERS_TOTALPOINTS);
2361 SQL_TQuery(db, DisplayClientNextRank, query, Client);
2362
2363 if (TimerRankChangeCheck[Client] != INVALID_HANDLE)
2364 TriggerTimer(TimerRankChangeCheck[Client], true);
2365}
2366
2367QueryClientStatsDP_NextRankFull(Client, const String:SteamID[])
2368{
2369 decl String:query[2048];
2370 Format(query, sizeof(query), "SELECT (%s + 1) - %i FROM %splayers WHERE (%s) >= %i AND steamid <> '%s' ORDER BY (%s) ASC LIMIT 1", DB_PLAYERS_TOTALPOINTS, ClientPoints[Client], DbPrefix, DB_PLAYERS_TOTALPOINTS, ClientPoints[Client], SteamID, DB_PLAYERS_TOTALPOINTS);
2371 SQL_TQuery(db, GetClientNextRank, query, Client);
2372
2373 decl String:query1[1024], String:query2[256], String:query3[1024];
2374 Format(query1, sizeof(query1), "SELECT name, (%s) AS totalpoints FROM %splayers WHERE (%s) >= %i AND steamid <> '%s' ORDER BY totalpoints ASC LIMIT 3", DB_PLAYERS_TOTALPOINTS, DbPrefix, DB_PLAYERS_TOTALPOINTS, ClientPoints[Client], SteamID);
2375 Format(query2, sizeof(query2), "SELECT name, %i AS totalpoints FROM %splayers WHERE steamid = '%s'", ClientPoints[Client], DbPrefix, SteamID);
2376 Format(query3, sizeof(query3), "SELECT name, (%s) as totalpoints FROM %splayers WHERE (%s) < %i ORDER BY totalpoints DESC LIMIT 3", DB_PLAYERS_TOTALPOINTS, DbPrefix, DB_PLAYERS_TOTALPOINTS, ClientPoints[Client]);
2377 Format(query, sizeof(query), "(%s) UNION (%s) UNION (%s) ORDER BY totalpoints DESC", query1, query2, query3);
2378 SQL_TQuery(db, DisplayNextRankFull, query, Client);
2379
2380 if (TimerRankChangeCheck[Client] != INVALID_HANDLE)
2381 TriggerTimer(TimerRankChangeCheck[Client], true);
2382}
2383
2384// Check if a map is already in the DB.
2385
2386CheckCurrentMapDB()
2387{
2388 if (StatsDisabled(true))
2389 return;
2390
2391 decl String:MapName[MAX_LINE_WIDTH];
2392 GetCurrentMap(MapName, sizeof(MapName));
2393
2394 decl String:query[512];
2395 Format(query, sizeof(query), "SELECT name FROM %smaps WHERE LOWER(name) = LOWER('%s') AND gamemode = %i AND mutation = '%s'", DbPrefix, MapName, GetCurrentGamemodeID(), CurrentMutation);
2396
2397 SQL_TQuery(db, InsertMapDB, query);
2398}
2399
2400// Insert a map into the database if they do not already exist.
2401
2402public InsertMapDB(Handle:owner, Handle:hndl, const String:error[], any:data)
2403{
2404 if (db == INVALID_HANDLE)
2405 return;
2406
2407 if (StatsDisabled(true))
2408 return;
2409
2410 if (!SQL_GetRowCount(hndl))
2411 {
2412 decl String:MapName[MAX_LINE_WIDTH];
2413 GetCurrentMap(MapName, sizeof(MapName));
2414
2415 decl String:query[512];
2416 Format(query, sizeof(query), "INSERT IGNORE INTO %smaps SET name = LOWER('%s'), custom = 1, gamemode = %i", DbPrefix, MapName, GetCurrentGamemodeID());
2417
2418 SQL_TQuery(db, SQLErrorCheckCallback, query);
2419 }
2420}
2421
2422// Check if a player is already in the DB, and update their timestamp and playtime.
2423
2424CheckPlayerDB(client)
2425{
2426 if (StatsDisabled())
2427 return;
2428
2429 if (IsClientBot(client))
2430 return;
2431
2432 decl String:SteamID[MAX_LINE_WIDTH];
2433 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
2434
2435 decl String:query[512];
2436 Format(query, sizeof(query), "SELECT steamid FROM %splayers WHERE steamid = '%s'", DbPrefix, SteamID);
2437 SQL_TQuery(db, InsertPlayerDB, query, client);
2438
2439 ReadClientRankMuteSteamID(client, SteamID);
2440}
2441
2442ReadClientRankMute(Client)
2443{
2444 // Check stats disabled and is client bot before calling this method!
2445
2446 decl String:SteamID[MAX_LINE_WIDTH];
2447 GetClientRankAuthString(Client, SteamID, sizeof(SteamID));
2448
2449 ReadClientRankMuteSteamID(Client, SteamID);
2450}
2451
2452ReadClientRankMuteSteamID(Client, const String:SteamID[])
2453{
2454 // Check stats disabled and is client bot before calling this method!
2455
2456 decl String:query[512];
2457 Format(query, sizeof(query), "SELECT mute FROM %ssettings WHERE steamid = '%s'", DbPrefix, SteamID);
2458 SQL_TQuery(db, GetClientRankMute, query, Client);
2459}
2460
2461// Insert a player into the database if they do not already exist.
2462
2463public InsertPlayerDB(Handle:owner, Handle:hndl, const String:error[], any:client)
2464{
2465 if (db == INVALID_HANDLE || IsClientBot(client))
2466 {
2467 return;
2468 }
2469
2470 if (hndl == INVALID_HANDLE)
2471 {
2472 LogError("InsertPlayerDB failed! Reason: %s", error);
2473 return;
2474 }
2475
2476 if (StatsDisabled())
2477 {
2478 return;
2479 }
2480
2481 if (!SQL_GetRowCount(hndl))
2482 {
2483 new String:SteamID[MAX_LINE_WIDTH];
2484 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
2485
2486 new String:query[512];
2487 Format(query, sizeof(query), "INSERT IGNORE INTO %splayers SET steamid = '%s'", DbPrefix, SteamID);
2488 SQL_TQuery(db, SQLErrorCheckCallback, query);
2489 }
2490
2491 UpdatePlayer(client);
2492}
2493
2494// Insert a player into the settings database if they do not already exist.
2495
2496public SetClientRankMute(Handle:owner, Handle:hndl, const String:error[], any:client)
2497{
2498 if (db == INVALID_HANDLE || IsClientBot(client))
2499 return;
2500
2501 if (hndl == INVALID_HANDLE)
2502 {
2503 LogError("SetClientRankMute failed! Reason: %s", error);
2504 return;
2505 }
2506
2507 if (StatsDisabled())
2508 return;
2509
2510 if (SQL_GetAffectedRows(owner) == 0)
2511 {
2512 // Something went wrong!
2513 ClientRankMute[client] = false;
2514 return;
2515 }
2516
2517 ReadClientRankMute(client);
2518}
2519
2520// Insert a player into the settings database if they do not already exist.
2521
2522public GetClientRankMute(Handle:owner, Handle:hndl, const String:error[], any:client)
2523{
2524 if (db == INVALID_HANDLE || IsClientBot(client))
2525 {
2526 return;
2527 }
2528
2529 if (hndl == INVALID_HANDLE)
2530 {
2531 LogError("GetClientRankMute failed! Reason: %s", error);
2532 return;
2533 }
2534
2535 if (StatsDisabled())
2536 {
2537 return;
2538 }
2539
2540 if (!SQL_GetRowCount(hndl))
2541 {
2542 new String:SteamID[MAX_LINE_WIDTH];
2543 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
2544
2545 new String:query[512];
2546 Format(query, sizeof(query), "INSERT IGNORE INTO %ssettings SET steamid = '%s'", DbPrefix, SteamID);
2547 SQL_TQuery(db, SetClientRankMute, query, client);
2548 }
2549 else
2550 {
2551 while (SQL_FetchRow(hndl))
2552 {
2553 ClientRankMute[client] = (SQL_FetchInt(hndl, 0) != 0);
2554 }
2555 }
2556}
2557
2558// Run a SQL query, used for UPDATE's only.
2559
2560SendSQLUpdate(const String:query[], SQLTCallback:callback=INVALID_FUNCTION)
2561{
2562 if (db == INVALID_HANDLE)
2563 {
2564 return;
2565 }
2566
2567 if (callback == INVALID_FUNCTION)
2568 {
2569 callback = SQLErrorCheckCallback;
2570 }
2571
2572 if (DEBUG)
2573 {
2574 if (QueryCounter >= 256)
2575 {
2576 QueryCounter = 0;
2577 }
2578
2579 new queryid = QueryCounter++;
2580
2581 Format(QueryBuffer[queryid], MAX_QUERY_COUNTER, query);
2582
2583 SQL_TQuery(db, callback, query, queryid);
2584 }
2585 else
2586 {
2587 SQL_TQuery(db, callback, query);
2588 }
2589}
2590
2591// Report error on sql query;
2592
2593public SQLErrorCheckCallback(Handle:owner, Handle:hndl, const String:error[], any:queryid)
2594{
2595 if (db == INVALID_HANDLE)
2596 {
2597 return;
2598 }
2599
2600 if(!StrEqual("", error))
2601 {
2602 if (DEBUG)
2603 {
2604 LogError("SQL Error: %s (Query: \"%s\")", error, QueryBuffer[queryid]);
2605 }
2606 else
2607 {
2608 LogError("SQL Error: %s", error);
2609 }
2610 }
2611}
2612
2613// Perform player update of name, playtime, and timestamp.
2614
2615public UpdatePlayer(client)
2616{
2617 if (!IsClientConnected(client))
2618 {
2619 return;
2620 }
2621
2622 decl String:SteamID[MAX_LINE_WIDTH];
2623 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
2624
2625 decl String:Name[MAX_LINE_WIDTH];
2626 GetClientName(client, Name, sizeof(Name));
2627
2628 ReplaceString(Name, sizeof(Name), "<?php", "");
2629 ReplaceString(Name, sizeof(Name), "<?PHP", "");
2630 ReplaceString(Name, sizeof(Name), "?>", "");
2631 ReplaceString(Name, sizeof(Name), "\\", "");
2632 ReplaceString(Name, sizeof(Name), "\"", "");
2633 ReplaceString(Name, sizeof(Name), "'", "");
2634 ReplaceString(Name, sizeof(Name), ";", "");
2635 ReplaceString(Name, sizeof(Name), "�", "");
2636 ReplaceString(Name, sizeof(Name), "`", "");
2637
2638 UpdatePlayerFull(client, SteamID, Name);
2639}
2640
2641// Perform player update of name, playtime, and timestamp.
2642
2643public UpdatePlayerFull(Client, const String:SteamID[], const String:Name[])
2644{
2645 // Client can be ZERO! Look at UpdatePlayerCallback.
2646
2647 decl String:Playtime[32];
2648
2649 switch (CurrentGamemodeID)
2650 {
2651 case GAMEMODE_VERSUS:
2652 {
2653 Format(Playtime, sizeof(Playtime), "playtime_versus");
2654 }
2655 case GAMEMODE_REALISM:
2656 {
2657 Format(Playtime, sizeof(Playtime), "playtime_realism");
2658 }
2659 case GAMEMODE_SURVIVAL:
2660 {
2661 Format(Playtime, sizeof(Playtime), "playtime_survival");
2662 }
2663 case GAMEMODE_SCAVENGE:
2664 {
2665 Format(Playtime, sizeof(Playtime), "playtime_scavenge");
2666 }
2667 case GAMEMODE_REALISMVERSUS:
2668 {
2669 Format(Playtime, sizeof(Playtime), "playtime_realismversus");
2670 }
2671 case GAMEMODE_OTHERMUTATIONS:
2672 {
2673 Format(Playtime, sizeof(Playtime), "playtime_mutations");
2674 }
2675 default:
2676 {
2677 Format(Playtime, sizeof(Playtime), "playtime");
2678 }
2679 }
2680
2681 decl String:IP[16];
2682 GetClientIP(Client, IP, sizeof(IP));
2683
2684 decl String:query[512];
2685 Format(query, sizeof(query), "UPDATE %splayers SET lastontime = UNIX_TIMESTAMP(), %s = %s + 1, lastgamemode = %i, name = '%s', ip = '%s' WHERE steamid = '%s'", DbPrefix, Playtime, Playtime, CurrentGamemodeID, Name, IP, SteamID);
2686 SQL_TQuery(db, UpdatePlayerCallback, query, Client);
2687}
2688
2689// Report error on sql query;
2690
2691public UpdatePlayerCallback(Handle:owner, Handle:hndl, const String:error[], any:client)
2692{
2693 if (db == INVALID_HANDLE)
2694 {
2695 return;
2696 }
2697
2698 if (!StrEqual("", error))
2699 {
2700 if (client > 0)
2701 {
2702 decl String:SteamID[MAX_LINE_WIDTH];
2703 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
2704
2705 UpdatePlayerFull(0, SteamID, "INVALID_CHARACTERS");
2706
2707 return;
2708 }
2709
2710 LogError("SQL Error: %s", error);
2711 }
2712}
2713
2714// Perform a map stat update.
2715public UpdateMapStat(const String:Field[], Score)
2716{
2717 if (Score <= 0)
2718 {
2719 return;
2720 }
2721
2722 decl String:MapName[64];
2723 GetCurrentMap(MapName, sizeof(MapName));
2724
2725 decl String:DiffSQL[MAX_LINE_WIDTH];
2726 decl String:Difficulty[MAX_LINE_WIDTH];
2727 GetConVarString(cvar_Difficulty, Difficulty, sizeof(Difficulty));
2728
2729 if (StrEqual(Difficulty, "normal", false)) Format(DiffSQL, sizeof(DiffSQL), "nor");
2730 else if (StrEqual(Difficulty, "hard", false)) Format(DiffSQL, sizeof(DiffSQL), "adv");
2731 else if (StrEqual(Difficulty, "impossible", false)) Format(DiffSQL, sizeof(DiffSQL), "exp");
2732 else return;
2733
2734 decl String:FieldSQL[MAX_LINE_WIDTH];
2735 Format(FieldSQL, sizeof(FieldSQL), "%s_%s", Field, DiffSQL);
2736
2737 decl String:query[512];
2738 Format(query, sizeof(query), "UPDATE %smaps SET %s = %s + %i WHERE LOWER(name) = LOWER('%s') and gamemode = %i", DbPrefix, FieldSQL, FieldSQL, Score, MapName, GetCurrentGamemodeID());
2739 SendSQLUpdate(query);
2740}
2741
2742// Perform a map stat update.
2743public UpdateMapStatFloat(const String:Field[], Float:Value)
2744{
2745 if (Value <= 0)
2746 {
2747 return;
2748 }
2749
2750 decl String:MapName[64];
2751 GetCurrentMap(MapName, sizeof(MapName));
2752
2753 decl String:DiffSQL[MAX_LINE_WIDTH];
2754 decl String:Difficulty[MAX_LINE_WIDTH];
2755 GetConVarString(cvar_Difficulty, Difficulty, sizeof(Difficulty));
2756
2757 if (StrEqual(Difficulty, "normal", false)) Format(DiffSQL, sizeof(DiffSQL), "nor");
2758 else if (StrEqual(Difficulty, "hard", false)) Format(DiffSQL, sizeof(DiffSQL), "adv");
2759 else if (StrEqual(Difficulty, "impossible", false)) Format(DiffSQL, sizeof(DiffSQL), "exp");
2760 else return;
2761
2762 decl String:FieldSQL[MAX_LINE_WIDTH];
2763 Format(FieldSQL, sizeof(FieldSQL), "%s_%s", Field, DiffSQL);
2764
2765 decl String:query[512];
2766 Format(query, sizeof(query), "UPDATE %smaps SET %s = %s + %f WHERE LOWER(name) = LOWER('%s') and gamemode = %i", DbPrefix, FieldSQL, FieldSQL, Value, MapName, GetCurrentGamemodeID());
2767 SendSQLUpdate(query);
2768}
2769
2770// End blinded state.
2771
2772public Action:timer_EndBoomerBlinded(Handle:timer, any:data)
2773{
2774 PlayerBlinded[data][0] = 0;
2775 PlayerBlinded[data][1] = 0;
2776}
2777
2778// End blinded state.
2779
2780public Action:timer_EndSmokerParalyzed(Handle:timer, any:data)
2781{
2782 PlayerParalyzed[data][0] = 0;
2783 PlayerParalyzed[data][1] = 0;
2784}
2785
2786// End lunging state.
2787
2788public Action:timer_EndHunterLunged(Handle:timer, any:data)
2789{
2790 PlayerLunged[data][0] = 0;
2791 PlayerLunged[data][1] = 0;
2792}
2793
2794// End plummel state.
2795
2796public Action:timer_EndChargerPlummel(Handle:timer, any:data)
2797{
2798 ChargerPlummelVictim[PlayerPlummeled[data][1]] = 0;
2799 PlayerPlummeled[data][0] = 0;
2800 PlayerPlummeled[data][1] = 0;
2801}
2802
2803// End charge impact counter state.
2804
2805public Action:timer_EndCharge(Handle:timer, any:data)
2806{
2807 ChargerImpactCounterTimer[data] = INVALID_HANDLE;
2808 new Counter = ChargerImpactCounter[data];
2809 ChargerImpactCounter[data] = 0;
2810
2811 new Score = 0;
2812 new String:ScoreSet[256] = "";
2813
2814 if (Counter >= GetConVarInt(cvar_ChargerRamHits))
2815 {
2816 Score = ModifyScoreDifficultyFloat(GetConVarInt(cvar_ChargerRamSuccess), 0.9, 0.8, TEAM_INFECTED);
2817
2818 if (CurrentGamemodeID == GAMEMODE_VERSUS)
2819 {
2820 Format(ScoreSet, sizeof(ScoreSet), "points_infected = points_infected + %i", Score);
2821 }
2822 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
2823 {
2824 Format(ScoreSet, sizeof(ScoreSet), "points_realism_infected = points_realism_infected + %i", Score);
2825 }
2826 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
2827 {
2828 Format(ScoreSet, sizeof(ScoreSet), "points_scavenge_infected = points_scavenge_infected + %i", Score);
2829 }
2830 else
2831 {
2832 Format(ScoreSet, sizeof(ScoreSet), "points_mutations = points_mutations + %i", Score);
2833 }
2834
2835 StrCat(ScoreSet, sizeof(ScoreSet), ", award_scatteringram = award_scatteringram + 1, ");
2836
2837 if (EnableSounds_Charger_Ram && GetConVarBool(cvar_SoundsEnabled))
2838 EmitSoundToAll(SOUND_CHARGER_RAM);
2839 }
2840 //UPDATE players SET points_infected = points_infected + 40, award_scatteringram = acharger_impacts = charger_impacts + 4 WHERE steamid = 'STEAM_1:1:12345678'
2841
2842 decl String:AttackerID[MAX_LINE_WIDTH];
2843 GetClientRankAuthString(data, AttackerID, sizeof(AttackerID));
2844
2845 decl String:query[512];
2846 Format(query, sizeof(query), "UPDATE %splayers SET %scharger_impacts = charger_impacts + %i WHERE steamid = '%s'", DbPrefix, ScoreSet, Counter, AttackerID);
2847 SendSQLUpdate(query);
2848
2849 if (Score > 0)
2850 UpdateMapStat("points_infected", Score);
2851
2852 if (Counter > 0)
2853 UpdateMapStat("charger_impacts", Counter);
2854
2855 new Mode = 0;
2856 if (Score > 0)
2857 Mode = GetConVarInt(cvar_AnnounceMode);
2858
2859 if ((Mode == 1 || Mode == 2) && IsClientConnected(data) && IsClientInGame(data))
2860 StatsPrintToChat(data, "You have earned \x04%i \x01points for charging a \x05Scattering Ram \x01on \x03%i \x01victims!", Score, Counter);
2861 else if (Mode == 3)
2862 {
2863 decl String:AttackerName[MAX_LINE_WIDTH];
2864 GetClientName(data, AttackerName, sizeof(AttackerName));
2865 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for charging a \x05Scattering Ram \x01on \x03%i \x01victims!", AttackerName, Score, Counter);
2866 }
2867}
2868
2869// End carried state.
2870
2871public Action:timer_EndChargerCarry(Handle:timer, any:data)
2872{
2873 ChargerCarryVictim[PlayerCarried[data][1]] = 0;
2874 PlayerCarried[data][0] = 0;
2875 PlayerCarried[data][1] = 0;
2876}
2877
2878// End jockey ride state.
2879
2880public Action:timer_EndJockeyRide(Handle:timer, any:data)
2881{
2882 JockeyVictim[PlayerCarried[data][1]] = 0;
2883 PlayerJockied[data][0] = 0;
2884 PlayerJockied[data][1] = 0;
2885}
2886
2887// End friendly fire damage counter.
2888
2889public Action:timer_FriendlyFireDamageEnd(Handle:timer, any:dp)
2890{
2891 ResetPack(dp);
2892
2893 new HumanDamage = ReadPackCell(dp);
2894 new BotDamage = ReadPackCell(dp);
2895 new Attacker = ReadPackCell(dp);
2896
2897 // This may fail! What happens when a player skips and another joins with the same Client ID (is this even possible in such short time?)
2898 FriendlyFireTimer[Attacker][0] = INVALID_HANDLE;
2899
2900 decl String:AttackerID[MAX_LINE_WIDTH];
2901 ReadPackString(dp, AttackerID, sizeof(AttackerID));
2902 decl String:AttackerName[MAX_LINE_WIDTH];
2903 ReadPackString(dp, AttackerName, sizeof(AttackerName));
2904
2905 // The damage is read and turned into lost points...
2906 ResetPack(dp);
2907 WritePackCell(dp, 0); // Human damage
2908 WritePackCell(dp, 0); // Bot damage
2909
2910 if (HumanDamage <= 0 && BotDamage <= 0)
2911 return;
2912
2913 new Score = 0;
2914
2915 if (GetConVarBool(cvar_EnableNegativeScore))
2916 {
2917 if (HumanDamage > 0)
2918 Score += ModifyScoreDifficultyNR(RoundToNearest(GetConVarFloat(cvar_FriendlyFireMultiplier) * HumanDamage), 2, 4, TEAM_SURVIVORS);
2919
2920 if (BotDamage > 0)
2921 {
2922 new Float:BotScoreMultiplier = GetConVarFloat(cvar_BotScoreMultiplier);
2923
2924 if (BotScoreMultiplier > 0.0)
2925 Score += ModifyScoreDifficultyNR(RoundToNearest(GetConVarFloat(cvar_FriendlyFireMultiplier) * BotDamage), 2, 4, TEAM_SURVIVORS);
2926 }
2927 }
2928
2929 decl String:UpdatePoints[32];
2930
2931 switch (CurrentGamemodeID)
2932 {
2933 case GAMEMODE_VERSUS:
2934 {
2935 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
2936 }
2937 case GAMEMODE_REALISM:
2938 {
2939 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
2940 }
2941 case GAMEMODE_SURVIVAL:
2942 {
2943 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
2944 }
2945 case GAMEMODE_SCAVENGE:
2946 {
2947 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
2948 }
2949 case GAMEMODE_REALISMVERSUS:
2950 {
2951 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
2952 }
2953 case GAMEMODE_OTHERMUTATIONS:
2954 {
2955 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
2956 }
2957 default:
2958 {
2959 Format(UpdatePoints, sizeof(UpdatePoints), "points");
2960 }
2961 }
2962
2963 decl String:query[1024];
2964 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i, award_friendlyfire = award_friendlyfire + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, AttackerID);
2965 SendSQLUpdate(query);
2966
2967 new Mode = 0;
2968 if (Score > 0)
2969 Mode = GetConVarInt(cvar_AnnounceMode);
2970
2971 if ((Mode == 1 || Mode == 2) && IsClientConnected(Attacker) && IsClientInGame(Attacker))
2972 StatsPrintToChat(Attacker, "You have \x03LOST \x04%i \x01points for inflicting \x03Friendly Fire Damage \x05(%i HP)\x01!", Score, HumanDamage + BotDamage);
2973 else if (Mode == 3)
2974 StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for inflicting \x03Friendly Fire Damage \x05(%i HP)\x01!", AttackerName, Score, HumanDamage + BotDamage);
2975}
2976
2977// Start team shuffle.
2978
2979public Action:timer_ShuffleTeams(Handle:timer, any:data)
2980{
2981 if (CheckHumans())
2982 return;
2983
2984 decl String:query[1024];
2985 Format(query, sizeof(query), "SELECT steamid FROM %splayers WHERE ", DbPrefix);
2986
2987 new maxplayers = GetMaxClients();
2988 decl String:SteamID[MAX_LINE_WIDTH], String:where[512];
2989 new counter = 0, team;
2990
2991 for (new i = 1; i <= maxplayers; i++)
2992 {
2993 if (IsClientBot(i) || !IsClientConnected(i) || !IsClientInGame(i))
2994 continue;
2995
2996 team = GetClientTeam(i);
2997
2998 if (team != TEAM_SURVIVORS && team != TEAM_INFECTED)
2999 continue;
3000
3001 if (counter++ > 0)
3002 StrCat(query, sizeof(query), "OR ");
3003
3004 GetClientRankAuthString(i, SteamID, sizeof(SteamID));
3005 Format(where, sizeof(where), "steamid = '%s' ", SteamID);
3006 StrCat(query, sizeof(query), where);
3007 }
3008
3009 if (counter <= 1)
3010 {
3011 StatsPrintToChatAllPreFormatted2(true, "Team shuffle by player PPM failed because there was \x03not enough players\x01!");
3012 return;
3013 }
3014
3015 Format(where, sizeof(where), "ORDER BY (%s) / (%s) DESC", DB_PLAYERS_TOTALPOINTS, DB_PLAYERS_TOTALPLAYTIME);
3016 StrCat(query, sizeof(query), where);
3017
3018 SQL_TQuery(db, ExecuteTeamShuffle, query);
3019}
3020
3021// End of RANKVOTE.
3022
3023public Action:timer_RankVote(Handle:timer, any:data)
3024{
3025 RankVoteTimer = INVALID_HANDLE;
3026
3027 if (!CheckHumans())
3028 {
3029 new humans = 0, votes = 0, yesvotes = 0, novotes = 0, WinningVoteCount = 0;
3030
3031 CheckRankVotes(humans, votes, yesvotes, novotes, WinningVoteCount);
3032
3033 StatsPrintToChatAll2(true, "Vote to shuffle teams by player PPM \x03%s \x01with \x04%i (yes) against %i (no)\x01.", (yesvotes > novotes ? "PASSED" : "DID NOT PASS"), yesvotes, novotes);
3034
3035 if (yesvotes > novotes)
3036 {
3037 CreateTimer(3.0, timer_ShuffleTeams);
3038 }
3039 }
3040}
3041
3042// End friendly fire cooldown.
3043
3044public Action:timer_FriendlyFireCooldownEnd(Handle:timer, any:data)
3045{
3046 FriendlyFireCooldown[FriendlyFirePrm[data][0]][FriendlyFirePrm[data][1]] = false;
3047 FriendlyFireTimer[FriendlyFirePrm[data][0]][FriendlyFirePrm[data][1]] = INVALID_HANDLE;
3048}
3049
3050// End friendly fire cooldown.
3051
3052public Action:timer_MeleeKill(Handle:timer, any:data)
3053{
3054 MeleeKillTimer[data] = INVALID_HANDLE;
3055 new Counter = MeleeKillCounter[data];
3056 MeleeKillCounter[data] = 0;
3057
3058 if (Counter <= 0 || IsClientBot(data) || !IsClientConnected(data) || !IsClientInGame(data) || GetClientTeam(data) != TEAM_SURVIVORS)
3059 return;
3060
3061 decl String:query[512], String:clientID[MAX_LINE_WIDTH];
3062 GetClientRankAuthString(data, clientID, sizeof(clientID));
3063 Format(query, sizeof(query), "UPDATE %splayers SET melee_kills = melee_kills + %i WHERE steamid = '%s'", DbPrefix, Counter, clientID);
3064 SendSQLUpdate(query);
3065}
3066
3067// Perform minutely updates of player database.
3068// Reports Disabled message if in Versus, Easy mode, not enough Human players, and if cheats are active.
3069
3070public Action:timer_UpdatePlayers(Handle:timer, Handle:hndl)
3071{
3072 if (CheckHumans())
3073 {
3074 if (GetConVarBool(cvar_DisabledMessages))
3075 {
3076 StatsPrintToChatAllPreFormatted("Left 4 Dead Stats are \x04DISABLED\x01, not enough Human players!");
3077 }
3078
3079 return;
3080 }
3081
3082 if (StatsDisabled())
3083 return;
3084
3085 UpdateMapStat("playtime", 1);
3086
3087 new maxplayers = GetMaxClients();
3088 for (new i = 1; i <= maxplayers; i++)
3089 {
3090 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
3091 CheckPlayerDB(i);
3092 }
3093}
3094
3095// Display rank change.
3096
3097public Action:timer_ShowRankChange(Handle:timer, any:client)
3098{
3099 DoShowRankChange(client);
3100}
3101
3102public DoShowRankChange(Client)
3103{
3104 if (StatsDisabled())
3105 return;
3106
3107 decl String:ClientID[MAX_LINE_WIDTH];
3108 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
3109
3110 QueryClientPointsSteamID(Client, ClientID, GetClientPointsRankChange);
3111}
3112
3113// Display player rank.
3114
3115public Action:timer_ShowPlayerJoined(Handle:timer, any:client)
3116{
3117 DoShowPlayerJoined(client);
3118}
3119
3120public DoShowPlayerJoined(client)
3121{
3122 if (StatsDisabled())
3123 {
3124 return;
3125 }
3126
3127 decl String:clientId[MAX_LINE_WIDTH];
3128 GetClientRankAuthString(client, clientId, sizeof(clientId));
3129
3130 QueryClientPointsSteamID(client, clientId, GetClientPointsPlayerJoined);
3131}
3132
3133// Display common Infected scores to each player.
3134
3135public Action:timer_ShowTimerScore(Handle:timer, Handle:hndl)
3136{
3137 if (StatsDisabled())
3138 return;
3139
3140 new Mode = GetConVarInt(cvar_AnnounceMode);
3141 decl String:Name[MAX_LINE_WIDTH];
3142
3143 new maxplayers = GetMaxClients();
3144 for (new i = 1; i <= maxplayers; i++)
3145 {
3146 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
3147 {
3148 // if (CurrentPoints[i] > GetConVarInt(cvar_MaxPoints))
3149 // continue;
3150
3151 TimerPoints[i] = GetMedkitPointReductionScore(TimerPoints[i]);
3152
3153 if (TimerPoints[i] > 0 && TimerKills[i] > 0)
3154 {
3155 if (Mode == 1 || Mode == 2)
3156 {
3157 StatsPrintToChat(i, "You have earned \x04%i \x01points for killing \x05%i \x01Infected!", TimerPoints[i], TimerKills[i]);
3158 }
3159 else if (Mode == 3)
3160 {
3161 GetClientName(i, Name, sizeof(Name));
3162 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for killing \x05%i \x01Infected!", Name, TimerPoints[i], TimerKills[i]);
3163 }
3164 }
3165
3166 InterstitialPlayerUpdate(i);
3167 }
3168
3169 TimerPoints[i] = 0;
3170 TimerKills[i] = 0;
3171 TimerHeadshots[i] = 0;
3172 }
3173
3174}
3175
3176// Update a player's stats, used for interstitial updating.
3177
3178public InterstitialPlayerUpdate(client)
3179{
3180 decl String:ClientID[MAX_LINE_WIDTH];
3181 GetClientRankAuthString(client, ClientID, sizeof(ClientID));
3182
3183 decl String:UpdatePoints[32];
3184
3185 switch (CurrentGamemodeID)
3186 {
3187 case GAMEMODE_VERSUS:
3188 {
3189 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3190 }
3191 case GAMEMODE_REALISM:
3192 {
3193 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3194 }
3195 case GAMEMODE_SURVIVAL:
3196 {
3197 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3198 }
3199 case GAMEMODE_SCAVENGE:
3200 {
3201 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3202 }
3203 case GAMEMODE_REALISMVERSUS:
3204 {
3205 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3206 }
3207 case GAMEMODE_OTHERMUTATIONS:
3208 {
3209 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3210 }
3211 default:
3212 {
3213 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3214 }
3215 }
3216
3217 new len = 0;
3218 decl String:query[1024];
3219 len += Format(query[len], sizeof(query)-len, "UPDATE %splayers SET %s = %s + %i, ", DbPrefix, UpdatePoints, UpdatePoints, TimerPoints[client]);
3220 len += Format(query[len], sizeof(query)-len, "kills = kills + %i, kill_infected = kill_infected + %i, ", TimerKills[client], TimerKills[client]);
3221 len += Format(query[len], sizeof(query)-len, "headshots = headshots + %i ", TimerHeadshots[client]);
3222 len += Format(query[len], sizeof(query)-len, "WHERE steamid = '%s'", ClientID);
3223 SendSQLUpdate(query);
3224
3225 UpdateMapStat("kills", TimerKills[client]);
3226 UpdateMapStat("points", TimerPoints[client]);
3227
3228 AddScore(client, TimerPoints[client]);
3229}
3230
3231// Player Death event. Used for killing AI Infected. +2 on headshot, and global announcement.
3232// Team Kill code is in the awards section. Tank Kill code is in Tank section.
3233
3234public Action:event_PlayerDeath(Handle:event, const String:name[], bool:dontBroadcast)
3235{
3236 if (StatsDisabled() || CampaignOver)
3237 return;
3238
3239 new Attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
3240 new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
3241 new bool:AttackerIsBot = GetEventBool(event, "attackerisbot");
3242 new bool:VictimIsBot = GetEventBool(event, "victimisbot");
3243 new VictimTeam = -1;
3244
3245 // Self inflicted death does not count
3246 if (Attacker == Victim)
3247 return;
3248
3249 if (!VictimIsBot)
3250 DoInfectedFinalChecks(Victim, ClientInfectedType[Victim]);
3251
3252 if (Victim > 0)
3253 VictimTeam = GetClientTeam(Victim);
3254
3255 if (Attacker == 0 || AttackerIsBot)
3256 {
3257 // Attacker is normal indected but the Victim was infected by blinding and/or paralysation.
3258 if (Attacker == 0
3259 && VictimTeam == TEAM_SURVIVORS
3260 && (PlayerBlinded[Victim][0] && PlayerBlinded[Victim][1]
3261 || PlayerParalyzed[Victim][0] && PlayerParalyzed[Victim][1]
3262 || PlayerLunged[Victim][0] && PlayerLunged[Victim][1]
3263 || PlayerCarried[Victim][0] && PlayerCarried[Victim][1]
3264 || PlayerPlummeled[Victim][0] && PlayerPlummeled[Victim][1]
3265 || PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
3266 && IsGamemodeVersus())
3267 PlayerDeathExternal(Victim);
3268
3269 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && Victim > 0 && VictimTeam == TEAM_SURVIVORS)
3270 CheckSurvivorsAllDown();
3271
3272 return;
3273 }
3274
3275 new Mode = GetConVarInt(cvar_AnnounceMode);
3276 new AttackerTeam = GetClientTeam(Attacker);
3277 decl String:AttackerName[MAX_LINE_WIDTH];
3278 decl String:AttackerID[MAX_LINE_WIDTH];
3279 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
3280 decl String:VictimName[MAX_LINE_WIDTH];
3281 new VictimInfType = -1;
3282
3283 if (Victim > 0)
3284 {
3285 GetClientName(Victim, VictimName, sizeof(VictimName));
3286
3287 if (VictimTeam == TEAM_INFECTED)
3288 VictimInfType = GetInfType(Victim);
3289 }
3290 else
3291 {
3292 GetEventString(event, "victimname", VictimName, sizeof(VictimName));
3293
3294 if (StrEqual(VictimName, "hunter", false))
3295 {
3296 VictimTeam = TEAM_INFECTED;
3297 VictimInfType = INF_ID_HUNTER;
3298 }
3299 else if (StrEqual(VictimName, "smoker", false))
3300 {
3301 VictimTeam = TEAM_INFECTED;
3302 VictimInfType = INF_ID_SMOKER;
3303 }
3304 else if (StrEqual(VictimName, "boomer", false))
3305 {
3306 VictimTeam = TEAM_INFECTED;
3307 VictimInfType = INF_ID_BOOMER;
3308 }
3309 if (StrEqual(VictimName, "spitter", false))
3310 {
3311 VictimTeam = TEAM_INFECTED;
3312 VictimInfType = INF_ID_SPITTER_L4D2;
3313 }
3314 else if (StrEqual(VictimName, "jockey", false))
3315 {
3316 VictimTeam = TEAM_INFECTED;
3317 VictimInfType = INF_ID_JOCKEY_L4D2;
3318 }
3319 else if (StrEqual(VictimName, "charger", false))
3320 {
3321 VictimTeam = TEAM_INFECTED;
3322 VictimInfType = INF_ID_CHARGER_L4D2;
3323 }
3324 else if (StrEqual(VictimName, "tank", false))
3325 {
3326 VictimTeam = TEAM_INFECTED;
3327 VictimInfType = INF_ID_TANK_L4D2;
3328 }
3329 else
3330 return;
3331 }
3332
3333 // The wearoff should now work properly! Don't initialize
3334 //if (Victim > 0 && (VictimInfType == INF_ID_HUNTER || VictimInfType == INF_ID_SMOKER))
3335 // InitializeClientInf(Victim);
3336
3337 if (VictimTeam == TEAM_SURVIVORS)
3338 CheckSurvivorsAllDown();
3339
3340 // Team Kill: Attacker is a Survivor and Victim is Survivor
3341 if (AttackerTeam == TEAM_SURVIVORS && VictimTeam == TEAM_SURVIVORS)
3342 {
3343 new Score = 0;
3344 if (GetConVarBool(cvar_EnableNegativeScore))
3345 {
3346 if (!IsClientBot(Victim))
3347 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_FKill), 2, 4, TEAM_SURVIVORS);
3348 else
3349 {
3350 new Float:BotScoreMultiplier = GetConVarFloat(cvar_BotScoreMultiplier);
3351
3352 if (BotScoreMultiplier > 0.0)
3353 Score = RoundToNearest(ModifyScoreDifficultyNR(GetConVarInt(cvar_FKill), 2, 4, TEAM_SURVIVORS) * BotScoreMultiplier);
3354 }
3355 }
3356 else
3357 Mode = 0;
3358
3359 decl String:UpdatePoints[32];
3360
3361 switch (CurrentGamemodeID)
3362 {
3363 case GAMEMODE_VERSUS:
3364 {
3365 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3366 }
3367 case GAMEMODE_REALISM:
3368 {
3369 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3370 }
3371 case GAMEMODE_SURVIVAL:
3372 {
3373 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3374 }
3375 case GAMEMODE_SCAVENGE:
3376 {
3377 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3378 }
3379 case GAMEMODE_REALISMVERSUS:
3380 {
3381 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3382 }
3383 case GAMEMODE_OTHERMUTATIONS:
3384 {
3385 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3386 }
3387 default:
3388 {
3389 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3390 }
3391 }
3392
3393 decl String:query[1024];
3394 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i, award_teamkill = award_teamkill + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, AttackerID);
3395
3396 SendSQLUpdate(query);
3397
3398 if (Mode == 1 || Mode == 2)
3399 StatsPrintToChat(Attacker, "You have \x03LOST \x04%i \x01points for \x03Team Killing \x05%s\x01!", Score, VictimName);
3400 else if (Mode == 3)
3401 StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for \x03Team Killing \x05%s\x01!", AttackerName, Score, VictimName);
3402 }
3403
3404 // Attacker is a Survivor
3405 else if (AttackerTeam == TEAM_SURVIVORS && VictimTeam == TEAM_INFECTED)
3406 {
3407 new Score = 0;
3408 decl String:InfectedType[8];
3409
3410 if (VictimInfType == INF_ID_HUNTER)
3411 {
3412 Format(InfectedType, sizeof(InfectedType), "hunter");
3413 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Hunter), 2, 3, TEAM_SURVIVORS);
3414 }
3415 else if (VictimInfType == INF_ID_SMOKER)
3416 {
3417 Format(InfectedType, sizeof(InfectedType), "smoker");
3418 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Smoker), 2, 3, TEAM_SURVIVORS);
3419 }
3420 else if (VictimInfType == INF_ID_BOOMER)
3421 {
3422 Format(InfectedType, sizeof(InfectedType), "boomer");
3423 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Boomer), 2, 3, TEAM_SURVIVORS);
3424 }
3425 else if (VictimInfType == INF_ID_SPITTER_L4D2)
3426 {
3427 Format(InfectedType, sizeof(InfectedType), "spitter");
3428 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Spitter), 2, 3, TEAM_SURVIVORS);
3429 }
3430 else if (VictimInfType == INF_ID_JOCKEY_L4D2)
3431 {
3432 Format(InfectedType, sizeof(InfectedType), "jockey");
3433 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Jockey), 2, 3, TEAM_SURVIVORS);
3434 }
3435 else if (VictimInfType == INF_ID_CHARGER_L4D2)
3436 {
3437 Format(InfectedType, sizeof(InfectedType), "charger");
3438 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Charger), 2, 3, TEAM_SURVIVORS);
3439 }
3440 else
3441 return;
3442
3443 new String:Headshot[32];
3444 if (GetEventBool(event, "headshot"))
3445 {
3446 Format(Headshot, sizeof(Headshot), ", headshots = headshots + 1");
3447 Score = Score + 2;
3448 }
3449
3450 Score = GetMedkitPointReductionScore(Score);
3451
3452 decl String:UpdatePoints[32];
3453
3454 switch (CurrentGamemodeID)
3455 {
3456 case GAMEMODE_VERSUS:
3457 {
3458 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3459 }
3460 case GAMEMODE_REALISM:
3461 {
3462 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3463 }
3464 case GAMEMODE_SURVIVAL:
3465 {
3466 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3467 }
3468 case GAMEMODE_SCAVENGE:
3469 {
3470 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3471 }
3472 case GAMEMODE_REALISMVERSUS:
3473 {
3474 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3475 }
3476 case GAMEMODE_OTHERMUTATIONS:
3477 {
3478 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3479 }
3480 default:
3481 {
3482 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3483 }
3484 }
3485
3486 new len = 0;
3487 decl String:query[1024];
3488 len += Format(query[len], sizeof(query)-len, "UPDATE %splayers SET %s = %s + %i, ", DbPrefix, UpdatePoints, UpdatePoints, Score);
3489 len += Format(query[len], sizeof(query)-len, "kills = kills + 1, kill_%s = kill_%s + 1", InfectedType, InfectedType);
3490 len += Format(query[len], sizeof(query)-len, "%s WHERE steamid = '%s'", Headshot, AttackerID);
3491 SendSQLUpdate(query);
3492
3493 if (Mode && Score > 0)
3494 {
3495 if (GetEventBool(event, "headshot"))
3496 {
3497 if (Mode > 1)
3498 {
3499 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
3500 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for killing%s \x05%s \x01with a \x04HEAD SHOT\x01!", AttackerName, Score, (VictimIsBot ? " a" : ""), VictimName);
3501 }
3502 else
3503 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for killing%s \x05%s \x01with a \x04HEAD SHOT\x01!", Score, (VictimIsBot ? " a" : ""), VictimName);
3504 }
3505 else
3506 {
3507 if (Mode > 2)
3508 {
3509 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
3510 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for killing%s \x05%s\x01!", AttackerName, Score, (VictimIsBot ? " a" : ""), VictimName);
3511 }
3512 else
3513 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for killing%s \x05%s\x01!", Score, (VictimIsBot ? " a" : ""), VictimName);
3514 }
3515 }
3516
3517 UpdateMapStat("kills", 1);
3518 UpdateMapStat("points", Score);
3519 AddScore(Attacker, Score);
3520 }
3521
3522 // Attacker is an Infected
3523 else if (AttackerTeam == TEAM_INFECTED && VictimTeam == TEAM_SURVIVORS)
3524 SurvivorDiedNamed(Attacker, Victim, VictimName, AttackerID, -1, Mode);
3525
3526 if (VictimTeam == TEAM_SURVIVORS)
3527 {
3528 if (PanicEvent)
3529 PanicEventIncap = true;
3530
3531 if (PlayerVomited)
3532 PlayerVomitedIncap = true;
3533 }
3534}
3535
3536// Common Infected death code. +1 on headshot.
3537
3538public Action:event_InfectedDeath(Handle:event, const String:name[], bool:dontBroadcast)
3539{
3540 if (StatsDisabled() || CampaignOver)
3541 return;
3542
3543 new Attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
3544
3545 if (!Attacker || IsClientBot(Attacker) || GetClientTeam(Attacker) == TEAM_INFECTED)
3546 return;
3547
3548 new Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Infected), 2, 3, TEAM_SURVIVORS);
3549
3550 if (GetEventBool(event, "headshot"))
3551 {
3552 Score = Score + 1;
3553 TimerHeadshots[Attacker] = TimerHeadshots[Attacker] + 1;
3554 }
3555
3556 TimerPoints[Attacker] = TimerPoints[Attacker] + Score;
3557 TimerKills[Attacker] = TimerKills[Attacker] + 1;
3558
3559 // Melee?
3560 if (ServerVersion != SERVER_VERSION_L4D1)
3561 {
3562 new WeaponID = GetEventInt(event, "weapon_id");
3563
3564 if (WeaponID == 19)
3565 IncrementMeleeKills(Attacker);
3566 }
3567
3568 //decl String:AttackerName[MAX_LINE_WIDTH];
3569 //GetClientName(Attacker, AttackerName, sizeof(AttackerName));
3570
3571 //LogMessage("[DEBUG] %s killed an infected (Weapon ID: %i)", AttackerName, WeaponID);
3572 //PrintToConsoleAll("[DEBUG] %s killed an infected (Weapon ID: %i)", AttackerName, WeaponID);
3573}
3574
3575// Check player validity before calling this method!
3576IncrementMeleeKills(client)
3577{
3578 if (MeleeKillTimer[client] != INVALID_HANDLE)
3579 CloseHandle(MeleeKillTimer[client]);
3580
3581 MeleeKillCounter[client]++;
3582 MeleeKillTimer[client] = CreateTimer(5.0, timer_MeleeKill, client);
3583}
3584
3585// Tank death code. Points are given to all players.
3586
3587public Action:event_TankKilled(Handle:event, const String:name[], bool:dontBroadcast)
3588{
3589 if (StatsDisabled() || CampaignOver)
3590 {
3591 return;
3592 }
3593
3594 if (TankCount >= 3)
3595 {
3596 return;
3597 }
3598
3599 new BaseScore = ModifyScoreDifficulty(GetConVarInt(cvar_Tank), 2, 4, TEAM_SURVIVORS);
3600 new Mode = GetConVarInt(cvar_AnnounceMode);
3601 new Deaths = 0;
3602 new Players = 0;
3603
3604 new maxplayers = GetMaxClients();
3605 for (new i = 1; i <= maxplayers; i++)
3606 {
3607 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
3608 {
3609 Players++;
3610
3611 if (!IsPlayerAlive(i))
3612 {
3613 Deaths++;
3614 }
3615 }
3616 }
3617
3618 // This was proposed by AlliedModders users el_psycho and PatriotGames (Thanks!)
3619 new Score = (BaseScore * ((Players - Deaths) / Players)) / Players;
3620
3621 decl String:UpdatePoints[32];
3622
3623 switch (CurrentGamemodeID)
3624 {
3625 case GAMEMODE_VERSUS:
3626 {
3627 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3628 }
3629 case GAMEMODE_REALISM:
3630 {
3631 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3632 }
3633 case GAMEMODE_SURVIVAL:
3634 {
3635 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3636 }
3637 case GAMEMODE_SCAVENGE:
3638 {
3639 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3640 }
3641 case GAMEMODE_REALISMVERSUS:
3642 {
3643 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3644 }
3645 case GAMEMODE_OTHERMUTATIONS:
3646 {
3647 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3648 }
3649 default:
3650 {
3651 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3652 }
3653 }
3654
3655 decl String:iID[MAX_LINE_WIDTH];
3656 decl String:query[512];
3657
3658 for (new i = 1; i <= maxplayers; i++)
3659 {
3660 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i) && GetClientTeam(i) == TEAM_SURVIVORS)
3661 {
3662 GetClientRankAuthString(i, iID, sizeof(iID));
3663 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_tankkill = award_tankkill + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
3664 SendSQLUpdate(query);
3665
3666 AddScore(i, Score);
3667 }
3668 }
3669
3670 if (Mode && Score > 0)
3671 {
3672 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have earned \x04%i \x01points for killing a Tank with \x05%i Deaths\x01!", Score, Deaths);
3673 }
3674
3675 UpdateMapStat("kills", 1);
3676 UpdateMapStat("points", Score);
3677 TankCount = TankCount + 1;
3678}
3679
3680// Adrenaline give code. Special note, Adrenalines can only be given once. (Even if it's initially given by a bot!)
3681
3682GiveAdrenaline(Giver, Recipient, AdrenalineID = -1)
3683{
3684 // Stats enabled is checked by the caller
3685
3686 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !SurvivalStarted)
3687 return;
3688
3689 if (AdrenalineID < 0)
3690 AdrenalineID = GetPlayerWeaponSlot(Recipient, 4);
3691
3692 if (AdrenalineID < 0 || Adrenaline[AdrenalineID] == 1)
3693 return;
3694 else
3695 Adrenaline[AdrenalineID] = 1;
3696
3697 if (IsClientBot(Giver))
3698 return;
3699
3700 decl String:RecipientName[MAX_LINE_WIDTH];
3701 GetClientName(Recipient, RecipientName, sizeof(RecipientName));
3702 decl String:RecipientID[MAX_LINE_WIDTH];
3703 GetClientRankAuthString(Recipient, RecipientID, sizeof(RecipientID));
3704
3705 decl String:GiverName[MAX_LINE_WIDTH];
3706 GetClientName(Giver, GiverName, sizeof(GiverName));
3707 decl String:GiverID[MAX_LINE_WIDTH];
3708 GetClientRankAuthString(Giver, GiverID, sizeof(GiverID));
3709
3710 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Adrenaline), 2, 4, TEAM_SURVIVORS);
3711 decl String:UpdatePoints[32];
3712
3713 switch (CurrentGamemodeID)
3714 {
3715 case GAMEMODE_VERSUS:
3716 {
3717 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3718 }
3719 case GAMEMODE_REALISM:
3720 {
3721 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3722 }
3723 case GAMEMODE_SURVIVAL:
3724 {
3725 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3726 }
3727 case GAMEMODE_SCAVENGE:
3728 {
3729 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3730 }
3731 case GAMEMODE_REALISMVERSUS:
3732 {
3733 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3734 }
3735 case GAMEMODE_OTHERMUTATIONS:
3736 {
3737 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3738 }
3739 default:
3740 {
3741 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3742 }
3743 }
3744
3745 decl String:query[1024];
3746 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_adrenaline = award_adrenaline + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, GiverID);
3747 SendSQLUpdate(query);
3748
3749 UpdateMapStat("points", Score);
3750 AddScore(Giver, Score);
3751
3752 if (Score > 0)
3753 {
3754 new Mode = GetConVarInt(cvar_AnnounceMode);
3755
3756 if (Mode == 1 || Mode == 2)
3757 StatsPrintToChat(Giver, "You have earned \x04%i \x01points for giving adrenaline to \x05%s\x01!", Score, RecipientName);
3758 else if (Mode == 3)
3759 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for giving adrenaline to \x05%s\x01!", GiverName, Score, RecipientName);
3760 }
3761}
3762
3763// Pill give event. (From give a weapon)
3764
3765public Action:event_GivePills(Handle:event, const String:name[], bool:dontBroadcast)
3766{
3767 if (StatsDisabled())
3768 return;
3769
3770 // If given weapon != 12 (Pain Pills) then return
3771 if (GetEventInt(event, "weapon") != 12)
3772 return;
3773
3774 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !GetConVarBool(cvar_EnableSvMedicPoints))
3775 return;
3776
3777 new Recipient = GetClientOfUserId(GetEventInt(event, "userid"));
3778 new Giver = GetClientOfUserId(GetEventInt(event, "giver"));
3779 new PillsID = GetEventInt(event, "weaponentid");
3780
3781 GivePills(Giver, Recipient, PillsID);
3782}
3783
3784// Pill give code. Special note, Pills can only be given once. (Even if it's initially given by a bot!)
3785
3786GivePills(Giver, Recipient, PillsID = -1)
3787{
3788 // Stats enabled is checked by the caller
3789
3790 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !SurvivalStarted)
3791 return;
3792
3793 if (PillsID < 0)
3794 PillsID = GetPlayerWeaponSlot(Recipient, 4);
3795
3796 if (PillsID < 0 || Pills[PillsID] == 1)
3797 return;
3798 else
3799 Pills[PillsID] = 1;
3800
3801 if (IsClientBot(Giver))
3802 return;
3803
3804 decl String:RecipientName[MAX_LINE_WIDTH];
3805 GetClientName(Recipient, RecipientName, sizeof(RecipientName));
3806 decl String:RecipientID[MAX_LINE_WIDTH];
3807 GetClientRankAuthString(Recipient, RecipientID, sizeof(RecipientID));
3808
3809 decl String:GiverName[MAX_LINE_WIDTH];
3810 GetClientName(Giver, GiverName, sizeof(GiverName));
3811 decl String:GiverID[MAX_LINE_WIDTH];
3812 GetClientRankAuthString(Giver, GiverID, sizeof(GiverID));
3813
3814 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Pills), 2, 4, TEAM_SURVIVORS);
3815 decl String:UpdatePoints[32];
3816
3817 switch (CurrentGamemodeID)
3818 {
3819 case GAMEMODE_VERSUS:
3820 {
3821 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3822 }
3823 case GAMEMODE_REALISM:
3824 {
3825 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3826 }
3827 case GAMEMODE_SURVIVAL:
3828 {
3829 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3830 }
3831 case GAMEMODE_SCAVENGE:
3832 {
3833 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3834 }
3835 case GAMEMODE_REALISMVERSUS:
3836 {
3837 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3838 }
3839 case GAMEMODE_OTHERMUTATIONS:
3840 {
3841 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3842 }
3843 default:
3844 {
3845 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3846 }
3847 }
3848
3849 decl String:query[1024];
3850 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_pills = award_pills + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, GiverID);
3851 SendSQLUpdate(query);
3852
3853 UpdateMapStat("points", Score);
3854 AddScore(Giver, Score);
3855
3856 if (Score > 0)
3857 {
3858 new Mode = GetConVarInt(cvar_AnnounceMode);
3859
3860 if (Mode == 1 || Mode == 2)
3861 StatsPrintToChat(Giver, "You have earned \x04%i \x01points for giving pills to \x05%s\x01!", Score, RecipientName);
3862 else if (Mode == 3)
3863 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for giving pills to \x05%s\x01!", GiverName, Score, RecipientName);
3864 }
3865}
3866
3867// Defibrillator used code.
3868
3869public Action:event_DefibPlayer(Handle:event, const String:name[], bool:dontBroadcast)
3870{
3871 if (StatsDisabled() || CampaignOver)
3872 return;
3873
3874 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && (!SurvivalStarted || !GetConVarBool(cvar_EnableSvMedicPoints)))
3875 return;
3876
3877 new Recipient = GetClientOfUserId(GetEventInt(event, "subject"));
3878 new Giver = GetClientOfUserId(GetEventInt(event, "userid"));
3879
3880 new bool:GiverIsBot = IsClientBot(Giver);
3881 new bool:RecipientIsBot = IsClientBot(Recipient);
3882
3883 if (CurrentGamemodeID != GAMEMODE_SURVIVAL && (!GiverIsBot || (GiverIsBot && (GetConVarInt(cvar_MedkitBotMode) >= 2 || (!RecipientIsBot && GetConVarInt(cvar_MedkitBotMode) >= 1)))))
3884 {
3885 MedkitsUsedCounter++;
3886 AnnounceMedkitPenalty();
3887 }
3888
3889 if (IsClientBot(Giver))
3890 return;
3891
3892 // How is this possible?
3893 if (Recipient == Giver)
3894 return;
3895
3896 decl String:RecipientName[MAX_LINE_WIDTH];
3897 GetClientName(Recipient, RecipientName, sizeof(RecipientName));
3898 decl String:RecipientID[MAX_LINE_WIDTH];
3899 GetClientRankAuthString(Recipient, RecipientID, sizeof(RecipientID));
3900
3901 decl String:GiverName[MAX_LINE_WIDTH];
3902 GetClientName(Giver, GiverName, sizeof(GiverName));
3903 decl String:GiverID[MAX_LINE_WIDTH];
3904 GetClientRankAuthString(Giver, GiverID, sizeof(GiverID));
3905
3906 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Defib), 2, 4, TEAM_SURVIVORS);
3907
3908 decl String:UpdatePoints[32];
3909
3910 switch (CurrentGamemodeID)
3911 {
3912 case GAMEMODE_VERSUS:
3913 {
3914 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
3915 }
3916 case GAMEMODE_REALISM:
3917 {
3918 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
3919 }
3920 case GAMEMODE_SURVIVAL:
3921 {
3922 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
3923 }
3924 case GAMEMODE_SCAVENGE:
3925 {
3926 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
3927 }
3928 case GAMEMODE_REALISMVERSUS:
3929 {
3930 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
3931 }
3932 case GAMEMODE_OTHERMUTATIONS:
3933 {
3934 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
3935 }
3936 default:
3937 {
3938 Format(UpdatePoints, sizeof(UpdatePoints), "points");
3939 }
3940 }
3941
3942 decl String:query[1024];
3943 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_defib = award_defib + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, GiverID);
3944 SendSQLUpdate(query);
3945
3946 UpdateMapStat("points", Score);
3947 AddScore(Giver, Score);
3948
3949 if (Score > 0)
3950 {
3951 new Mode = GetConVarInt(cvar_AnnounceMode);
3952 if (Mode == 1 || Mode == 2)
3953 StatsPrintToChat(Giver, "You have earned \x04%i \x01points for Reviving \x05%s\x01 using a Defibrillator!", Score, RecipientName);
3954 else if (Mode == 3)
3955 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Reviving \x05%s\x01 using a Defibrillator!", GiverName, Score, RecipientName);
3956 }
3957}
3958
3959// Medkit give code.
3960
3961public Action:event_HealPlayer(Handle:event, const String:name[], bool:dontBroadcast)
3962{
3963 if (StatsDisabled() || CampaignOver)
3964 return;
3965
3966 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && (!SurvivalStarted || !GetConVarBool(cvar_EnableSvMedicPoints)))
3967 return;
3968
3969 new Recipient = GetClientOfUserId(GetEventInt(event, "subject"));
3970 new Giver = GetClientOfUserId(GetEventInt(event, "userid"));
3971 new Amount = GetEventInt(event, "health_restored");
3972
3973 new bool:GiverIsBot = IsClientBot(Giver);
3974 new bool:RecipientIsBot = IsClientBot(Recipient);
3975
3976 if (CurrentGamemodeID != GAMEMODE_SURVIVAL && (!GiverIsBot || (GiverIsBot && (GetConVarInt(cvar_MedkitBotMode) >= 2 || (!RecipientIsBot && GetConVarInt(cvar_MedkitBotMode) >= 1)))))
3977 {
3978 MedkitsUsedCounter++;
3979 AnnounceMedkitPenalty();
3980 }
3981
3982 if (GiverIsBot)
3983 return;
3984
3985 if (Recipient == Giver)
3986 return;
3987
3988 decl String:RecipientName[MAX_LINE_WIDTH];
3989 GetClientName(Recipient, RecipientName, sizeof(RecipientName));
3990 decl String:RecipientID[MAX_LINE_WIDTH];
3991 GetClientRankAuthString(Recipient, RecipientID, sizeof(RecipientID));
3992
3993 decl String:GiverName[MAX_LINE_WIDTH];
3994 GetClientName(Giver, GiverName, sizeof(GiverName));
3995 decl String:GiverID[MAX_LINE_WIDTH];
3996 GetClientRankAuthString(Giver, GiverID, sizeof(GiverID));
3997
3998 new Score = (Amount + 1) / 2;
3999 if (GetConVarInt(cvar_MedkitMode))
4000 Score = ModifyScoreDifficulty(GetConVarInt(cvar_Medkit), 2, 4, TEAM_SURVIVORS);
4001 else
4002 Score = ModifyScoreDifficulty(Score, 2, 3, TEAM_SURVIVORS);
4003
4004 decl String:UpdatePoints[32];
4005
4006 switch (CurrentGamemodeID)
4007 {
4008 case GAMEMODE_VERSUS:
4009 {
4010 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
4011 }
4012 case GAMEMODE_REALISM:
4013 {
4014 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
4015 }
4016 case GAMEMODE_SURVIVAL:
4017 {
4018 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
4019 }
4020 case GAMEMODE_SCAVENGE:
4021 {
4022 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
4023 }
4024 case GAMEMODE_REALISMVERSUS:
4025 {
4026 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
4027 }
4028 case GAMEMODE_OTHERMUTATIONS:
4029 {
4030 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
4031 }
4032 default:
4033 {
4034 Format(UpdatePoints, sizeof(UpdatePoints), "points");
4035 }
4036 }
4037
4038 decl String:query[1024];
4039 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_medkit = award_medkit + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, GiverID);
4040 SendSQLUpdate(query);
4041
4042 UpdateMapStat("points", Score);
4043 AddScore(Giver, Score);
4044
4045 if (Score > 0)
4046 {
4047 new Mode = GetConVarInt(cvar_AnnounceMode);
4048 if (Mode == 1 || Mode == 2)
4049 StatsPrintToChat(Giver, "You have earned \x04%i \x01points for healing \x05%s\x01!", Score, RecipientName);
4050 else if (Mode == 3)
4051 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for healing \x05%s\x01!", GiverName, Score, RecipientName);
4052 }
4053}
4054
4055// Friendly fire code.
4056
4057public Action:event_FriendlyFire(Handle:event, const String:name[], bool:dontBroadcast)
4058{
4059 if (StatsDisabled() || CampaignOver)
4060 return;
4061
4062 new Attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
4063 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
4064
4065 if (!Attacker || !Victim)
4066 return;
4067
4068// if (IsClientBot(Victim))
4069// return;
4070
4071 new FFMode = GetConVarInt(cvar_FriendlyFireMode);
4072
4073 if (FFMode == 1)
4074 {
4075 new CooldownMode = GetConVarInt(cvar_FriendlyFireCooldownMode);
4076
4077 if (CooldownMode == 1 || CooldownMode == 2)
4078 {
4079 new Target = 0;
4080
4081 // Player specific : CooldownMode = 1
4082 // General : CooldownMode = 2
4083 if (CooldownMode == 1)
4084 Target = Victim;
4085
4086 if (FriendlyFireCooldown[Attacker][Target])
4087 return;
4088
4089 FriendlyFireCooldown[Attacker][Target] = true;
4090
4091 if (FriendlyFirePrmCounter >= MAXPLAYERS)
4092 FriendlyFirePrmCounter = 0;
4093
4094 FriendlyFirePrm[FriendlyFirePrmCounter][0] = Attacker;
4095 FriendlyFirePrm[FriendlyFirePrmCounter][1] = Target;
4096 FriendlyFireTimer[Attacker][Target] = CreateTimer(GetConVarFloat(cvar_FriendlyFireCooldown), timer_FriendlyFireCooldownEnd, FriendlyFirePrmCounter++);
4097 }
4098 }
4099 else if (FFMode == 2)
4100 {
4101 // Friendly fire is calculated in player_hurt event (Damage based)
4102 return;
4103 }
4104
4105 UpdateFriendlyFire(Attacker, Victim);
4106}
4107
4108// Campaign win code.
4109
4110public Action:event_CampaignWin(Handle:event, const String:name[], bool:dontBroadcast)
4111{
4112 if (CampaignOver || StatsDisabled())
4113 return;
4114
4115 CampaignOver = true;
4116
4117 StopMapTiming();
4118
4119 if (CurrentGamemodeID == GAMEMODE_SCAVENGE ||
4120 CurrentGamemodeID == GAMEMODE_SURVIVAL)
4121 return;
4122
4123 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_VictorySurvivors), 4, 12, TEAM_SURVIVORS);
4124 new Mode = GetConVarInt(cvar_AnnounceMode);
4125 new SurvivorCount = GetEventInt(event, "survivorcount");
4126 new ClientTeam, bool:NegativeScore = GetConVarBool(cvar_EnableNegativeScore);
4127
4128 Score *= SurvivorCount;
4129
4130 decl String:query[1024];
4131 decl String:iID[MAX_LINE_WIDTH];
4132 decl String:UpdatePoints[32], String:UpdatePointsPenalty[32];
4133
4134 switch (CurrentGamemodeID)
4135 {
4136 case GAMEMODE_VERSUS:
4137 {
4138 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
4139 Format(UpdatePointsPenalty, sizeof(UpdatePointsPenalty), "points_infected");
4140 }
4141 case GAMEMODE_REALISM:
4142 {
4143 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
4144 }
4145 case GAMEMODE_REALISMVERSUS:
4146 {
4147 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
4148 Format(UpdatePointsPenalty, sizeof(UpdatePointsPenalty), "points_realism_infected");
4149 }
4150 case GAMEMODE_OTHERMUTATIONS:
4151 {
4152 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
4153 }
4154 default:
4155 {
4156 Format(UpdatePoints, sizeof(UpdatePoints), "points");
4157 }
4158 }
4159
4160 new maxplayers = GetMaxClients();
4161 for (new i = 1; i <= maxplayers; i++)
4162 {
4163 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
4164 {
4165 ClientTeam = GetClientTeam(i);
4166
4167 if (ClientTeam == TEAM_SURVIVORS)
4168 {
4169 GetClientRankAuthString(i, iID, sizeof(iID));
4170
4171 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_campaigns = award_campaigns + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
4172 SendSQLUpdate(query);
4173
4174 if (Score > 0)
4175 {
4176 UpdateMapStat("points", Score);
4177 AddScore(i, Score);
4178 }
4179 }
4180 else if (ClientTeam == TEAM_INFECTED && NegativeScore)
4181 {
4182 GetClientRankAuthString(i, iID, sizeof(iID));
4183
4184 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i WHERE steamid = '%s'", DbPrefix, UpdatePointsPenalty, UpdatePointsPenalty, Score, iID);
4185 SendSQLUpdate(query);
4186
4187 if (Score < 0)
4188 AddScore(i, Score * (-1));
4189 }
4190 }
4191 }
4192
4193 if (Mode && Score > 0)
4194 {
4195 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have earned \x04%i \x01points for winning the \x04Campaign Finale \x01with \x05%i survivors\x01!", Score, SurvivorCount);
4196
4197 if (NegativeScore)
4198 StatsPrintToChatTeam(TEAM_INFECTED, "\x03ALL INFECTED \x01have \x03LOST \x04%i \x01points for loosing the \x04Campaign Finale \x01to \x05%i survivors\x01!", Score, SurvivorCount);
4199 }
4200}
4201
4202// Safe House reached code. Points are given to all players.
4203// Also, Witch Not Disturbed code, points also given to all players.
4204
4205public Action:event_MapTransition(Handle:event, const String:name[], bool:dontBroadcast)
4206{
4207 if (StatsDisabled())
4208 return;
4209
4210 CheckSurvivorsWin();
4211}
4212
4213// Begin panic event.
4214
4215public Action:event_PanicEvent(Handle:event, const String:name[], bool:dontBroadcast)
4216{
4217 if (StatsDisabled())
4218 return;
4219
4220 if (CampaignOver || PanicEvent)
4221 return;
4222
4223 PanicEvent = true;
4224
4225 if (CurrentGamemodeID == GAMEMODE_SURVIVAL)
4226 {
4227 SurvivalStart();
4228 return;
4229 }
4230
4231 CreateTimer(75.0, timer_PanicEventEnd);
4232}
4233
4234// Panic Event with no Incaps code. Points given to all players.
4235
4236public Action:timer_PanicEventEnd(Handle:timer, Handle:hndl)
4237{
4238 if (StatsDisabled())
4239 return;
4240
4241 if (CampaignOver || CurrentGamemodeID == GAMEMODE_SURVIVAL)
4242 return;
4243
4244 new Mode = GetConVarInt(cvar_AnnounceMode);
4245
4246 if (PanicEvent && !PanicEventIncap)
4247 {
4248 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Panic), 2, 4, TEAM_SURVIVORS);
4249
4250 if (Score > 0)
4251 {
4252 decl String:query[1024];
4253 decl String:iID[MAX_LINE_WIDTH];
4254 decl String:UpdatePoints[32];
4255
4256 switch (CurrentGamemodeID)
4257 {
4258 case GAMEMODE_VERSUS:
4259 {
4260 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
4261 }
4262 case GAMEMODE_REALISM:
4263 {
4264 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
4265 }
4266 case GAMEMODE_SCAVENGE:
4267 {
4268 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
4269 }
4270 case GAMEMODE_REALISMVERSUS:
4271 {
4272 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
4273 }
4274 case GAMEMODE_OTHERMUTATIONS:
4275 {
4276 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
4277 }
4278 default:
4279 {
4280 Format(UpdatePoints, sizeof(UpdatePoints), "points");
4281 }
4282 }
4283
4284 new maxplayers = GetMaxClients();
4285 for (new i = 1; i <= maxplayers; i++)
4286 {
4287 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
4288 {
4289 GetClientRankAuthString(i, iID, sizeof(iID));
4290 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i WHERE steamid = '%s' ", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
4291 SendSQLUpdate(query);
4292 UpdateMapStat("points", Score);
4293 AddScore(i, Score);
4294 }
4295 }
4296
4297 if (Mode)
4298 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have earned \x04%i \x01points for \x05No Incapicitates Or Deaths After Panic Event\x01!", Score);
4299 }
4300 }
4301
4302 PanicEvent = false;
4303 PanicEventIncap = false;
4304}
4305
4306// Begin Boomer blind.
4307
4308public Action:event_PlayerBlind(Handle:event, const String:name[], bool:dontBroadcast)
4309{
4310 if (StatsDisabled() || CampaignOver)
4311 return;
4312
4313 new Attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
4314
4315 if (StatsGetClientTeam(Attacker) != TEAM_INFECTED)
4316 return;
4317
4318 PlayerVomited = true;
4319
4320// new bool:Infected = GetEventBool(event, "infected");
4321//
4322// if (!Infected)
4323// return;
4324
4325 new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
4326
4327 if (IsClientBot(Attacker))
4328 return;
4329
4330 PlayerBlinded[Victim][0] = 1;
4331 PlayerBlinded[Victim][1] = Attacker;
4332
4333 BoomerHitCounter[Attacker]++;
4334
4335 if (TimerBoomerPerfectCheck[Attacker] != INVALID_HANDLE)
4336 {
4337 CloseHandle(TimerBoomerPerfectCheck[Attacker]);
4338 TimerBoomerPerfectCheck[Attacker] = INVALID_HANDLE;
4339 }
4340
4341 TimerBoomerPerfectCheck[Attacker] = CreateTimer(6.0, timer_BoomerBlindnessCheck, Attacker);
4342}
4343
4344// Boomer Mob Survival with no Incaps code. Points are given to all players.
4345
4346public Action:event_PlayerBlindEnd(Handle:event, const String:name[], bool:dontBroadcast)
4347{
4348 if (StatsDisabled())
4349 return;
4350
4351 new Player = GetClientOfUserId(GetEventInt(event, "userid"));
4352
4353 if (StatsGetClientTeam(Player) != TEAM_SURVIVORS)
4354 return;
4355
4356 if (Player > 0)
4357 CreateTimer(INF_WEAROFF_TIME, timer_EndBoomerBlinded, Player);
4358
4359 new Mode = GetConVarInt(cvar_AnnounceMode);
4360
4361 if (PlayerVomited && !PlayerVomitedIncap)
4362 {
4363 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_BoomerMob), 2, 5, TEAM_SURVIVORS);
4364
4365 if (Score > 0)
4366 {
4367 decl String:query[1024];
4368 decl String:iID[MAX_LINE_WIDTH];
4369 decl String:UpdatePoints[32];
4370
4371 switch (CurrentGamemodeID)
4372 {
4373 case GAMEMODE_VERSUS:
4374 {
4375 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
4376 }
4377 case GAMEMODE_REALISM:
4378 {
4379 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
4380 }
4381 case GAMEMODE_SURVIVAL:
4382 {
4383 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
4384 }
4385 case GAMEMODE_SCAVENGE:
4386 {
4387 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
4388 }
4389 case GAMEMODE_REALISMVERSUS:
4390 {
4391 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
4392 }
4393 case GAMEMODE_OTHERMUTATIONS:
4394 {
4395 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
4396 }
4397 default:
4398 {
4399 Format(UpdatePoints, sizeof(UpdatePoints), "points");
4400 }
4401 }
4402
4403 new maxplayers = GetMaxClients();
4404 for (new i = 1; i <= maxplayers; i++)
4405 {
4406 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
4407 {
4408 GetClientRankAuthString(i, iID, sizeof(iID));
4409 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i WHERE steamid = '%s' ", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
4410 SendSQLUpdate(query);
4411 UpdateMapStat("points", Score);
4412 AddScore(i, Score);
4413 }
4414 }
4415
4416 if (Mode)
4417 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have earned \x04%i \x01points for \x05No Incapicitates Or Deaths After Boomer Mob\x01!", Score);
4418 }
4419 }
4420
4421 PlayerVomited = false;
4422 PlayerVomitedIncap = false;
4423}
4424
4425// Friendly Incapicitate code. Also handles if players should be awarded
4426// points for surviving a Panic Event or Boomer Mob without incaps.
4427
4428PlayerIncap(Attacker, Victim)
4429{
4430 // Stats enabled and CampaignOver is checked by the caller
4431
4432 if (PanicEvent)
4433 PanicEventIncap = true;
4434
4435 if (PlayerVomited)
4436 PlayerVomitedIncap = true;
4437
4438 if (Victim <= 0)
4439 return;
4440
4441 if (!Attacker || IsClientBot(Attacker))
4442 {
4443 // Attacker is normal indected but the Victim was infected by blinding and/or paralysation.
4444 if (Attacker == 0
4445 && Victim > 0
4446 && (PlayerBlinded[Victim][0] && PlayerBlinded[Victim][1]
4447 || PlayerParalyzed[Victim][0] && PlayerParalyzed[Victim][1]
4448 || PlayerLunged[Victim][0] && PlayerLunged[Victim][1]
4449 || PlayerCarried[Victim][0] && PlayerCarried[Victim][1]
4450 || PlayerPlummeled[Victim][0] && PlayerPlummeled[Victim][1]
4451 || PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
4452 && IsGamemodeVersus())
4453 PlayerIncapExternal(Victim);
4454
4455 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && Victim > 0)
4456 CheckSurvivorsAllDown();
4457
4458 return;
4459 }
4460
4461 new AttackerTeam = GetClientTeam(Attacker);
4462 new VictimTeam = GetClientTeam(Victim);
4463 new Mode = GetConVarInt(cvar_AnnounceMode);
4464
4465 if (VictimTeam == TEAM_SURVIVORS)
4466 CheckSurvivorsAllDown();
4467
4468 // Attacker is a Survivor
4469 if (AttackerTeam == TEAM_SURVIVORS && VictimTeam == TEAM_SURVIVORS)
4470 {
4471 decl String:AttackerID[MAX_LINE_WIDTH];
4472 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
4473 decl String:AttackerName[MAX_LINE_WIDTH];
4474 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
4475
4476 decl String:VictimName[MAX_LINE_WIDTH];
4477 GetClientName(Victim, VictimName, sizeof(VictimName));
4478
4479 new Score = 0;
4480 if (GetConVarBool(cvar_EnableNegativeScore))
4481 {
4482 if (!IsClientBot(Victim))
4483 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_FIncap), 2, 4, TEAM_SURVIVORS);
4484 else
4485 {
4486 new Float:BotScoreMultiplier = GetConVarFloat(cvar_BotScoreMultiplier);
4487
4488 if (BotScoreMultiplier > 0.0)
4489 Score = RoundToNearest(ModifyScoreDifficultyNR(GetConVarInt(cvar_FIncap), 2, 4, TEAM_SURVIVORS) * BotScoreMultiplier);
4490 }
4491 }
4492 else
4493 Mode = 0;
4494
4495 decl String:UpdatePoints[32];
4496
4497 switch (CurrentGamemodeID)
4498 {
4499 case GAMEMODE_VERSUS:
4500 {
4501 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
4502 }
4503 case GAMEMODE_REALISM:
4504 {
4505 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
4506 }
4507 case GAMEMODE_SURVIVAL:
4508 {
4509 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
4510 }
4511 case GAMEMODE_SCAVENGE:
4512 {
4513 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
4514 }
4515 case GAMEMODE_REALISMVERSUS:
4516 {
4517 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
4518 }
4519 case GAMEMODE_OTHERMUTATIONS:
4520 {
4521 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
4522 }
4523 default:
4524 {
4525 Format(UpdatePoints, sizeof(UpdatePoints), "points");
4526 }
4527 }
4528
4529 decl String:query[512];
4530 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i, award_fincap = award_fincap + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, AttackerID);
4531 SendSQLUpdate(query);
4532
4533 if (Mode == 1 || Mode == 2)
4534 StatsPrintToChat(Attacker, "You have \x03LOST \x04%i \x01points for \x03Incapicitating \x05%s\x01!", Score, VictimName);
4535 else if (Mode == 3)
4536 StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for \x03Incapicitating \x05%s\x01!", AttackerName, Score, VictimName);
4537 }
4538
4539 // Attacker is an Infected
4540 else if (AttackerTeam == TEAM_INFECTED && VictimTeam == TEAM_SURVIVORS)
4541 {
4542 SurvivorIncappedByInfected(Attacker, Victim, Mode);
4543 }
4544}
4545
4546// Friendly Incapacitate event.
4547
4548public Action:event_PlayerIncap(Handle:event, const String:name[], bool:dontBroadcast)
4549{
4550 if (StatsDisabled() || CampaignOver)
4551 return;
4552
4553 new Attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
4554 new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
4555
4556 PlayerIncap(Attacker, Victim);
4557}
4558
4559// Save friendly from being dragged by Smoker.
4560
4561public Action:event_TongueSave(Handle:event, const String:name[], bool:dontBroadcast)
4562{
4563 if (StatsDisabled())
4564 return;
4565
4566 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
4567 new Savior = GetClientOfUserId(GetEventInt(event, "userid"));
4568
4569 HunterSmokerSave(Savior, Victim, GetConVarInt(cvar_SmokerDrag), 2, 3, "Smoker", "award_smoker");
4570}
4571
4572// Save friendly from being choked by Smoker.
4573
4574public Action:event_ChokeSave(Handle:event, const String:name[], bool:dontBroadcast)
4575{
4576 if (StatsDisabled())
4577 return;
4578
4579 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
4580 new Savior = GetClientOfUserId(GetEventInt(event, "userid"));
4581
4582 HunterSmokerSave(Savior, Victim, GetConVarInt(cvar_ChokePounce), 2, 3, "Smoker", "award_smoker");
4583}
4584
4585// Save friendly from being pounced by Hunter.
4586
4587public Action:event_PounceSave(Handle:event, const String:name[], bool:dontBroadcast)
4588{
4589 if (StatsDisabled())
4590 return;
4591
4592 new Savior = GetClientOfUserId(GetEventInt(event, "userid"));
4593 new Victim = GetClientOfUserId(GetEventInt(event, "Victim"));
4594
4595 if (Victim > 0)
4596 CreateTimer(INF_WEAROFF_TIME, timer_EndHunterLunged, Victim);
4597
4598 HunterSmokerSave(Savior, Victim, GetConVarInt(cvar_ChokePounce), 2, 3, "Hunter", "award_hunter");
4599}
4600
4601// Player is hanging from a ledge.
4602
4603public Action:event_PlayerFallDamage(Handle:event, const String:name[], bool:dontBroadcast)
4604{
4605 if (StatsDisabled() || CampaignOver || !IsGamemodeVersus())
4606 return;
4607
4608 new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
4609 new Attacker = GetClientOfUserId(GetEventInt(event, "causer"));
4610 new Damage = RoundToNearest(GetEventFloat(event, "damage"));
4611
4612 if (Attacker == 0 && PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
4613 Attacker = PlayerJockied[Victim][1];
4614
4615 if (Attacker == 0 || IsClientBot(Attacker) || GetClientTeam(Attacker) != TEAM_INFECTED || GetClientTeam(Victim) != TEAM_SURVIVORS || Damage <= 0)
4616 return;
4617
4618 new VictimHealth = GetClientHealth(Victim);
4619 new VictimIsIncap = GetEntProp(Victim, Prop_Send, "m_isIncapacitated");
4620
4621 // If the victim health is zero or below zero or is incapacitated don't count the damage from the fall
4622 if (VictimHealth <= 0 || VictimIsIncap != 0)
4623 return;
4624
4625 // Damage should never exceed the amount of healt the fallen survivor had before falling down.
4626 if (VictimHealth < Damage)
4627 Damage = VictimHealth;
4628
4629 if (Damage <= 0)
4630 return;
4631
4632 SurvivorHurt(Attacker, Victim, Damage);
4633}
4634
4635// Player melee killed an infected
4636
4637public Action:event_MeleeKill(Handle:event, const String:name[], bool:dontBroadcast)
4638{
4639 if (StatsDisabled() || CampaignOver)
4640 return;
4641
4642 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
4643 //new EntityID = GetEventInt(event, "entityid");
4644 //new bool:Ambushed = GetEventBool(event, "ambush");
4645
4646 if (Attacker == 0 || IsClientBot(Attacker) || GetClientTeam(Attacker) != TEAM_SURVIVORS || !IsClientConnected(Attacker) || !IsClientInGame(Attacker))
4647 return;
4648
4649 IncrementMeleeKills(Attacker);
4650}
4651
4652// Player is hanging from a ledge.
4653
4654public Action:event_PlayerLedge(Handle:event, const String:name[], bool:dontBroadcast)
4655{
4656 if (StatsDisabled() || !IsGamemodeVersus())
4657 return;
4658
4659 new Attacker = GetClientOfUserId(GetEventInt(event, "causer"));
4660 new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
4661
4662 if (Attacker == 0 && PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
4663 Attacker = PlayerJockied[Victim][1];
4664
4665 if (Attacker == 0 || IsClientBot(Attacker) || GetClientTeam(Attacker) != TEAM_INFECTED)
4666 return;
4667
4668 new Score = ModifyScoreDifficultyFloat(GetConVarInt(cvar_PlayerLedgeSuccess), 0.9, 0.8, TEAM_INFECTED);
4669
4670 if (Score > 0)
4671 {
4672 decl String:VictimName[MAX_LINE_WIDTH];
4673 GetClientName(Victim, VictimName, sizeof(VictimName));
4674
4675 new Mode = GetConVarInt(cvar_AnnounceMode);
4676
4677 decl String:ClientID[MAX_LINE_WIDTH];
4678 GetClientRankAuthString(Attacker, ClientID, sizeof(ClientID));
4679
4680 decl String:query[1024];
4681 if (CurrentGamemodeID == GAMEMODE_VERSUS)
4682 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_ledgegrab = award_ledgegrab + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
4683 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
4684 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_ledgegrab = award_ledgegrab + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
4685 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
4686 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_ledgegrab = award_ledgegrab + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
4687 else
4688 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_ledgegrab = award_ledgegrab + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
4689
4690 SendSQLUpdate(query);
4691
4692 UpdateMapStat("points_infected", Score);
4693
4694 if (Mode == 1 || Mode == 2)
4695 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for causing player \x05%s\x01 to grab a ledge!", Score, VictimName);
4696 else if (Mode == 3)
4697 {
4698 decl String:AttackerName[MAX_LINE_WIDTH];
4699 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
4700 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for causing player \x05%s\x01 to grab a ledge!", AttackerName, Score, VictimName);
4701 }
4702 }
4703}
4704
4705// Player spawned in game.
4706
4707public Action:event_PlayerSpawn(Handle:event, const String:name[], bool:dontBroadcast)
4708{
4709 if (StatsDisabled())
4710 return;
4711
4712 new Player = GetClientOfUserId(GetEventInt(event, "userid"));
4713
4714 if (Player == 0)
4715 return;
4716
4717 InitializeClientInf(Player);
4718
4719 ClientInfectedType[Player] = 0;
4720 BoomerHitCounter[Player] = 0;
4721 BoomerVomitUpdated[Player] = false;
4722 SmokerDamageCounter[Player] = 0;
4723 SpitterDamageCounter[Player] = 0;
4724 JockeyDamageCounter[Player] = 0;
4725 ChargerDamageCounter[Player] = 0;
4726 ChargerImpactCounter[Player] = 0;
4727 TankPointsCounter[Player] = 0;
4728 TankDamageCounter[Player] = 0;
4729 TankDamageTotalCounter[Player] = 0;
4730 TankSurvivorKillCounter[Player] = 0;
4731 ChargerCarryVictim[Player] = 0;
4732 ChargerPlummelVictim[Player] = 0;
4733 JockeyVictim[Player] = 0;
4734 JockeyRideStartTime[Player] = 0;
4735
4736 PlayerBlinded[Player][0] = 0;
4737 PlayerBlinded[Player][1] = 0;
4738 PlayerParalyzed[Player][0] = 0;
4739 PlayerParalyzed[Player][1] = 0;
4740 PlayerLunged[Player][0] = 0;
4741 PlayerLunged[Player][1] = 0;
4742 PlayerPlummeled[Player][0] = 0;
4743 PlayerPlummeled[Player][1] = 0;
4744 PlayerCarried[Player][0] = 0;
4745 PlayerCarried[Player][1] = 0;
4746 PlayerJockied[Player][0] = 0;
4747 PlayerJockied[Player][1] = 0;
4748
4749 if (!IsClientBot(Player))
4750 SetClientInfectedType(Player);
4751
4752 if (ChargerImpactCounterTimer[Player] != INVALID_HANDLE)
4753 CloseHandle(ChargerImpactCounterTimer[Player]);
4754
4755 ChargerImpactCounterTimer[Player] = INVALID_HANDLE;
4756}
4757
4758// Player hurt. Used for calculating damage points for the Infected players and also
4759// the friendly fire damage when Friendly Fire Mode is set to Damage Based.
4760
4761public Action:event_PlayerHurt(Handle:event, const String:name[], bool:dontBroadcast)
4762{
4763 if (StatsDisabled() || CampaignOver)
4764 return;
4765
4766 new Attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
4767 new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
4768
4769 // Self inflicted damage does not count
4770 if (Attacker == Victim)
4771 return;
4772
4773 if (Attacker == 0 || IsClientBot(Attacker))
4774 {
4775 // Attacker is normal indected but the Victim was infected by blinding and/or paralysation.
4776 if (Attacker == 0
4777 && Victim > 0
4778 && (PlayerBlinded[Victim][0] && PlayerBlinded[Victim][1]
4779 || PlayerParalyzed[Victim][0] && PlayerParalyzed[Victim][1]
4780 || PlayerLunged[Victim][0] && PlayerLunged[Victim][1]
4781 || PlayerCarried[Victim][0] && PlayerCarried[Victim][1]
4782 || PlayerPlummeled[Victim][0] && PlayerPlummeled[Victim][1]
4783 || PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
4784 && IsGamemodeVersus())
4785 SurvivorHurtExternal(event, Victim);
4786
4787 return;
4788 }
4789
4790 new Damage = GetEventInt(event, "dmg_health");
4791 new AttackerTeam = GetClientTeam(Attacker);
4792 new AttackerInfType = -1;
4793
4794 new VictimTeam = GetClientTeam(Victim);
4795 if (AttackerTeam == VictimTeam && AttackerTeam == TEAM_INFECTED)
4796 return;
4797
4798 if (Attacker > 0)
4799 {
4800 if (AttackerTeam == TEAM_INFECTED)
4801 AttackerInfType = ClientInfectedType[Attacker];
4802 else if (AttackerTeam == TEAM_SURVIVORS && GetConVarInt(cvar_FriendlyFireMode) == 2)
4803 {
4804 if (VictimTeam == TEAM_SURVIVORS)
4805 {
4806 if (FriendlyFireTimer[Attacker][0] != INVALID_HANDLE)
4807 {
4808 CloseHandle(FriendlyFireTimer[Attacker][0]);
4809 FriendlyFireTimer[Attacker][0] = INVALID_HANDLE;
4810 }
4811
4812 decl String:AttackerID[MAX_LINE_WIDTH];
4813 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
4814 decl String:AttackerName[MAX_LINE_WIDTH];
4815 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
4816
4817 // Using datapack to deliver the needed info so that the attacker can't escape the penalty by disconnecting
4818
4819 new Handle:dp = INVALID_HANDLE;
4820 new OldHumanDamage = 0;
4821 new OldBotDamage = 0;
4822
4823 if (!GetTrieValue(FriendlyFireDamageTrie, AttackerID, dp))
4824 {
4825 dp = CreateDataPack();
4826 SetTrieValue(FriendlyFireDamageTrie, AttackerID, dp);
4827 }
4828 else
4829 {
4830 // Read old damage value
4831 ResetPack(dp);
4832 OldHumanDamage = ReadPackCell(dp);
4833 OldBotDamage = ReadPackCell(dp);
4834 }
4835
4836 if (IsClientBot(Victim))
4837 OldBotDamage += Damage;
4838 else
4839 OldHumanDamage += Damage;
4840
4841 ResetPack(dp, true);
4842
4843 WritePackCell(dp, OldHumanDamage);
4844 WritePackCell(dp, OldBotDamage);
4845 WritePackCell(dp, Attacker);
4846 WritePackString(dp, AttackerID);
4847 WritePackString(dp, AttackerName);
4848
4849 // This may fail! What happens when a player skips and another joins with the same Client ID (is this even possible in such short time?)
4850 FriendlyFireTimer[Attacker][0] = CreateTimer(5.0, timer_FriendlyFireDamageEnd, dp);
4851
4852 return;
4853 }
4854 }
4855 }
4856 if (AttackerInfType < 0)
4857 return;
4858
4859// decl String:AttackerID[MAX_LINE_WIDTH];
4860// GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
4861
4862// new Mode;
4863// new Victim = GetClientOfUserId(GetEventInt(event, "userid"));
4864// decl String:VictimName[MAX_LINE_WIDTH];
4865// new VictimTeam = 0;
4866// new Score = 0;
4867
4868// if (Victim > 0)
4869// {
4870// GetClientName(Victim, VictimName, sizeof(VictimName));
4871// VictimTeam = GetClientTeam(Victim);
4872// }
4873// else
4874// Format(VictimName, sizeof(VictimName), "UNKNOWN");
4875
4876// if (VictimTeam == TEAM_INFECTED)
4877// {
4878// decl String:query[1024];
4879//
4880// Score = GetConVarInt(cvar_FFire);
4881// Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected - %i WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
4882// SendSQLUpdate(query);
4883//
4884// UpdateMapStat("points_infected", Score * -1);
4885// Mode = GetConVarInt(cvar_AnnounceMode);
4886//
4887// if (Mode == 1 || Mode == 2)
4888// StatsPrintToChat(Attacker, "You have \x03LOST \x04%i \x01points for \x03Friendly Firing \x05%s\x01!", Score, VictimName);
4889// else if (Mode == 3)
4890// {
4891// decl String:AttackerName[MAX_LINE_WIDTH];
4892// GetClientName(Attacker, AttackerName, sizeof(AttackerName));
4893// StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for \x03Friendly Firing \x05%s\x01!", AttackerName, Score, VictimName);
4894// }
4895//
4896// return;
4897// }
4898
4899 SurvivorHurt(Attacker, Victim, Damage, AttackerInfType, event);
4900}
4901
4902// Smoker events.
4903
4904public Action:event_SmokerGrap(Handle:event, const String:name[], bool:dontBroadcast)
4905{
4906 if (StatsDisabled() || !IsGamemodeVersus() || CampaignOver)
4907 return;
4908
4909 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
4910 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
4911
4912 PlayerParalyzed[Victim][0] = 1;
4913 PlayerParalyzed[Victim][1] = Attacker;
4914}
4915
4916// Jockey events.
4917
4918public Action:event_JockeyStart(Handle:event, const String:name[], bool:dontBroadcast)
4919{
4920 if (StatsDisabled() || CampaignOver)
4921 return;
4922
4923 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
4924 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
4925
4926 PlayerJockied[Victim][0] = 1;
4927 PlayerJockied[Victim][1] = Attacker;
4928
4929 JockeyVictim[Attacker] = Victim;
4930 JockeyRideStartTime[Attacker] = 0;
4931
4932 if (Attacker == 0 || IsClientBot(Attacker) || !IsClientConnected(Attacker) || !IsClientInGame(Attacker))
4933 return;
4934
4935 JockeyRideStartTime[Attacker] = GetTime();
4936
4937 decl String:query[1024];
4938 decl String:iID[MAX_LINE_WIDTH];
4939 GetClientRankAuthString(Attacker, iID, sizeof(iID));
4940 Format(query, sizeof(query), "UPDATE %splayers SET jockey_rides = jockey_rides + 1 WHERE steamid = '%s'", DbPrefix, iID);
4941 SendSQLUpdate(query);
4942 UpdateMapStat("jockey_rides", 1);
4943}
4944
4945public Action:event_JockeyRelease(Handle:event, const String:name[], bool:dontBroadcast)
4946{
4947 if (StatsDisabled() || CampaignOver)
4948 return;
4949
4950 new Jockey = GetClientOfUserId(GetEventInt(event, "userid"));
4951 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
4952 new Rescuer = GetClientOfUserId(GetEventInt(event, "rescuer"));
4953 new Float:RideLength = GetEventFloat(event, "ride_length");
4954
4955 if (Rescuer > 0 && !IsClientBot(Rescuer) && IsClientInGame(Rescuer))
4956 {
4957 decl String:query[1024], String:JockeyName[MAX_LINE_WIDTH], String:VictimName[MAX_LINE_WIDTH], String:RescuerName[MAX_LINE_WIDTH], String:RescuerID[MAX_LINE_WIDTH], String:UpdatePoints[32];
4958 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_JockeyRide), 2, 3, TEAM_SURVIVORS);
4959
4960 GetClientRankAuthString(Rescuer, RescuerID, sizeof(RescuerID));
4961
4962 switch (CurrentGamemodeID)
4963 {
4964 case GAMEMODE_VERSUS:
4965 {
4966 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
4967 }
4968 case GAMEMODE_REALISM:
4969 {
4970 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
4971 }
4972 case GAMEMODE_SURVIVAL:
4973 {
4974 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
4975 }
4976 case GAMEMODE_SCAVENGE:
4977 {
4978 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
4979 }
4980 case GAMEMODE_REALISMVERSUS:
4981 {
4982 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
4983 }
4984 case GAMEMODE_OTHERMUTATIONS:
4985 {
4986 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
4987 }
4988 default:
4989 {
4990 Format(UpdatePoints, sizeof(UpdatePoints), "points");
4991 }
4992 }
4993
4994 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_jockey = award_jockey + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, RescuerID);
4995 SendSQLUpdate(query);
4996
4997 if (Score > 0)
4998 {
4999 UpdateMapStat("points", Score);
5000 AddScore(Rescuer, Score);
5001 }
5002
5003 GetClientName(Jockey, JockeyName, sizeof(JockeyName));
5004 GetClientName(Victim, VictimName, sizeof(VictimName));
5005
5006 new Mode = GetConVarInt(cvar_AnnounceMode);
5007
5008 if (Score > 0)
5009 {
5010 if (Mode == 1 || Mode == 2)
5011 StatsPrintToChat(Rescuer, "You have earned \x04%i \x01points for saving \x05%s \x01from \x04%s\x01!", Score, VictimName, JockeyName);
5012 else if (Mode == 3)
5013 {
5014 GetClientName(Rescuer, RescuerName, sizeof(RescuerName));
5015 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for saving \x05%s \x01from \x04%s\x01!", RescuerName, Score, VictimName, JockeyName);
5016 }
5017 }
5018 }
5019
5020 JockeyVictim[Jockey] = 0;
5021
5022 if (Jockey == 0 || IsClientBot(Jockey) || !IsClientInGame(Jockey))
5023 {
5024 PlayerJockied[Victim][0] = 0;
5025 PlayerJockied[Victim][1] = 0;
5026 JockeyRideStartTime[Victim] = 0;
5027 return;
5028 }
5029
5030 UpdateJockeyRideLength(Jockey, RideLength);
5031
5032 if (Victim > 0)
5033 CreateTimer(INF_WEAROFF_TIME, timer_EndJockeyRide, Victim);
5034}
5035
5036public Action:event_JockeyKilled(Handle:event, const String:name[], bool:dontBroadcast)
5037{
5038 if (StatsDisabled())
5039 return;
5040
5041 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5042
5043 if (Attacker == 0 || IsClientBot(Attacker) || !IsClientInGame(Attacker))
5044 return;
5045
5046 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5047
5048 if (Victim > 0)
5049 CreateTimer(INF_WEAROFF_TIME, timer_EndJockeyRide, Victim);
5050}
5051
5052// Charger events.
5053
5054public Action:event_ChargerKilled(Handle:event, const String:name[], bool:dontBroadcast)
5055{
5056 if (StatsDisabled() || CampaignOver)
5057 return;
5058
5059 new Killer = GetClientOfUserId(GetEventInt(event, "attacker"));
5060
5061 if (Killer == 0 || IsClientBot(Killer) || !IsClientInGame(Killer))
5062 return;
5063
5064 new Charger = GetClientOfUserId(GetEventInt(event, "userid"));
5065 decl String:query[1024], String:KillerName[MAX_LINE_WIDTH], String:KillerID[MAX_LINE_WIDTH], String:UpdatePoints[32];
5066 new Score = 0;
5067 new bool:IsMatador = GetEventBool(event, "melee") && GetEventBool(event, "charging");
5068
5069 GetClientRankAuthString(Killer, KillerID, sizeof(KillerID));
5070
5071 switch (CurrentGamemodeID)
5072 {
5073 case GAMEMODE_VERSUS:
5074 {
5075 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
5076 }
5077 case GAMEMODE_REALISM:
5078 {
5079 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
5080 }
5081 case GAMEMODE_SURVIVAL:
5082 {
5083 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
5084 }
5085 case GAMEMODE_SCAVENGE:
5086 {
5087 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
5088 }
5089 case GAMEMODE_REALISMVERSUS:
5090 {
5091 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
5092 }
5093 case GAMEMODE_OTHERMUTATIONS:
5094 {
5095 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
5096 }
5097 default:
5098 {
5099 Format(UpdatePoints, sizeof(UpdatePoints), "points");
5100 }
5101 }
5102
5103 if (ChargerCarryVictim[Charger])
5104 {
5105 Score += ModifyScoreDifficulty(GetConVarInt(cvar_ChargerCarry), 2, 3, TEAM_SURVIVORS);
5106 }
5107 else if (ChargerPlummelVictim[Charger])
5108 {
5109 Score += ModifyScoreDifficulty(GetConVarInt(cvar_ChargerPlummel), 2, 3, TEAM_SURVIVORS);
5110 }
5111
5112 if (IsMatador)
5113 {
5114 // Give a Matador award
5115 Score += ModifyScoreDifficulty(GetConVarInt(cvar_Matador), 2, 3, TEAM_SURVIVORS);
5116 }
5117
5118 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_charger = award_charger + 1%s WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, (IsMatador ? ", award_matador = award_matador + 1" : ""), KillerID);
5119 SendSQLUpdate(query);
5120
5121 if (Score <= 0)
5122 return;
5123
5124 UpdateMapStat("points", Score);
5125 AddScore(Killer, Score);
5126
5127 new Mode = GetConVarInt(cvar_AnnounceMode);
5128
5129 if (Mode)
5130 {
5131 GetClientName(Killer, KillerName, sizeof(KillerName));
5132
5133 if (IsMatador)
5134 {
5135 if (Mode == 1 || Mode == 2)
5136 StatsPrintToChat(Killer, "You have earned \x04%i \x01points for \x04Leveling a Charge\x01!", Score);
5137 else if (Mode == 3)
5138 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for \x04Leveling a Charge\x01!", KillerName, Score);
5139 }
5140 else
5141 {
5142 decl String:VictimName[MAX_LINE_WIDTH], String:ChargerName[MAX_LINE_WIDTH];
5143
5144 GetClientName(Charger, ChargerName, sizeof(ChargerName));
5145
5146 if (ChargerCarryVictim[Charger] > 0 && (IsClientBot(ChargerCarryVictim[Charger]) || (IsClientConnected(ChargerCarryVictim[Charger]) && IsClientInGame(ChargerCarryVictim[Charger]))))
5147 {
5148 GetClientName(ChargerCarryVictim[Charger], VictimName, sizeof(VictimName));
5149 Format(VictimName, sizeof(VictimName), "\x05%s\x01", VictimName);
5150 }
5151 else
5152 Format(VictimName, sizeof(VictimName), "a survivor");
5153
5154 if (Mode == 1 || Mode == 2)
5155 StatsPrintToChat(Killer, "You have earned \x04%i \x01points for saving %s from \x04%s\x01!", Score, VictimName, ChargerName);
5156 else if (Mode == 3)
5157 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for saving %s from \x04%s\x01!", KillerName, Score, VictimName, ChargerName);
5158 }
5159 }
5160}
5161
5162public Action:event_ChargerCarryStart(Handle:event, const String:name[], bool:dontBroadcast)
5163{
5164 if (StatsDisabled())
5165 return;
5166
5167 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5168 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5169
5170 PlayerCarried[Victim][0] = 1;
5171 PlayerCarried[Victim][1] = Attacker;
5172
5173 ChargerCarryVictim[Attacker] = Victim;
5174
5175 if (IsClientBot(Attacker) || !IsClientConnected(Attacker) || !IsClientInGame(Attacker))
5176 return;
5177
5178 IncrementImpactCounter(Attacker);
5179}
5180
5181public Action:event_ChargerCarryRelease(Handle:event, const String:name[], bool:dontBroadcast)
5182{
5183 if (StatsDisabled())
5184 return;
5185
5186 //new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5187 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5188
5189 //if (Attacker == 0 || IsClientBot(Attacker) || !IsClientInGame(Attacker))
5190 //{
5191 // ChargerCarryVictim[Attacker] = 0;
5192 // PlayerCarried[Victim][0] = 0;
5193 // PlayerCarried[Victim][1] = 0;
5194 // return;
5195 //}
5196
5197 if (Victim > 0)
5198 CreateTimer(INF_WEAROFF_TIME, timer_EndChargerCarry, Victim);
5199}
5200
5201public Action:event_ChargerImpact(Handle:event, const String:name[], bool:dontBroadcast)
5202{
5203 if (StatsDisabled() || CampaignOver)
5204 return;
5205
5206 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5207
5208 if (Attacker == 0 || IsClientBot(Attacker) || !IsClientConnected(Attacker) || !IsClientInGame(Attacker))
5209 return;
5210
5211 //new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5212 IncrementImpactCounter(Attacker);
5213}
5214
5215IncrementImpactCounter(client)
5216{
5217 if (ChargerImpactCounterTimer[client] != INVALID_HANDLE)
5218 CloseHandle(ChargerImpactCounterTimer[client]);
5219
5220 ChargerImpactCounterTimer[client] = CreateTimer(3.0, timer_EndCharge, client);
5221
5222 ChargerImpactCounter[client]++;
5223}
5224
5225public Action:event_ChargerPummelStart(Handle:event, const String:name[], bool:dontBroadcast)
5226{
5227 if (StatsDisabled())
5228 return;
5229
5230 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5231
5232 // There is no delay on charger carry once the plummel starts
5233 ChargerCarryVictim[Attacker] = 0;
5234
5235 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5236
5237 PlayerPlummeled[Victim][0] = 1;
5238 PlayerPlummeled[Victim][1] = Attacker;
5239
5240 ChargerPlummelVictim[Attacker] = Victim;
5241
5242 //if (Attacker == 0 || IsClientBot(Attacker) || !IsClientInGame(Attacker))
5243 // return;
5244}
5245
5246public Action:event_ChargerPummelRelease(Handle:event, const String:name[], bool:dontBroadcast)
5247{
5248 if (StatsDisabled())
5249 return;
5250
5251 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5252 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5253
5254 if (Attacker == 0 || IsClientBot(Attacker) || !IsClientInGame(Attacker))
5255 {
5256 PlayerPlummeled[Victim][0] = 0;
5257 PlayerPlummeled[Victim][1] = 0;
5258 ChargerPlummelVictim[Attacker] = 0;
5259 return;
5260 }
5261
5262 if (Victim > 0)
5263 CreateTimer(INF_WEAROFF_TIME, timer_EndChargerPlummel, Victim);
5264}
5265
5266// Hunter events.
5267
5268public Action:event_HunterRelease(Handle:event, const String:name[], bool:dontBroadcast)
5269{
5270 if (StatsDisabled())
5271 return;
5272
5273 new Player = GetClientOfUserId(GetEventInt(event, "victim"));
5274
5275 if (Player > 0)
5276 CreateTimer(INF_WEAROFF_TIME, timer_EndHunterLunged, Player);
5277}
5278
5279// Smoker events.
5280
5281public Action:event_SmokerRelease(Handle:event, const String:name[], bool:dontBroadcast)
5282{
5283 if (StatsDisabled())
5284 return;
5285
5286 new Player = GetClientOfUserId(GetEventInt(event, "victim"));
5287
5288 if (Player > 0)
5289 CreateTimer(INF_WEAROFF_TIME, timer_EndSmokerParalyzed, Player);
5290}
5291
5292// L4D2 ammo upgrade deployed event.
5293
5294public Action:event_UpgradePackAdded(Handle:event, const String:name[], bool:dontBroadcast)
5295{
5296 if (StatsDisabled() || CampaignOver)
5297 return;
5298
5299 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !SurvivalStarted)
5300 return;
5301
5302 new Player = GetClientOfUserId(GetEventInt(event, "userid"));
5303
5304 if (Player == 0 || IsClientBot(Player))
5305 return;
5306
5307 new Score = GetConVarInt(cvar_AmmoUpgradeAdded);
5308
5309 if (Score > 0)
5310 Score = ModifyScoreDifficulty(Score, 2, 3, TEAM_SURVIVORS);
5311
5312 decl String:PlayerID[MAX_LINE_WIDTH];
5313 GetClientRankAuthString(Player, PlayerID, sizeof(PlayerID));
5314
5315 decl String:UpdatePoints[32];
5316
5317 switch (CurrentGamemodeID)
5318 {
5319 case GAMEMODE_VERSUS:
5320 {
5321 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
5322 }
5323 case GAMEMODE_REALISM:
5324 {
5325 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
5326 }
5327 case GAMEMODE_SURVIVAL:
5328 {
5329 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
5330 }
5331 case GAMEMODE_SCAVENGE:
5332 {
5333 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
5334 }
5335 case GAMEMODE_REALISMVERSUS:
5336 {
5337 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
5338 }
5339 case GAMEMODE_OTHERMUTATIONS:
5340 {
5341 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
5342 }
5343 default:
5344 {
5345 Format(UpdatePoints, sizeof(UpdatePoints), "points");
5346 }
5347 }
5348
5349 decl String:query[1024];
5350 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_upgrades_added = award_upgrades_added + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, PlayerID);
5351
5352 SendSQLUpdate(query);
5353
5354 if (Score > 0)
5355 {
5356 new Mode = GetConVarInt(cvar_AnnounceMode);
5357
5358 if (!Mode)
5359 return;
5360
5361 new EntityID = GetEventInt(event, "upgradeid");
5362 decl String:ModelName[128];
5363 GetEntPropString(EntityID, Prop_Data, "m_ModelName", ModelName, sizeof(ModelName));
5364
5365 if (StrContains(ModelName, "incendiary_ammo", false) >= 0)
5366 strcopy(ModelName, sizeof(ModelName), "Incendiary Ammo");
5367 else if (StrContains(ModelName, "exploding_ammo", false) >= 0)
5368 strcopy(ModelName, sizeof(ModelName), "Exploding Ammo");
5369 else
5370 strcopy(ModelName, sizeof(ModelName), "UNKNOWN");
5371
5372 if (Mode == 1 || Mode == 2)
5373 StatsPrintToChat(Player, "You have earned \x04%i \x01points for deploying \x05%s\x01!", Score, ModelName);
5374 else if (Mode == 3)
5375 {
5376 decl String:PlayerName[MAX_LINE_WIDTH];
5377 GetClientName(Player, PlayerName, sizeof(PlayerName));
5378 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for deploying \x05%s\x01!", PlayerName, Score, ModelName);
5379 }
5380 }
5381}
5382
5383// L4D2 gascan pour completed event.
5384
5385public Action:event_GascanPoured(Handle:event, const String:name[], bool:dontBroadcast)
5386{
5387 if (StatsDisabled() || CampaignOver)
5388 return;
5389
5390 new Player = GetClientOfUserId(GetEventInt(event, "userid"));
5391
5392 if (Player == 0 || IsClientBot(Player))
5393 return;
5394
5395 new Score = GetConVarInt(cvar_GascanPoured);
5396
5397 if (Score > 0)
5398 Score = ModifyScoreDifficulty(Score, 2, 3, TEAM_SURVIVORS);
5399
5400 decl String:PlayerID[MAX_LINE_WIDTH];
5401 GetClientRankAuthString(Player, PlayerID, sizeof(PlayerID));
5402
5403 decl String:UpdatePoints[32];
5404
5405 switch (CurrentGamemodeID)
5406 {
5407 case GAMEMODE_VERSUS:
5408 {
5409 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
5410 }
5411 case GAMEMODE_REALISM:
5412 {
5413 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
5414 }
5415 case GAMEMODE_SURVIVAL:
5416 {
5417 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
5418 }
5419 case GAMEMODE_SCAVENGE:
5420 {
5421 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
5422 }
5423 case GAMEMODE_REALISMVERSUS:
5424 {
5425 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
5426 }
5427 case GAMEMODE_OTHERMUTATIONS:
5428 {
5429 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
5430 }
5431 default:
5432 {
5433 Format(UpdatePoints, sizeof(UpdatePoints), "points");
5434 }
5435 }
5436
5437 decl String:query[1024];
5438 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_gascans_poured = award_gascans_poured + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, PlayerID);
5439
5440 SendSQLUpdate(query);
5441
5442 if (Score > 0)
5443 {
5444 new Mode = GetConVarInt(cvar_AnnounceMode);
5445
5446 if (Mode == 1 || Mode == 2)
5447 StatsPrintToChat(Player, "You have earned \x04%i \x01points for successfully \x05Pouring a Gascan\x01!", Score);
5448 else if (Mode == 3)
5449 {
5450 decl String:PlayerName[MAX_LINE_WIDTH];
5451 GetClientName(Player, PlayerName, sizeof(PlayerName));
5452 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for successfully \x05Pouring a Gascan\x01!", PlayerName, Score);
5453 }
5454 }
5455}
5456
5457// Achievement earned.
5458
5459/*
546056 - Helping Hand
546157 - Field Medic
546258 - Pharm-Assist
546359 - My Bodyguard
546460 - Dead Stop
546561 - Crownd
546662 - Untouchables
546763 -
546864 - Drag and Drop
546965 - Blind Luck
547066 - Akimbo Assassin
547167 -
547268 - Hero Closet
547369 - Hunter Punter
547470 - Tongue Twister
547571 - No Smoking Section
547672 -
547773 - 101 Cremations
547874 - Do Not Disturb
547975 - Man Vs Tank
548076 - TankBusters
548177 - Safety First
548278 - No-one Left Behind
548379 -
548480 -
548581 - Unbreakable
548682 - Witch Hunter
548783 - Red Mist
548884 - Pyrotechnician
548985 - Zombie Genocidest
549086 - Dead Giveaway
549187 - Stand Tall
549288 -
549389 -
549490 - Zombicidal Maniac
549591 - What are you trying to Prove?
549692 -
549793 - Nothing Special
549894 - Burn the Witch
549995 - Towering Inferno
550096 - Spinal Tap
550197 - Stomach Upset
550298 - Brain Salad
550399 - Jump Shot
5504100 - Mercy Killer
5505101 - Back 2 Help
5506102 - Toll Collector
5507103 - Dead Baron
5508104 - Grim Reaper
5509105 - Ground Cover
5510106 - Clean Kill
5511107 - Big Drag
5512108 - Chain Smoker
5513109 - Barf Bagged
5514110 - Double Jump
5515111 - All 4 Dead
5516112 - Dead Wreckening
5517113 - Lamb 2 Slaughter
5518114 - Outbreak
5519*/
5520
5521public Action:event_Achievement(Handle:event, const String:name[], bool:dontBroadcast)
5522{
5523 if (StatsDisabled())
5524 {
5525 return;
5526 }
5527
5528 new Player = GetClientOfUserId(GetEventInt(event, "player"));
5529 new Achievement = GetEventInt(event, "achievement");
5530
5531 if (IsClientBot(Player))
5532 {
5533 return;
5534 }
5535
5536 if (DEBUG)
5537 {
5538 LogMessage("Achievement earned: %i", Achievement);
5539 }
5540}
5541
5542// Saferoom door opens.
5543
5544public Action:event_DoorOpen(Handle:event, const String:name[], bool:dontBroadcast)
5545{
5546 if(MapTimingBlocked || MapTimingStartTime != 0.0 || !GetEventBool(event, "checkpoint") || !GetEventBool(event, "closed") || CurrentGamemodeID == GAMEMODE_SURVIVAL || StatsDisabled())
5547 {
5548 MapTimingBlocked = true;
5549 return Plugin_Continue;
5550 }
5551
5552 StartMapTiming();
5553
5554 return Plugin_Continue;
5555}
5556
5557public Action:event_StartArea(Handle:event, const String:name[], bool:dontBroadcast)
5558{
5559 if(MapTimingBlocked || MapTimingStartTime != 0.0 || CurrentGamemodeID == GAMEMODE_SURVIVAL || StatsDisabled())
5560 {
5561 MapTimingBlocked = true;
5562 return Plugin_Continue;
5563 }
5564
5565 StartMapTiming();
5566
5567 return Plugin_Continue;
5568}
5569
5570public Action:event_PlayerTeam(Handle:event, const String:name[], bool:dontBroadcast)
5571{
5572 if(MapTimingBlocked || MapTimingStartTime != 0.0 || GetEventBool(event, "isbot"))
5573 {
5574 return Plugin_Continue;
5575 }
5576
5577 new Player = GetClientOfUserId(GetEventInt(event, "userid"));
5578 //new NewTeam = GetEventInt(event, "team");
5579 //new OldTeam = GetEventInt(event, "oldteam");
5580
5581 if (Player <= 0)
5582 {
5583 return Plugin_Continue;
5584 }
5585
5586 decl String:PlayerID[MAX_LINE_WIDTH];
5587 GetClientRankAuthString(Player, PlayerID, sizeof(PlayerID));
5588
5589 RemoveFromTrie(MapTimingSurvivors, PlayerID);
5590 RemoveFromTrie(MapTimingInfected, PlayerID);
5591
5592 return Plugin_Continue;
5593}
5594
5595// AbilityUse.
5596
5597public Action:event_AbilityUse(Handle:event, const String:name[], bool:dontBroadcast)
5598{
5599 if (StatsDisabled())
5600 return;
5601
5602 new Player = GetClientOfUserId(GetEventInt(event, "userid"));
5603 GetClientAbsOrigin(Player, HunterPosition[Player]);
5604
5605 if (!IsClientBot(Player) && GetClientInfectedType(Player) == INF_ID_BOOMER)
5606 {
5607 decl String:query[1024];
5608 decl String:iID[MAX_LINE_WIDTH];
5609 GetClientRankAuthString(Player, iID, sizeof(iID));
5610 Format(query, sizeof(query), "UPDATE %splayers SET infected_boomer_vomits = infected_boomer_vomits + 1 WHERE steamid = '%s'", DbPrefix, iID);
5611 SendSQLUpdate(query);
5612 UpdateMapStat("infected_boomer_vomits", 1);
5613 BoomerVomitUpdated[Player] = true;
5614 }
5615}
5616
5617// Player got pounced.
5618
5619public Action:event_PlayerPounced(Handle:event, const String:name[], bool:dontBroadcast)
5620{
5621 if (StatsDisabled() || CampaignOver)
5622 return;
5623
5624 new Attacker = GetClientOfUserId(GetEventInt(event, "userid"));
5625 new Victim = GetClientOfUserId(GetEventInt(event, "victim"));
5626
5627 PlayerLunged[Victim][0] = 1;
5628 PlayerLunged[Victim][1] = Attacker;
5629
5630 if (IsClientBot(Attacker))
5631 return;
5632
5633 new Float:PouncePosition[3];
5634
5635 GetClientAbsOrigin(Attacker, PouncePosition);
5636 new PounceDistance = RoundToNearest(GetVectorDistance(HunterPosition[Attacker], PouncePosition));
5637
5638 if (PounceDistance < MinPounceDistance)
5639 return;
5640
5641 new Dmg = RoundToNearest((((PounceDistance - float(MinPounceDistance)) / float(MaxPounceDistance - MinPounceDistance)) * float(MaxPounceDamage)) + 1);
5642 new DmgCap = GetConVarInt(cvar_HunterDamageCap);
5643
5644 if (Dmg > DmgCap)
5645 Dmg = DmgCap;
5646
5647 new PerfectDmgLimit = GetConVarInt(cvar_HunterPerfectPounceDamage);
5648 new NiceDmgLimit = GetConVarInt(cvar_HunterNicePounceDamage);
5649
5650 UpdateHunterDamage(Attacker, Dmg);
5651
5652 if (Dmg < NiceDmgLimit && Dmg < PerfectDmgLimit)
5653 return;
5654
5655 new Mode = GetConVarInt(cvar_AnnounceMode);
5656
5657 decl String:AttackerName[MAX_LINE_WIDTH];
5658 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
5659 decl String:AttackerID[MAX_LINE_WIDTH];
5660 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
5661 decl String:VictimName[MAX_LINE_WIDTH];
5662 GetClientName(Victim, VictimName, sizeof(VictimName));
5663
5664 new Score = 0;
5665 decl String:Label[32];
5666 decl String:query[1024];
5667
5668 if (Dmg >= PerfectDmgLimit)
5669 {
5670 Score = GetConVarInt(cvar_HunterPerfectPounceSuccess);
5671 if (CurrentGamemodeID == GAMEMODE_VERSUS)
5672 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_pounce_perfect = award_pounce_perfect + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5673 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
5674 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_pounce_perfect = award_pounce_perfect + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5675 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
5676 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_pounce_perfect = award_pounce_perfect + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5677 else
5678 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_pounce_perfect = award_pounce_perfect + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5679 Format(Label, sizeof(Label), "Death From Above");
5680
5681 if (EnableSounds_Hunter_Perfect && GetConVarBool(cvar_SoundsEnabled))
5682 EmitSoundToAll(StatsSound_Hunter_Perfect);
5683 }
5684 else
5685 {
5686 Score = GetConVarInt(cvar_HunterNicePounceSuccess);
5687 if (CurrentGamemodeID == GAMEMODE_VERSUS)
5688 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_pounce_nice = award_pounce_nice + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5689 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
5690 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_pounce_nice = award_pounce_nice + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5691 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
5692 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_pounce_nice = award_pounce_nice + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5693 else
5694 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_pounce_nice = award_pounce_nice + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
5695 Format(Label, sizeof(Label), "Pain From Above");
5696 }
5697
5698 SendSQLUpdate(query);
5699 UpdateMapStat("points_infected", Score);
5700
5701 if (Mode == 1 || Mode == 2)
5702 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for landing a \x05%s \x01Pounce on \x05%s\x01!", Score, Label, VictimName);
5703 else if (Mode == 3)
5704 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for landing a \x05%s \x01Pounce on \x05%s\x01!", AttackerName, Score, Label, VictimName);
5705}
5706
5707// Revive friendly code.
5708
5709public Action:event_RevivePlayer(Handle:event, const String:name[], bool:dontBroadcast)
5710{
5711 if (StatsDisabled())
5712 return;
5713
5714 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !SurvivalStarted)
5715 return;
5716
5717 if (GetEventBool(event, "ledge_hang"))
5718 return;
5719
5720 new Savior = GetClientOfUserId(GetEventInt(event, "userid"));
5721 new Victim = GetClientOfUserId(GetEventInt(event, "subject"));
5722 new Mode = GetConVarInt(cvar_AnnounceMode);
5723
5724 if (IsClientBot(Savior) || IsClientBot(Victim))
5725 return;
5726
5727 decl String:SaviorName[MAX_LINE_WIDTH];
5728 GetClientName(Savior, SaviorName, sizeof(SaviorName));
5729 decl String:SaviorID[MAX_LINE_WIDTH];
5730 GetClientRankAuthString(Savior, SaviorID, sizeof(SaviorID));
5731
5732 decl String:VictimName[MAX_LINE_WIDTH];
5733 GetClientName(Victim, VictimName, sizeof(VictimName));
5734 decl String:VictimID[MAX_LINE_WIDTH];
5735 GetClientRankAuthString(Victim, VictimID, sizeof(VictimID));
5736
5737 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Revive), 2, 3, TEAM_SURVIVORS);
5738
5739 decl String:UpdatePoints[32];
5740
5741 switch (CurrentGamemodeID)
5742 {
5743 case GAMEMODE_VERSUS:
5744 {
5745 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
5746 }
5747 case GAMEMODE_REALISM:
5748 {
5749 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
5750 }
5751 case GAMEMODE_SURVIVAL:
5752 {
5753 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
5754 }
5755 case GAMEMODE_SCAVENGE:
5756 {
5757 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
5758 }
5759 case GAMEMODE_REALISMVERSUS:
5760 {
5761 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
5762 }
5763 case GAMEMODE_OTHERMUTATIONS:
5764 {
5765 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
5766 }
5767 default:
5768 {
5769 Format(UpdatePoints, sizeof(UpdatePoints), "points");
5770 }
5771 }
5772
5773 decl String:query[1024];
5774 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_revive = award_revive + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, SaviorID);
5775 SendSQLUpdate(query);
5776
5777 UpdateMapStat("points", Score);
5778 AddScore(Savior, Score);
5779
5780 if (Score > 0)
5781 {
5782 if (Mode == 1 || Mode == 2)
5783 StatsPrintToChat(Savior, "You have earned \x04%i \x01points for Reviving \x05%s\x01!", Score, VictimName);
5784 else if (Mode == 3)
5785 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Reviving \x05%s\x01!", SaviorName, Score, VictimName);
5786 }
5787}
5788
5789/*
5790L4D1:
5791
579256 - Helping Hand
579357 - Field Medic
579458 - Pharm-Assist
579559 - My Bodyguard
579660 - Dead Stop
579761 - Crownd
579862 - Untouchables
579963 -
580064 - Drag and Drop
580165 - Blind Luck
580266 - Akimbo Assassin
580367 -
580468 - Hero Closet
580569 - Hunter Punter
580670 - Tongue Twister
580771 - No Smoking Section
580872 -
580973 - 101 Cremations
581074 - Do Not Disturb
581175 - Man Vs Tank
581276 - TankBusters
581377 - Safety First
581478 - No-one Left Behind
581579 -
581680 -
581781 - Unbreakable
581882 - Witch Hunter
581983 - Red Mist
582084 - Pyrotechnician
582185 - Zombie Genocidest
582286 - Dead Giveaway
582387 - Stand Tall
582488 -
582589 -
582690 - Zombicidal Maniac
582791 - What are you trying to Prove?
582892 -
582993 - Nothing Special
583094 - Burn the Witch
583195 - Towering Inferno
583296 - Spinal Tap
583397 - Stomach Upset
583498 - Brain Salad
583599 - Jump Shot
5836100 - Mercy Killer
5837101 - Back 2 Help
5838102 - Toll Collector
5839103 - Dead Baron
5840104 - Grim Reaper
5841105 - Ground Cover
5842106 - Clean Kill
5843107 - Big Drag
5844108 - Chain Smoker
5845109 - Barf Bagged
5846110 - Double Jump
5847111 - All 4 Dead
5848112 - Dead Wreckening
5849113 - Lamb 2 Slaughter
5850114 - Outbreak
5851*/
5852
5853// Miscellaneous events and awards. See specific award for info.
5854
5855public Action:event_Award_L4D1(Handle:event, const String:name[], bool:dontBroadcast)
5856{
5857 if (StatsDisabled())
5858 {
5859 return;
5860 }
5861
5862 new PlayerID = GetEventInt(event, "userid");
5863
5864 if (!PlayerID)
5865 {
5866 return;
5867 }
5868
5869 new User = GetClientOfUserId(PlayerID);
5870
5871 if (IsClientBot(User))
5872 {
5873 return;
5874 }
5875
5876 new SubjectID = GetEventInt(event, "subjectentid");
5877 new Mode = GetConVarInt(cvar_AnnounceMode);
5878 decl String:UserName[MAX_LINE_WIDTH];
5879 GetClientName(User, UserName, sizeof(UserName));
5880
5881 new Recipient;
5882 decl String:RecipientName[MAX_LINE_WIDTH];
5883
5884 new Score = 0;
5885 new String:AwardSQL[128];
5886 new AwardID = GetEventInt(event, "award");
5887
5888 if (AwardID == 67) // Protect friendly
5889 {
5890 if (!SubjectID)
5891 {
5892 return;
5893 }
5894
5895 ProtectedFriendlyCounter[User]++;
5896
5897 if (TimerProtectedFriendly[User] != INVALID_HANDLE)
5898 {
5899 CloseHandle(TimerProtectedFriendly[User]);
5900 TimerProtectedFriendly[User] = INVALID_HANDLE;
5901 }
5902
5903 TimerProtectedFriendly[User] = CreateTimer(3.0, timer_ProtectedFriendly, User);
5904
5905 return;
5906 }
5907 else if (AwardID == 79) // Respawn friendly
5908 {
5909 if (!SubjectID)
5910 {
5911 return;
5912 }
5913
5914 Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
5915
5916 if (IsClientBot(Recipient))
5917 return;
5918
5919 Score = ModifyScoreDifficulty(GetConVarInt(cvar_Rescue), 2, 3, TEAM_SURVIVORS);
5920 GetClientName(Recipient, RecipientName, sizeof(RecipientName));
5921 Format(AwardSQL, sizeof(AwardSQL), ", award_rescue = award_rescue + 1");
5922 UpdateMapStat("points", Score);
5923 AddScore(User, Score);
5924
5925 if (Score > 0)
5926 {
5927 if (Mode == 1 || Mode == 2)
5928 {
5929 StatsPrintToChat(User, "You have earned \x04%i \x01points for Rescuing \x05%s\x01!", Score, RecipientName);
5930 }
5931 else if (Mode == 3)
5932 {
5933 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Rescuing \x05%s\x01!", UserName, Score, RecipientName);
5934 }
5935 }
5936 }
5937 else if (AwardID == 80) // Kill Tank with no deaths
5938 {
5939 Score = ModifyScoreDifficulty(0, 1, 1, TEAM_SURVIVORS);
5940 Format(AwardSQL, sizeof(AwardSQL), ", award_tankkillnodeaths = award_tankkillnodeaths + 1");
5941 }
5942// Moved to event_PlayerDeath
5943// else if (AwardID == 83 && !CampaignOver) // Team kill
5944// {
5945// if (!SubjectID)
5946// return;
5947//
5948// Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
5949//
5950// Format(AwardSQL, sizeof(AwardSQL), ", award_teamkill = award_teamkill + 1");
5951// Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_FKill), 2, 4, TEAM_SURVIVORS);
5952// Score = Score * -1;
5953//
5954// if (Mode == 1 || Mode == 2)
5955// StatsPrintToChat(User, "You have \x03LOST \x04%i \x01points for \x03Team Killing!", Score);
5956// else if (Mode == 3)
5957// StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for \x03Team Killing!", UserName, Score);
5958// }
5959 else if (AwardID == 85) // Left friendly for dead
5960 {
5961 Format(AwardSQL, sizeof(AwardSQL), ", award_left4dead = award_left4dead + 1");
5962 Score = ModifyScoreDifficulty(0, 1, 1, TEAM_SURVIVORS);
5963 }
5964 else if (AwardID == 94) // Let infected in safe room
5965 {
5966 Format(AwardSQL, sizeof(AwardSQL), ", award_letinsafehouse = award_letinsafehouse + 1");
5967
5968 Score = 0;
5969 if (GetConVarBool(cvar_EnableNegativeScore))
5970 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_InSafeRoom), 2, 4, TEAM_SURVIVORS);
5971 else
5972 Mode = 0;
5973
5974 if (Mode == 1 || Mode == 2)
5975 StatsPrintToChat(User, "You have \x03LOST \x04%i \x01points for letting \x03Infected In The Safe Room!", Score);
5976 else if (Mode == 3)
5977 StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for letting \x03Infected In The Safe Room!", UserName, Score);
5978
5979 Score = Score * -1;
5980 }
5981 else if (AwardID == 98) // Round restart
5982 {
5983 UpdateMapStat("restarts", 1);
5984
5985 if (!GetConVarBool(cvar_EnableNegativeScore))
5986 {
5987 return;
5988 }
5989
5990 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Restart), 2, 3, TEAM_SURVIVORS);
5991 Score = 400 - Score;
5992
5993 if (Mode)
5994 {
5995 StatsPrintToChat(User, "\x03ALL SURVIVORS \x01have \x03LOST \x04%i \x01points for \x03All Survivors Dying!", Score);
5996 }
5997
5998 Score = Score * -1;
5999 }
6000 else
6001 {
6002// if (DEBUG)
6003// LogError("event_Award => %i", AwardID);
6004//StatsPrintToChat(User, "[DEBUG] event_Award => %i", AwardID);
6005 return;
6006 }
6007
6008 decl String:UpdatePoints[32];
6009 decl String:UserID[MAX_LINE_WIDTH];
6010 GetClientRankAuthString(User, UserID, sizeof(UserID));
6011
6012 switch (CurrentGamemodeID)
6013 {
6014 case GAMEMODE_VERSUS:
6015 {
6016 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
6017 }
6018 case GAMEMODE_REALISM:
6019 {
6020 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
6021 }
6022 case GAMEMODE_SURVIVAL:
6023 {
6024 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
6025 }
6026 case GAMEMODE_SCAVENGE:
6027 {
6028 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
6029 }
6030 case GAMEMODE_REALISMVERSUS:
6031 {
6032 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
6033 }
6034 case GAMEMODE_OTHERMUTATIONS:
6035 {
6036 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
6037 }
6038 default:
6039 {
6040 Format(UpdatePoints, sizeof(UpdatePoints), "points");
6041 }
6042 }
6043
6044 decl String:query[1024];
6045 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i%s WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, AwardSQL, UserID);
6046 SendSQLUpdate(query);
6047}
6048
6049/*
6050L4D2:
60510 - End of Campaign (Not 100% Sure)
60527 - End of Level (Not 100% Sure)
60538 - End of Level (Not 100% Sure)
605417 - Kill Tank
605522 - Random Director Mob
605623 - End of Level (Not 100% Sure)
605740 - End of Campaign (Not 100% Sure)
605867 - Protect Friendly
605968 - Give Pain Pills
606069 - Give Adrenaline
606170 - Give Heatlh (Heal using Med Pack)
606271 - End of Level (Not 100% Sure)
606372 - End of Campaign (Not 100% Sure)
606475 - Save Friendly from Ledge Grasp
606576 - Save Friendly from Special Infected
606680 - Hero Closet Rescue Survivor
606781 - Kill Tank with no deaths
606884 - Team Kill
606985 - Incap Friendly
607086 - Left Friendly for Dead
607187 - Friendly Fire
607289 - Incap Friendly
607395 - Let infected in safe room
607499 - Round Restart (All Dead)
6075*/
6076
6077// Miscellaneous events and awards. See specific award for info.
6078
6079public Action:event_Award_L4D2(Handle:event, const String:name[], bool:dontBroadcast)
6080{
6081 if (StatsDisabled())
6082 {
6083 return;
6084 }
6085
6086 new PlayerID = GetEventInt(event, "userid");
6087
6088 if (!PlayerID)
6089 {
6090 return;
6091 }
6092
6093 new User = GetClientOfUserId(PlayerID);
6094
6095 if (IsClientBot(User))
6096 {
6097 return;
6098 }
6099
6100 new SubjectID = GetEventInt(event, "subjectentid");
6101 new Mode = GetConVarInt(cvar_AnnounceMode);
6102 decl String:UserName[MAX_LINE_WIDTH];
6103 GetClientName(User, UserName, sizeof(UserName));
6104
6105 new Recipient;
6106 decl String:RecipientName[MAX_LINE_WIDTH];
6107
6108 new Score = 0;
6109 new String:AwardSQL[128];
6110 new AwardID = GetEventInt(event, "award");
6111
6112 //StatsPrintToChat(User, "[TEST] Your actions gave you award (ID = %i)", AwardID);
6113
6114 if (AwardID == 67) // Protect friendly
6115 {
6116 if (!SubjectID)
6117 {
6118 return;
6119 }
6120
6121 ProtectedFriendlyCounter[User]++;
6122
6123 if (TimerProtectedFriendly[User] != INVALID_HANDLE)
6124 {
6125 CloseHandle(TimerProtectedFriendly[User]);
6126 TimerProtectedFriendly[User] = INVALID_HANDLE;
6127 }
6128
6129 TimerProtectedFriendly[User] = CreateTimer(3.0, timer_ProtectedFriendly, User);
6130
6131 return;
6132 }
6133
6134 if (AwardID == 68) // Pills given
6135 {
6136 if (!SubjectID)
6137 {
6138 return;
6139 }
6140
6141 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !GetConVarBool(cvar_EnableSvMedicPoints))
6142 {
6143 return;
6144 }
6145
6146 Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
6147
6148 GivePills(User, Recipient);
6149
6150 return;
6151 }
6152
6153 if (AwardID == 69) // Adrenaline given
6154 {
6155 if (!SubjectID)
6156 {
6157 return;
6158 }
6159
6160 if (CurrentGamemodeID == GAMEMODE_SURVIVAL && !GetConVarBool(cvar_EnableSvMedicPoints))
6161 {
6162 return;
6163 }
6164
6165 Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
6166
6167 GiveAdrenaline(User, Recipient);
6168
6169 return;
6170 }
6171
6172 if (AwardID == 85) // Incap friendly
6173 {
6174 if (!SubjectID)
6175 {
6176 return;
6177 }
6178
6179 Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
6180
6181 PlayerIncap(User, Recipient);
6182
6183 return;
6184 }
6185
6186 if (AwardID == 80) // Respawn friendly
6187 {
6188 if (!SubjectID)
6189 {
6190 return;
6191 }
6192
6193 Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
6194
6195 if (IsClientBot(Recipient))
6196 {
6197 return;
6198 }
6199
6200 Score = ModifyScoreDifficulty(GetConVarInt(cvar_Rescue), 2, 3, TEAM_SURVIVORS);
6201 GetClientName(Recipient, RecipientName, sizeof(RecipientName));
6202 Format(AwardSQL, sizeof(AwardSQL), ", award_rescue = award_rescue + 1");
6203 UpdateMapStat("points", Score);
6204 AddScore(User, Score);
6205
6206 if (Score > 0)
6207 {
6208 if (Mode == 1 || Mode == 2)
6209 {
6210 StatsPrintToChat(User, "You have earned \x04%i \x01points for Rescuing \x05%s\x01!", Score, RecipientName);
6211 }
6212 else if (Mode == 3)
6213 {
6214 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Rescuing \x05%s\x01!", UserName, Score, RecipientName);
6215 }
6216 }
6217 }
6218 else if (AwardID == 81) // Kill Tank with no deaths
6219 {
6220 Score = ModifyScoreDifficulty(0, 1, 1, TEAM_SURVIVORS);
6221 Format(AwardSQL, sizeof(AwardSQL), ", award_tankkillnodeaths = award_tankkillnodeaths + 1");
6222 }
6223// Moved to event_PlayerDeath
6224// else if (AwardID == 84 && !CampaignOver) // Team kill
6225// {
6226// if (!SubjectID)
6227// return;
6228//
6229// Recipient = GetClientOfUserId(GetClientUserId(SubjectID));
6230//
6231// Format(AwardSQL, sizeof(AwardSQL), ", award_teamkill = award_teamkill + 1");
6232// Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_FKill), 2, 4, TEAM_SURVIVORS);
6233// Score = Score * -1;
6234//
6235// if (Mode == 1 || Mode == 2)
6236// StatsPrintToChat(User, "You have \x03LOST \x04%i \x01points for \x03Team Killing!", Score);
6237// else if (Mode == 3)
6238// StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for \x03Team Killing!", UserName, Score);
6239// }
6240 else if (AwardID == 86) // Left friendly for dead
6241 {
6242 Format(AwardSQL, sizeof(AwardSQL), ", award_left4dead = award_left4dead + 1");
6243 Score = ModifyScoreDifficulty(0, 1, 1, TEAM_SURVIVORS);
6244 }
6245 else if (AwardID == 95) // Let infected in safe room
6246 {
6247 Format(AwardSQL, sizeof(AwardSQL), ", award_letinsafehouse = award_letinsafehouse + 1");
6248
6249 Score = 0;
6250 if (GetConVarBool(cvar_EnableNegativeScore))
6251 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_InSafeRoom), 2, 4, TEAM_SURVIVORS);
6252 else
6253 Mode = 0;
6254
6255 if (Mode == 1 || Mode == 2)
6256 StatsPrintToChat(User, "You have \x03LOST \x04%i \x01points for letting \x03Infected In The Safe Room!", Score);
6257 else if (Mode == 3)
6258 StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for letting \x03Infected In The Safe Room!", UserName, Score);
6259
6260 Score = Score * -1;
6261 }
6262 else if (AwardID == 99) // Round restart
6263 {
6264 UpdateMapStat("restarts", 1);
6265
6266 if (!GetConVarBool(cvar_EnableNegativeScore))
6267 return;
6268
6269 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Restart), 2, 3, TEAM_SURVIVORS);
6270 Score = 400 - Score;
6271
6272 if (Mode)
6273 StatsPrintToChat(User, "\x03ALL SURVIVORS \x01have \x03LOST \x04%i \x01points for \x03All Survivors Dying!", Score);
6274
6275 Score = Score * -1;
6276 }
6277 else
6278 {
6279//StatsPrintToChat(User, "[DEBUG] event_Award => %i", AwardID);
6280 return;
6281 }
6282
6283 decl String:UpdatePoints[32];
6284 decl String:UserID[MAX_LINE_WIDTH];
6285 GetClientRankAuthString(User, UserID, sizeof(UserID));
6286
6287 switch (CurrentGamemodeID)
6288 {
6289 case GAMEMODE_VERSUS:
6290 {
6291 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
6292 }
6293 case GAMEMODE_REALISM:
6294 {
6295 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
6296 }
6297 case GAMEMODE_SURVIVAL:
6298 {
6299 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
6300 }
6301 case GAMEMODE_SCAVENGE:
6302 {
6303 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
6304 }
6305 case GAMEMODE_REALISMVERSUS:
6306 {
6307 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
6308 }
6309 case GAMEMODE_OTHERMUTATIONS:
6310 {
6311 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
6312 }
6313 default:
6314 {
6315 Format(UpdatePoints, sizeof(UpdatePoints), "points");
6316 }
6317 }
6318
6319 decl String:query[1024];
6320 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i%s WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, AwardSQL, UserID);
6321 SendSQLUpdate(query);
6322}
6323
6324// Scavenge halftime code.
6325
6326public Action:event_ScavengeHalftime(Handle:event, const String:name[], bool:dontBroadcast)
6327{
6328 if (StatsDisabled() || CampaignOver)
6329 return;
6330
6331 CampaignOver = true;
6332
6333 new maxplayers = GetMaxClients();
6334
6335 for (new i = 1; i <= maxplayers; i++)
6336 {
6337 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
6338 {
6339 switch (GetClientTeam(i))
6340 {
6341 case TEAM_SURVIVORS:
6342 InterstitialPlayerUpdate(i);
6343 case TEAM_INFECTED:
6344 DoInfectedFinalChecks(i);
6345 }
6346 }
6347 }
6348}
6349
6350// Survival started code.
6351
6352public Action:event_SurvivalStart(Handle:event, const String:name[], bool:dontBroadcast)
6353{
6354 if (StatsDisabled())
6355 return;
6356
6357 SurvivalStart();
6358}
6359
6360public SurvivalStart()
6361{
6362 UpdateMapStat("restarts", 1);
6363 SurvivalStarted = true;
6364 MapTimingStartTime = 0.0;
6365 MapTimingBlocked = false;
6366 StartMapTiming();
6367}
6368
6369// Car alarm triggered code.
6370
6371public Action:event_CarAlarm(Handle:event, const String:name[], bool:dontBroadcast)
6372{
6373 if (StatsDisabled() || CurrentGamemodeID == GAMEMODE_SURVIVAL || !GetConVarBool(cvar_EnableNegativeScore))
6374 return;
6375
6376 new Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_CarAlarm), 2, 3, TEAM_SURVIVORS);
6377 UpdateMapStat("caralarm", 1);
6378
6379 if (Score <= 0)
6380 return;
6381
6382 decl String:UpdatePoints[32];
6383 decl String:query[1024];
6384
6385 switch (CurrentGamemodeID)
6386 {
6387 case GAMEMODE_VERSUS:
6388 {
6389 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
6390 }
6391 case GAMEMODE_REALISM:
6392 {
6393 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
6394 }
6395 case GAMEMODE_SURVIVAL:
6396 {
6397 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
6398 }
6399 case GAMEMODE_SCAVENGE:
6400 {
6401 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
6402 }
6403 case GAMEMODE_REALISMVERSUS:
6404 {
6405 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
6406 }
6407 case GAMEMODE_OTHERMUTATIONS:
6408 {
6409 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
6410 }
6411 default:
6412 {
6413 Format(UpdatePoints, sizeof(UpdatePoints), "points");
6414 }
6415 }
6416
6417 new maxplayers = GetMaxClients();
6418 new Mode = GetConVarInt(cvar_AnnounceMode);
6419 decl String:iID[MAX_LINE_WIDTH];
6420
6421 for (new i = 1; i <= maxplayers; i++)
6422 {
6423 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i) && GetClientTeam(i) == TEAM_SURVIVORS)
6424 {
6425 GetClientRankAuthString(i, iID, sizeof(iID));
6426 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
6427 SendSQLUpdate(query);
6428
6429 if (Mode)
6430 StatsPrintToChat(i, "\x03ALL SURVIVORS \x01have \x03LOST \x04%i \x01points for \x03Triggering the Car Alarm\x01!", Score);
6431 }
6432 }
6433}
6434
6435// Reset Witch existence in the world when a new one is created.
6436
6437public Action:event_WitchSpawn(Handle:event, const String:name[], bool:dontBroadcast)
6438{
6439 if (StatsDisabled())
6440 return;
6441
6442 WitchExists = true;
6443}
6444
6445// Witch was crowned!
6446
6447public Action:event_WitchCrowned(Handle:event, const String:name[], bool:dontBroadcast)
6448{
6449 if (StatsDisabled() || CurrentGamemodeID == GAMEMODE_SURVIVAL)
6450 return;
6451
6452 new Killer = GetClientOfUserId(GetEventInt(event, "userid"));
6453 new bool:Crowned = GetEventBool(event, "oneshot");
6454
6455 if (Crowned && Killer > 0 && !IsClientBot(Killer) && IsClientConnected(Killer) && IsClientInGame(Killer))
6456 {
6457 decl String:SteamID[MAX_LINE_WIDTH];
6458 GetClientRankAuthString(Killer, SteamID, sizeof(SteamID));
6459
6460 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_WitchCrowned), 2, 3, TEAM_SURVIVORS);
6461 decl String:UpdatePoints[32];
6462
6463 switch (CurrentGamemodeID)
6464 {
6465 case GAMEMODE_VERSUS:
6466 {
6467 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
6468 }
6469 case GAMEMODE_REALISM:
6470 {
6471 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
6472 }
6473 case GAMEMODE_SURVIVAL:
6474 {
6475 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
6476 }
6477 case GAMEMODE_SCAVENGE:
6478 {
6479 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
6480 }
6481 case GAMEMODE_REALISMVERSUS:
6482 {
6483 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
6484 }
6485 case GAMEMODE_OTHERMUTATIONS:
6486 {
6487 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
6488 }
6489 default:
6490 {
6491 Format(UpdatePoints, sizeof(UpdatePoints), "points");
6492 }
6493 }
6494
6495 decl String:query[1024];
6496 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, award_witchcrowned = award_witchcrowned + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, SteamID);
6497 SendSQLUpdate(query);
6498
6499 if (Score > 0 && GetConVarInt(cvar_AnnounceMode))
6500 {
6501 decl String:Name[MAX_LINE_WIDTH];
6502 GetClientName(Killer, Name, sizeof(Name));
6503
6504 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x05%s \x01has earned \x04%i \x01points for \x04Crowning the Witch\x01!", Name, Score);
6505 }
6506 }
6507}
6508
6509// Witch was disturbed!
6510
6511public Action:event_WitchDisturb(Handle:event, const String:name[], bool:dontBroadcast)
6512{
6513 if (StatsDisabled())
6514 return;
6515
6516 if (WitchExists)
6517 {
6518 WitchDisturb = true;
6519
6520 if (!GetEventInt(event, "userid"))
6521 return;
6522
6523 new User = GetClientOfUserId(GetEventInt(event, "userid"));
6524
6525 if (IsClientBot(User))
6526 return;
6527
6528 decl String:UserID[MAX_LINE_WIDTH];
6529 GetClientRankAuthString(User, UserID, sizeof(UserID));
6530
6531 decl String:query[1024];
6532 Format(query, sizeof(query), "UPDATE %splayers SET award_witchdisturb = award_witchdisturb + 1 WHERE steamid = '%s'", DbPrefix, UserID);
6533 SendSQLUpdate(query);
6534 }
6535}
6536
6537// DEBUG
6538//public Action:cmd_StatsTest(client, args)
6539//{
6540// new String:CurrentMode[16];
6541// GetConVarString(cvar_Gamemode, CurrentMode, sizeof(CurrentMode));
6542// PrintToConsole(0, "Gamemode: %s", CurrentMode);
6543// UpdateMapStat("playtime", 10);
6544// PrintToConsole(0, "Added 10 seconds to maps table current map.");
6545// new Float:ReductionFactor = GetMedkitPointReductionFactor();
6546//
6547// StatsPrintToChat(client, "\x03ALL SURVIVORS \x01now earns only \x04%i percent \x01of their normal points after using their \x05%i%s Medkit\x01!", RoundToNearest(ReductionFactor * 100), MedkitsUsedCounter, (MedkitsUsedCounter == 1 ? "st" : (MedkitsUsedCounter == 2 ? "nd" : (MedkitsUsedCounter == 3 ? "rd" : "th"))), GetClientTeam(client));
6548//}
6549
6550/*
6551-----------------------------------------------------------------------------
6552Chat/command handling and panels for Rank and Top10
6553-----------------------------------------------------------------------------
6554*/
6555
6556public Action:HandleCommands(client, const String:Text[])
6557{
6558 if (strcmp(Text, "rankmenu", false) == 0)
6559 {
6560 cmd_ShowRankMenu(client, 0);
6561 if (GetConVarBool(cvar_SilenceChat))
6562 return Plugin_Handled;
6563 }
6564
6565 else if (strcmp(Text, "rank", false) == 0)
6566 {
6567 cmd_ShowRank(client, 0);
6568 if (GetConVarBool(cvar_SilenceChat))
6569 return Plugin_Handled;
6570 }
6571
6572 else if (strcmp(Text, "showrank", false) == 0)
6573 {
6574 cmd_ShowRanks(client, 0);
6575 if (GetConVarBool(cvar_SilenceChat))
6576 return Plugin_Handled;
6577 }
6578
6579 else if (strcmp(Text, "showppm", false) == 0)
6580 {
6581 cmd_ShowPPMs(client, 0);
6582 if (GetConVarBool(cvar_SilenceChat))
6583 return Plugin_Handled;
6584 }
6585
6586 else if (strcmp(Text, "top10", false) == 0)
6587 {
6588 cmd_ShowTop10(client, 0);
6589 if (GetConVarBool(cvar_SilenceChat))
6590 return Plugin_Handled;
6591 }
6592
6593 else if (strcmp(Text, "top10ppm", false) == 0)
6594 {
6595 cmd_ShowTop10PPM(client, 0);
6596 if (GetConVarBool(cvar_SilenceChat))
6597 return Plugin_Handled;
6598 }
6599
6600 else if (strcmp(Text, "nextrank", false) == 0)
6601 {
6602 cmd_ShowNextRank(client, 0);
6603 if (GetConVarBool(cvar_SilenceChat))
6604 return Plugin_Handled;
6605 }
6606
6607 else if (strcmp(Text, "showtimer", false) == 0)
6608 {
6609 cmd_ShowTimedMapsTimer(client, 0);
6610 if (GetConVarBool(cvar_SilenceChat))
6611 return Plugin_Handled;
6612 }
6613
6614 else if (strcmp(Text, "timedmaps", false) == 0)
6615 {
6616 cmd_TimedMaps(client, 0);
6617 if (GetConVarBool(cvar_SilenceChat))
6618 return Plugin_Handled;
6619 }
6620
6621 else if (strcmp(Text, "showmaptime", false) == 0)
6622 {
6623 cmd_ShowMapTimes(client, 0);
6624 if (GetConVarBool(cvar_SilenceChat))
6625 return Plugin_Handled;
6626 }
6627
6628 else if (strcmp(Text, "maptimes", false) == 0)
6629 {
6630 cmd_MapTimes(client, 0);
6631 if (GetConVarBool(cvar_SilenceChat))
6632 return Plugin_Handled;
6633 }
6634
6635 else if (strcmp(Text, "rankvote", false) == 0)
6636 {
6637 cmd_RankVote(client, 0);
6638 if (GetConVarBool(cvar_SilenceChat))
6639 return Plugin_Handled;
6640 }
6641
6642 else if (strcmp(Text, "rankmutetoggle", false) == 0)
6643 {
6644 cmd_ToggleClientRankMute(client, 0);
6645 if (GetConVarBool(cvar_SilenceChat))
6646 return Plugin_Handled;
6647 }
6648
6649 else if (strcmp(Text, "showmotd", false) == 0)
6650 {
6651 cmd_ShowMotd(client, 0);
6652 if (GetConVarBool(cvar_SilenceChat))
6653 return Plugin_Handled;
6654 }
6655
6656 return Plugin_Continue;
6657}
6658
6659// Parse chat for RANK and TOP10 triggers.
6660public Action:cmd_Say(client, args)
6661{
6662 decl String:Text[192];
6663 //new String:Command[64];
6664 new Start = 0;
6665
6666 GetCmdArgString(Text, sizeof(Text));
6667
6668 new TextLen = strlen(Text);
6669
6670 // This apparently happens sometimes?
6671 if (TextLen <= 0)
6672 {
6673 return Plugin_Continue;
6674 }
6675
6676 if (Text[TextLen-1] == '"')
6677 {
6678 Text[TextLen-1] = '\0';
6679 Start = 1;
6680 }
6681
6682 // Command is never set? This will always result to false.
6683 //if (strcmp(Command, "say2", false) == 0)
6684 // Start += 4;
6685
6686 return HandleCommands(client, Text[Start]);
6687}
6688
6689// Show current Timed Maps timer.
6690public Action:cmd_ShowTimedMapsTimer(client, args)
6691{
6692 if (client != 0 && !IsClientConnected(client) && !IsClientInGame(client))
6693 return Plugin_Handled;
6694
6695 if (client != 0 && IsClientBot(client))
6696 return Plugin_Handled;
6697
6698 if (MapTimingStartTime <= 0.0)
6699 {
6700 if (client == 0)
6701 {
6702 PrintToConsole(0, "[RANK] Map timer has not started");
6703 }
6704 else
6705 {
6706 StatsPrintToChatPreFormatted(client, "Map timer has not started");
6707 }
6708
6709 return Plugin_Handled;
6710 }
6711
6712 new Float:CurrentMapTimer = GetEngineTime() - MapTimingStartTime;
6713 decl String:TimeLabel[32];
6714
6715 SetTimeLabel(CurrentMapTimer, TimeLabel, sizeof(TimeLabel));
6716
6717 if (client == 0)
6718 PrintToConsole(0, "[RANK] Current map timer: %s", TimeLabel);
6719 else
6720 StatsPrintToChat(client, "Current map timer: \x04%s", TimeLabel);
6721
6722 return Plugin_Handled;
6723}
6724
6725// Begin generating the NEXTRANK display panel.
6726public Action:cmd_ShowNextRank(client, args)
6727{
6728 if (!IsClientConnected(client) && !IsClientInGame(client))
6729 return Plugin_Handled;
6730
6731 if (IsClientBot(client))
6732 return Plugin_Handled;
6733
6734 decl String:SteamID[MAX_LINE_WIDTH];
6735 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
6736
6737 QueryClientStatsSteamID(client, SteamID, CM_NEXTRANK);
6738
6739 return Plugin_Handled;
6740}
6741
6742// Clear database.
6743//public Action:cmd_RankAdmin(client, args)
6744//{
6745// if (!client)
6746// return Plugin_Handled;
6747//
6748// new Handle:RankAdminPanel = CreatePanel();
6749//
6750// SetPanelTitle(RankAdminPanel, "Rank Admin:");
6751//
6752// DrawPanelItem(RankAdminPanel, "Clear...");
6753// DrawPanelItem(RankAdminPanel, "Clear Players");
6754// DrawPanelItem(RankAdminPanel, "Clear Maps");
6755// DrawPanelItem(RankAdminPanel, "Clear All");
6756//
6757// SendPanelToClient(RankAdminPanel, client, RankAdminPanelHandler, 30);
6758// CloseHandle(RankAdminPanel);
6759//
6760// return Plugin_Handled;
6761//}
6762
6763DisplayYesNoPanel(client, const String:title[], MenuHandler:handler, delay=30)
6764{
6765 if (!client)
6766 return;
6767
6768 new Handle:panel = CreatePanel();
6769
6770 SetPanelTitle(panel, title);
6771
6772 DrawPanelItem(panel, "Yes");
6773 DrawPanelItem(panel, "No");
6774
6775 SendPanelToClient(panel, client, handler, delay);
6776 CloseHandle(panel);
6777}
6778
6779public bool:IsTeamGamemode()
6780{
6781 return IsGamemode("versus") ||
6782 IsGamemode("realismversus") ||
6783 IsGamemode("scavenge") ||
6784 IsGamemode("mutation11") || // Healthpackalypse!
6785 IsGamemode("mutation12") || // Realism Versus
6786 IsGamemode("mutation13") || // Follow the Liter
6787 IsGamemode("mutation15") || // Versus Survival
6788 IsGamemode("mutation18") || // Bleed Out Versus
6789 IsGamemode("mutation19") || // Taaannnkk!
6790 IsGamemode("community3") || // Riding My Survivor
6791 IsGamemode("community6"); // Confogl
6792}
6793
6794// Run Team Shuffle.
6795public Action:cmd_ShuffleTeams(client, args)
6796{
6797 if (!IsTeamGamemode())
6798 {
6799 PrintToConsole(client, "[RANK] Team shuffle is not enabled in this gamemode!");
6800 return Plugin_Handled;
6801 }
6802
6803 if (RankVoteTimer != INVALID_HANDLE)
6804 {
6805 CloseHandle(RankVoteTimer);
6806 RankVoteTimer = INVALID_HANDLE;
6807
6808 StatsPrintToChatAllPreFormatted("Team shuffle executed by administrator.");
6809 }
6810
6811 PrintToConsole(client, "[RANK] Executing team shuffle...");
6812 CreateTimer(1.0, timer_ShuffleTeams);
6813
6814 return Plugin_Handled;
6815}
6816
6817// Set Message Of The Day.
6818public Action:cmd_SetMotd(client, args)
6819{
6820 decl String:arg[1024];
6821
6822 GetCmdArgString(arg, sizeof(arg));
6823
6824 UpdateServerSettings(client, "motdmessage", arg, MOTD_TITLE);
6825
6826 return Plugin_Handled;
6827}
6828
6829// Clear database.
6830public Action:cmd_ClearRank(client, args)
6831{
6832 if (client == 0)
6833 {
6834 PrintToConsole(client, "[RANK] Clear Stats: Database clearing from server console currently disabled because of a bug in it! Run the command from in-game console or from Admin Panel.");
6835
6836 return Plugin_Handled;
6837 }
6838
6839 if (ClearDatabaseTimer != INVALID_HANDLE)
6840 {
6841 CloseHandle(ClearDatabaseTimer);
6842 }
6843
6844 ClearDatabaseTimer = INVALID_HANDLE;
6845
6846 if (ClearDatabaseCaller == client)
6847 {
6848 PrintToConsole(client, "[RANK] Clear Stats: Started clearing the database!");
6849 ClearDatabaseCaller = -1;
6850
6851 ClearStatsAll(client);
6852
6853 return Plugin_Handled;
6854 }
6855
6856 PrintToConsole(client, "[RANK] Clear Stats: To clear the database, execute this command again in %.2f seconds!", CLEAR_DATABASE_CONFIRMTIME);
6857 ClearDatabaseCaller = client;
6858
6859 ClearDatabaseTimer = CreateTimer(CLEAR_DATABASE_CONFIRMTIME, timer_ClearDatabase);
6860
6861 return Plugin_Handled;
6862}
6863
6864public ClearStatsMaps(client)
6865{
6866 if (!DoFastQuery(client, "START TRANSACTION"))
6867 {
6868 return;
6869 }
6870
6871 decl String:query[256];
6872 Format(query, sizeof(query), "SELECT * FROM %smaps WHERE 1 = 2", DbPrefix);
6873
6874 SQL_TQuery(db, ClearStatsMapsHandler, query, client);
6875}
6876
6877public ClearStatsAll(client)
6878{
6879 if (!DoFastQuery(client, "START TRANSACTION"))
6880 {
6881 return;
6882 }
6883
6884 if (!DoFastQuery(client, "DELETE FROM %stimedmaps", DbPrefix))
6885 {
6886 PrintToConsole(client, "[RANK] Clear Stats: Clearing timedmaps table failed. Executing rollback...");
6887 DoFastQuery(client, "ROLLBACK");
6888 PrintToConsole(client, "[RANK] Clear Stats: Failure!");
6889
6890 return;
6891 }
6892
6893 if (!DoFastQuery(client, "DELETE FROM %splayers", DbPrefix))
6894 {
6895 PrintToConsole(client, "[RANK] Clear Stats: Clearing players table failed. Executing rollback...");
6896 DoFastQuery(client, "ROLLBACK");
6897 PrintToConsole(client, "[RANK] Clear Stats: Failure!");
6898
6899 return;
6900 }
6901
6902 decl String:query[256];
6903 Format(query, sizeof(query), "SELECT * FROM %smaps WHERE 1 = 2", DbPrefix);
6904
6905 SQL_TQuery(db, ClearStatsMapsHandler, query, client);
6906}
6907
6908public ClearStatsPlayers(client)
6909{
6910 if (!DoFastQuery(client, "START TRANSACTION"))
6911 {
6912 return;
6913 }
6914
6915 if (!DoFastQuery(client, "DELETE FROM %splayers", DbPrefix))
6916 {
6917 PrintToConsole(client, "[RANK] Clear Stats: Clearing players table failed. Executing rollback...");
6918 DoFastQuery(client, "ROLLBACK");
6919 PrintToConsole(client, "[RANK] Clear Stats: Failure!");
6920 }
6921 else
6922 {
6923 DoFastQuery(client, "COMMIT");
6924 PrintToConsole(client, "[RANK] Clear Stats: Ranks succesfully cleared!");
6925 }
6926}
6927
6928public ClearStatsMapsHandler(Handle:owner, Handle:hndl, const String:error[], any:client)
6929{
6930 if (hndl == INVALID_HANDLE)
6931 {
6932 PrintToConsole(client, "[RANK] Clear Stats: Query failed! (%s)", error);
6933 DoFastQuery(client, "ROLLBACK");
6934 PrintToConsole(client, "[RANK] Clear Stats: Failure!");
6935 return;
6936 }
6937
6938 new FieldCount = SQL_GetFieldCount(hndl);
6939 decl String:FieldName[MAX_LINE_WIDTH];
6940 decl String:FieldSet[MAX_LINE_WIDTH];
6941
6942 new Counter = 0;
6943 decl String:query[4096];
6944 Format(query, sizeof(query), "UPDATE %smaps SET", DbPrefix);
6945
6946 for (new i = 0; i < FieldCount; i++)
6947 {
6948 SQL_FieldNumToName(hndl, i, FieldName, sizeof(FieldName));
6949
6950 if (StrEqual(FieldName, "name", false) ||
6951 StrEqual(FieldName, "gamemode", false) ||
6952 StrEqual(FieldName, "custom", false))
6953 {
6954 continue;
6955 }
6956
6957 if (Counter++ > 0)
6958 {
6959 StrCat(query, sizeof(query), ",");
6960 }
6961
6962 Format(FieldSet, sizeof(FieldSet), " %s = 0", FieldName);
6963 StrCat(query, sizeof(query), FieldSet);
6964 }
6965
6966 if (!DoFastQuery(client, query))
6967 {
6968 PrintToConsole(client, "[RANK] Clear Stats: Clearing maps table failed. Executing rollback...");
6969 DoFastQuery(client, "ROLLBACK");
6970 PrintToConsole(client, "[RANK] Clear Stats: Failure!");
6971 }
6972 else
6973 {
6974 DoFastQuery(client, "COMMIT");
6975 PrintToConsole(client, "[RANK] Clear Stats: Stats succesfully cleared!", query);
6976 }
6977}
6978
6979bool:DoFastQuery(Client, const String:Query[], any:...)
6980{
6981 new String:FormattedQuery[4096];
6982 VFormat(FormattedQuery, sizeof(FormattedQuery), Query, 3);
6983
6984 new String:Error[1024];
6985
6986 if (!SQL_FastQuery(db, FormattedQuery))
6987 {
6988 if (SQL_GetError(db, Error, sizeof(Error)))
6989 {
6990 PrintToConsole(Client, "[RANK] Fast query failed! (Error = \"%s\") Query = \"%s\"", Error, FormattedQuery);
6991 LogError("Fast query failed! (Error = \"%s\") Query = \"%s\"", Error, FormattedQuery);
6992 }
6993 else
6994 {
6995 PrintToConsole(Client, "[RANK] Fast query failed! Query = \"%s\"", FormattedQuery);
6996 LogError("Fast query failed! Query = \"%s\"", FormattedQuery);
6997 }
6998
6999 return false;
7000 }
7001
7002 return true;
7003}
7004
7005public Action:timer_ClearDatabase(Handle:timer, any:data)
7006{
7007 ClearDatabaseTimer = INVALID_HANDLE;
7008 ClearDatabaseCaller = -1;
7009}
7010
7011// Begin generating the RANKMENU display panel.
7012public Action:cmd_ShowRankMenu(client, args)
7013{
7014 if (client <= 0)
7015 {
7016 if (client == 0)
7017 PrintToConsole(0, "[RANK] You must be ingame to operate rankmenu.");
7018
7019 return Plugin_Handled;
7020 }
7021
7022 if (!IsClientConnected(client) && !IsClientInGame(client))
7023 return Plugin_Handled;
7024
7025 if (IsClientBot(client))
7026 return Plugin_Handled;
7027
7028 DisplayRankMenu(client);
7029
7030 return Plugin_Handled;
7031}
7032
7033public DisplayRankMenu(client)
7034{
7035 decl String:Title[MAX_LINE_WIDTH];
7036
7037 Format(Title, sizeof(Title), "%s:", PLUGIN_NAME);
7038
7039 new Handle:menu = CreateMenu(Menu_CreateRankMenuHandler);
7040
7041 SetMenuTitle(menu, Title);
7042 SetMenuExitBackButton(menu, false);
7043 SetMenuExitButton(menu, true);
7044
7045 AddMenuItem(menu, "rank", "Show my rank");
7046 AddMenuItem(menu, "top10", "Show top 10");
7047 AddMenuItem(menu, "top10ppm", "Show top 10 PPM");
7048 AddMenuItem(menu, "nextrank", "Show my next rank");
7049 AddMenuItem(menu, "showtimer", "Show current timer");
7050 AddMenuItem(menu, "showrank", "Show others rank");
7051 AddMenuItem(menu, "showppm", "Show others PPM");
7052 if (GetConVarBool(cvar_EnableRankVote) && IsTeamGamemode())
7053 {
7054 AddMenuItem(menu, "rankvote", "Vote for team shuffle by PPM");
7055 }
7056 AddMenuItem(menu, "timedmaps", "Show all map timings");
7057 if (IsSingleTeamGamemode())
7058 {
7059 AddMenuItem(menu, "maptimes", "Show current map timings");
7060 }
7061 if (GetConVarInt(cvar_AnnounceMode))
7062 {
7063 AddMenuItem(menu, "showsettings", "Modify rank settings");
7064 }
7065 //AddMenuItem(menu, "showmaptimes", "Show others current map timings");
7066
7067 Format(Title, sizeof(Title), "About %s", PLUGIN_NAME);
7068 AddMenuItem(menu, "rankabout", Title);
7069
7070 DisplayMenu(menu, client, 30);
7071
7072 if (EnableSounds_Rankmenu_Show && GetConVarBool(cvar_SoundsEnabled))
7073 EmitSoundToClient(client, StatsSound_Rankmenu_Show);
7074}
7075
7076NotServerConsoleCommand()
7077{
7078 PrintToConsole(0, "[RANK] Error: Most of the rank commands including this one are not available from server console.");
7079}
7080
7081// Begin generating the RANK display panel.
7082public Action:cmd_ShowRank(client, args)
7083{
7084 if (client == 0)
7085 {
7086 NotServerConsoleCommand();
7087 return Plugin_Handled;
7088 }
7089
7090 if (!IsClientConnected(client) && !IsClientInGame(client))
7091 return Plugin_Handled;
7092
7093 if (IsClientBot(client))
7094 return Plugin_Handled;
7095
7096 decl String:SteamID[MAX_LINE_WIDTH];
7097 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
7098
7099 QueryClientStatsSteamID(client, SteamID, CM_RANK);
7100
7101 return Plugin_Handled;
7102}
7103
7104// Generate client's point total.
7105public GetClientPointsPlayerJoined(Handle:owner, Handle:hndl, const String:error[], any:client)
7106{
7107 GetClientPointsWorker(owner, hndl, error, client, GetClientRankPlayerJoined);
7108}
7109
7110// Generate client's point total.
7111public GetClientPointsRankChange(Handle:owner, Handle:hndl, const String:error[], any:client)
7112{
7113 GetClientPointsWorker(owner, hndl, error, client, GetClientRankRankChange);
7114}
7115
7116// Generate client's point total.
7117GetClientPointsWorker(Handle:owner, Handle:hndl, const String:error[], any:client, SQLTCallback:callback=INVALID_FUNCTION)
7118{
7119 if (!client)
7120 {
7121 return;
7122 }
7123
7124 if (callback == INVALID_FUNCTION)
7125 {
7126 LogError("GetClientPointsWorker method invoke failed: SQLTCallback:callback=INVALID_FUNCTION");
7127 return;
7128 }
7129
7130 if (hndl == INVALID_HANDLE)
7131 {
7132 LogError("GetClientPointsWorker Query failed: %s", error);
7133 return;
7134 }
7135
7136 GetClientPoints(owner, hndl, error, client);
7137 QueryClientRank(client, callback);
7138}
7139
7140// Generate client's point total.
7141public GetClientPoints(Handle:owner, Handle:hndl, const String:error[], any:client)
7142{
7143 if (!client)
7144 return;
7145
7146 if (hndl == INVALID_HANDLE)
7147 {
7148 LogError("GetClientPoints Query failed: %s", error);
7149 return;
7150 }
7151
7152 if (SQL_FetchRow(hndl))
7153 {
7154 ClientPoints[client] = SQL_FetchInt(hndl, 0);
7155 }
7156}
7157
7158// Generate client's gamemode point total.
7159public GetClientGameModePoints(Handle:owner, Handle:hndl, const String:error[], any:client)
7160{
7161 if (!client)
7162 return;
7163
7164 if (hndl == INVALID_HANDLE)
7165 {
7166 LogError("GetClientGameModePoints Query failed: %s", error);
7167 return;
7168 }
7169
7170 while (SQL_FetchRow(hndl))
7171 {
7172 ClientGameModePoints[client][GAMEMODE_COOP] = SQL_FetchInt(hndl, GAMEMODE_COOP);
7173 ClientGameModePoints[client][GAMEMODE_VERSUS] = SQL_FetchInt(hndl, GAMEMODE_VERSUS);
7174 ClientGameModePoints[client][GAMEMODE_REALISM] = SQL_FetchInt(hndl, GAMEMODE_REALISM);
7175 ClientGameModePoints[client][GAMEMODE_SURVIVAL] = SQL_FetchInt(hndl, GAMEMODE_SURVIVAL);
7176 ClientGameModePoints[client][GAMEMODE_SCAVENGE] = SQL_FetchInt(hndl, GAMEMODE_SCAVENGE);
7177 ClientGameModePoints[client][GAMEMODE_REALISMVERSUS] = SQL_FetchInt(hndl, GAMEMODE_REALISMVERSUS);
7178 ClientGameModePoints[client][GAMEMODE_OTHERMUTATIONS] = SQL_FetchInt(hndl, GAMEMODE_OTHERMUTATIONS);
7179 }
7180}
7181
7182// Generate client's next rank.
7183public DisplayClientNextRank(Handle:owner, Handle:hndl, const String:error[], any:client)
7184{
7185 if (!client)
7186 return;
7187
7188 if (hndl == INVALID_HANDLE)
7189 {
7190 LogError("GetClientRankRankChange Query failed: %s", error);
7191 return;
7192 }
7193
7194 GetClientNextRank(owner, hndl, error, client);
7195
7196 DisplayNextRank(client);
7197}
7198
7199// Generate client's next rank.
7200public GetClientNextRank(Handle:owner, Handle:hndl, const String:error[], any:client)
7201{
7202 if (!client)
7203 return;
7204
7205 if (hndl == INVALID_HANDLE)
7206 {
7207 LogError("GetClientRankRankChange Query failed: %s", error);
7208 return;
7209 }
7210
7211 if (SQL_FetchRow(hndl))
7212 ClientNextRank[client] = SQL_FetchInt(hndl, 0);
7213 else
7214 ClientNextRank[client] = 0;
7215}
7216
7217// Generate client's rank.
7218public GetClientRankRankChange(Handle:owner, Handle:hndl, const String:error[], any:client)
7219{
7220 if (!client)
7221 return;
7222
7223 if (hndl == INVALID_HANDLE)
7224 {
7225 LogError("GetClientRankRankChange Query failed: %s", error);
7226 return;
7227 }
7228
7229 GetClientRank(owner, hndl, error, client);
7230
7231 if (RankChangeLastRank[client] != ClientRank[client])
7232 {
7233 new RankChange = RankChangeLastRank[client] - ClientRank[client];
7234
7235 if (!RankChangeFirstCheck[client] && RankChange == 0)
7236 return;
7237
7238 RankChangeLastRank[client] = ClientRank[client];
7239
7240 if (RankChangeFirstCheck[client])
7241 {
7242 RankChangeFirstCheck[client] = false;
7243 return;
7244 }
7245
7246 if (!GetConVarInt(cvar_AnnounceMode) || !GetConVarBool(cvar_AnnounceRankChange))
7247 return;
7248
7249 decl String:Label[16];
7250 if (RankChange > 0)
7251 Format(Label, sizeof(Label), "GAINED");
7252 else
7253 {
7254 RankChange *= -1;
7255 Format(Label, sizeof(Label), "DROPPED");
7256 }
7257
7258 if (!IsClientBot(client) && IsClientConnected(client) && IsClientInGame(client))
7259 StatsPrintToChat(client, "You've \x04%s \x01rank for \x04%i position%s\x01! \x05(Rank: %i)", Label, RankChange, (RankChange > 1 ? "s" : ""), RankChangeLastRank[client]);
7260 }
7261}
7262
7263// Generate client's rank.
7264public GetClientRankPlayerJoined(Handle:owner, Handle:hndl, const String:error[], any:client)
7265{
7266 if (!client)
7267 {
7268 return;
7269 }
7270
7271 if (hndl == INVALID_HANDLE)
7272 {
7273 LogError("GetClientRankPlayerJoined Query failed: %s", error);
7274 return;
7275 }
7276
7277 GetClientRank(owner, hndl, error, client);
7278
7279 decl String:userName[MAX_LINE_WIDTH];
7280 GetClientName(client, userName, sizeof(userName));
7281
7282 if (ClientRank[client] > 0)
7283 {
7284 StatsPrintToChatAll("Player \x05%s \x01joined the game! (Rank: \x03%i \x01/ Points: \x03%i\x01)", userName, ClientRank[client], ClientPoints[client]);
7285 }
7286 else
7287 {
7288 StatsPrintToChatAll("Player \x05%s \x01joined the game! (No ranking yet)", userName);
7289 }
7290}
7291
7292// Generate client's rank.
7293public GetClientRank(Handle:owner, Handle:hndl, const String:error[], any:client)
7294{
7295 if (!client)
7296 return;
7297
7298 if (hndl == INVALID_HANDLE)
7299 {
7300 LogError("GetClientRank Query failed: %s", error);
7301 return;
7302 }
7303
7304 if (SQL_FetchRow(hndl))
7305 ClientRank[client] = SQL_FetchInt(hndl, 0);
7306}
7307
7308// Generate client's rank.
7309public GetClientGameModeRank(Handle:owner, Handle:hndl, const String:error[], any:client)
7310{
7311 if (!client)
7312 return;
7313
7314 if (hndl == INVALID_HANDLE)
7315 {
7316 LogError("GetClientGameModeRank Query failed: %s", error);
7317 return;
7318 }
7319
7320 while (SQL_FetchRow(hndl))
7321 ClientGameModeRank[client] = SQL_FetchInt(hndl, 0);
7322}
7323
7324// Generate total rank amount.
7325public GetRankTotal(Handle:owner, Handle:hndl, const String:error[], any:data)
7326{
7327 if (hndl == INVALID_HANDLE)
7328 {
7329 LogError("GetRankTotal Query failed: %s", error);
7330 return;
7331 }
7332
7333 while (SQL_FetchRow(hndl))
7334 RankTotal = SQL_FetchInt(hndl, 0);
7335}
7336
7337// Generate total gamemode rank amount.
7338public GetGameModeRankTotal(Handle:owner, Handle:hndl, const String:error[], any:data)
7339{
7340 if (hndl == INVALID_HANDLE)
7341 {
7342 LogError("GetGameModeRankTotal Query failed: %s", error);
7343 return;
7344 }
7345
7346 while (SQL_FetchRow(hndl))
7347 GameModeRankTotal = SQL_FetchInt(hndl, 0);
7348}
7349
7350// Send the NEXTRANK panel to the client's display.
7351public DisplayNextRank(client)
7352{
7353 if (!client)
7354 return;
7355
7356 new Handle:NextRankPanel = CreatePanel();
7357 new String:Value[MAX_LINE_WIDTH];
7358
7359 SetPanelTitle(NextRankPanel, "Next Rank:");
7360
7361 if (ClientNextRank[client])
7362 {
7363 Format(Value, sizeof(Value), "Points required: %i", ClientNextRank[client]);
7364 DrawPanelText(NextRankPanel, Value);
7365
7366 Format(Value, sizeof(Value), "Current rank: %i", ClientRank[client]);
7367 DrawPanelText(NextRankPanel, Value);
7368 }
7369 else
7370 DrawPanelText(NextRankPanel, "You are 1st");
7371
7372 DrawPanelItem(NextRankPanel, "More...");
7373 DrawPanelItem(NextRankPanel, "Close");
7374 SendPanelToClient(NextRankPanel, client, NextRankPanelHandler, 30);
7375 CloseHandle(NextRankPanel);
7376}
7377
7378// Send the NEXTRANK panel to the client's display.
7379public DisplayNextRankFull(Handle:owner, Handle:hndl, const String:error[], any:client)
7380{
7381 if (!client)
7382 return;
7383
7384 if (hndl == INVALID_HANDLE)
7385 {
7386 LogError("DisplayNextRankFull Query failed: %s", error);
7387 return;
7388 }
7389
7390 if(SQL_GetRowCount(hndl) <= 1)
7391 return;
7392
7393 new Points;
7394 decl String:Name[32];
7395
7396 new Handle:NextRankPanel = CreatePanel();
7397 new String:Value[MAX_LINE_WIDTH];
7398
7399 SetPanelTitle(NextRankPanel, "Next Rank:");
7400
7401 if (ClientNextRank[client])
7402 {
7403 Format(Value, sizeof(Value), "Points required: %i", ClientNextRank[client]);
7404 DrawPanelText(NextRankPanel, Value);
7405
7406 Format(Value, sizeof(Value), "Current rank: %i", ClientRank[client]);
7407 DrawPanelText(NextRankPanel, Value);
7408 }
7409 else
7410 DrawPanelText(NextRankPanel, "You are 1st");
7411
7412 while (SQL_FetchRow(hndl))
7413 {
7414 SQL_FetchString(hndl, 0, Name, sizeof(Name));
7415 Points = SQL_FetchInt(hndl, 1);
7416
7417 Format(Value, sizeof(Value), "%i points: %s", Points, Name);
7418 DrawPanelText(NextRankPanel, Value);
7419 }
7420
7421 DrawPanelItem(NextRankPanel, "Close");
7422 SendPanelToClient(NextRankPanel, client, NextRankFullPanelHandler, 30);
7423 CloseHandle(NextRankPanel);
7424}
7425
7426// Send the RANK panel to the client's display.
7427public DisplayRank(Handle:owner, Handle:hndl, const String:error[], any:client)
7428{
7429 if (!client)
7430 return;
7431
7432 if (hndl == INVALID_HANDLE)
7433 {
7434 LogError("DisplayRank Query failed: %s", error);
7435 return;
7436 }
7437
7438 new Float:PPM;
7439 new Playtime, Points, InfectedKilled, SurvivorsKilled, Headshots;
7440 new String:Name[32];
7441
7442 if (SQL_FetchRow(hndl))
7443 {
7444 SQL_FetchString(hndl, 0, Name, sizeof(Name));
7445 Playtime = SQL_FetchInt(hndl, 1);
7446 Points = SQL_FetchInt(hndl, 2);
7447 InfectedKilled = SQL_FetchInt(hndl, 3);
7448 SurvivorsKilled = SQL_FetchInt(hndl, 4);
7449 Headshots = SQL_FetchInt(hndl, 5);
7450 PPM = float(Points) / float(Playtime);
7451 }
7452 else
7453 {
7454 GetClientName(client, Name, sizeof(Name));
7455 Playtime = 0;
7456 Points = 0;
7457 InfectedKilled = 0;
7458 SurvivorsKilled = 0;
7459 Headshots = 0;
7460 PPM = 0.0;
7461 }
7462
7463 new Handle:RankPanel = CreatePanel();
7464 new String:Value[MAX_LINE_WIDTH];
7465 new String:URL[MAX_LINE_WIDTH];
7466
7467 GetConVarString(cvar_SiteURL, URL, sizeof(URL));
7468 new Float:HeadshotRatio = Headshots == 0 ? 0.00 : FloatDiv(float(Headshots), float(InfectedKilled))*100;
7469
7470 Format(Value, sizeof(Value), "Ranking of %s" , Name);
7471 SetPanelTitle(RankPanel, Value);
7472
7473 Format(Value, sizeof(Value), "Rank: %i of %i" , ClientRank[client], RankTotal);
7474 DrawPanelText(RankPanel, Value);
7475
7476 if (!InvalidGameMode())
7477 {
7478 Format(Value, sizeof(Value), "%s Rank: %i of %i" ,CurrentGamemodeLabel , ClientGameModeRank[client], GameModeRankTotal);
7479 DrawPanelText(RankPanel, Value);
7480 }
7481
7482 if (Playtime > 60)
7483 {
7484 Format(Value, sizeof(Value), "Playtime: %.2f hours" , FloatDiv(float(Playtime), 60.0));
7485 DrawPanelText(RankPanel, Value);
7486 }
7487 else
7488 {
7489 Format(Value, sizeof(Value), "Playtime: %i min" , Playtime);
7490 DrawPanelText(RankPanel, Value);
7491 }
7492
7493 Format(Value, sizeof(Value), "Points: %i" , Points);
7494 DrawPanelText(RankPanel, Value);
7495
7496 Format(Value, sizeof(Value), "PPM: %.2f" , PPM);
7497 DrawPanelText(RankPanel, Value);
7498
7499 Format(Value, sizeof(Value), "Infected Killed: %i" , InfectedKilled);
7500 DrawPanelText(RankPanel, Value);
7501
7502 Format(Value, sizeof(Value), "Survivors Killed: %i" , SurvivorsKilled);
7503 DrawPanelText(RankPanel, Value);
7504
7505 Format(Value, sizeof(Value), "Headshots: %i" , Headshots);
7506 DrawPanelText(RankPanel, Value);
7507
7508 Format(Value, sizeof(Value), "Headshot Ratio: %.2f \%" , HeadshotRatio);
7509 DrawPanelText(RankPanel, Value);
7510
7511 if (!StrEqual(URL, "", false))
7512 {
7513 Format(Value, sizeof(Value), "For full stats visit %s", URL);
7514 DrawPanelText(RankPanel, Value);
7515 }
7516
7517 //DrawPanelItem(RankPanel, "Next Rank");
7518 DrawPanelItem(RankPanel, "Close");
7519 SendPanelToClient(RankPanel, client, RankPanelHandler, 30);
7520 CloseHandle(RankPanel);
7521}
7522
7523public StartRankVote(client)
7524{
7525 if (L4DStatsConf == INVALID_HANDLE)
7526 {
7527 if (client > 0)
7528 {
7529 StatsPrintToChatPreFormatted(client, "The \x04Rank Vote \x01is \x03DISABLED\x01. \x05Plugin configurations failed.");
7530 }
7531 else
7532 {
7533 PrintToConsole(0, "[RANK] The Rank Vote is DISABLED! Could not load gamedata/l4d_stats.txt.");
7534 }
7535 }
7536
7537 else if (!GetConVarBool(cvar_EnableRankVote))
7538 {
7539 if (client > 0)
7540 {
7541 StatsPrintToChatPreFormatted(client, "The \x04Rank Vote \x01is \x03DISABLED\x01.");
7542 }
7543 else
7544 {
7545 PrintToConsole(0, "[RANK] The Rank Vote is DISABLED.");
7546 }
7547 }
7548
7549 else
7550 {
7551 InitializeRankVote(client);
7552 }
7553}
7554
7555// Toggle client rank mute.
7556public Action:cmd_ToggleClientRankMute(client, args)
7557{
7558 if (client == 0)
7559 return Plugin_Handled;
7560
7561 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7562 return Plugin_Handled;
7563
7564 new String:SteamID[MAX_LINE_WIDTH];
7565 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
7566
7567 ClientRankMute[client] = !ClientRankMute[client];
7568
7569 decl String:query[256];
7570 Format(query, sizeof(query), "UPDATE %ssettings SET mute = %i WHERE steamid = '%s'", DbPrefix, (ClientRankMute[client] ? 1 : 0), SteamID);
7571 SendSQLUpdate(query);
7572
7573 AnnounceClientRankMute(client);
7574
7575 return Plugin_Handled;
7576}
7577
7578ShowRankMuteUsage(client)
7579{
7580 PrintToConsole(client, "[RANK] Command usage: sm_rankmute <0|1>");
7581}
7582
7583// Show current message of the day.
7584public Action:cmd_ShowMotd(client, args)
7585{
7586 ShowMOTD(client, true);
7587}
7588
7589// Set client rank mute.
7590public Action:cmd_ClientRankMute(client, args)
7591{
7592 if (client == 0)
7593 return Plugin_Handled;
7594
7595 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7596 return Plugin_Handled;
7597
7598 if (args != 1)
7599 {
7600 ShowRankMuteUsage(client);
7601 return Plugin_Handled;
7602 }
7603
7604 decl String:arg[MAX_LINE_WIDTH];
7605 GetCmdArgString(arg, sizeof(arg));
7606
7607 if (!StrEqual(arg, "0") && !StrEqual(arg, "1"))
7608 {
7609 ShowRankMuteUsage(client);
7610 return Plugin_Handled;
7611 }
7612
7613 decl String:SteamID[MAX_LINE_WIDTH];
7614 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
7615
7616 ClientRankMute[client] = StrEqual(arg, "1");
7617
7618 decl String:query[256];
7619 Format(query, sizeof(query), "UPDATE %ssettings SET mute = %s WHERE steamid = '%s'", DbPrefix, arg, SteamID);
7620 SendSQLUpdate(query);
7621
7622 AnnounceClientRankMute(client);
7623
7624 return Plugin_Handled;
7625}
7626
7627AnnounceClientRankMute(client)
7628{
7629 StatsPrintToChat2(client, true, "You %s \x01the \x05%s\x01.", (ClientRankMute[client] ? "\x04MUTED" : "\x03UNMUTED"), PLUGIN_NAME);
7630}
7631
7632// Start RANKVOTE.
7633public Action:cmd_RankVote(client, args)
7634{
7635 if (client == 0)
7636 {
7637 StartRankVote(client);
7638 return Plugin_Handled;
7639 }
7640
7641 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7642 {
7643 return Plugin_Handled;
7644 }
7645
7646 new ClientFlags = GetUserFlagBits(client);
7647 new bool:IsAdmin = ((ClientFlags & ADMFLAG_GENERIC) == ADMFLAG_GENERIC);
7648
7649 new ClientTeam = GetClientTeam(client);
7650
7651 if (!IsAdmin && ClientTeam != TEAM_SURVIVORS && ClientTeam != TEAM_INFECTED)
7652 {
7653 StatsPrintToChatPreFormatted2(client, true, "The spectators cannot initiate the \x04Rank Vote\x01.");
7654 return Plugin_Handled;
7655 }
7656
7657 StartRankVote(client);
7658
7659 return Plugin_Handled;
7660}
7661
7662// Generate the TIMEDMAPS display menu.
7663public Action:cmd_TimedMaps(client, args)
7664{
7665 if (client == 0 || !IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7666 return Plugin_Handled;
7667
7668 decl String:query[256];
7669 Format(query, sizeof(query), "SELECT DISTINCT tm.gamemode, tm.mutation FROM %stimedmaps AS tm INNER JOIN %splayers AS p ON tm.steamid = p.steamid", DbPrefix, DbPrefix);
7670 SQL_TQuery(db, CreateTimedMapsMenu, query, client);
7671
7672 return Plugin_Handled;
7673}
7674
7675// Generate the MAPTIME display menu.
7676public Action:cmd_MapTimes(client, args)
7677{
7678 if (client == 0 || !IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7679 return Plugin_Handled;
7680
7681 decl String:Info[MAX_LINE_WIDTH], String:CurrentMapName[MAX_LINE_WIDTH];
7682
7683 GetCurrentMap(CurrentMapName, sizeof(CurrentMapName));
7684 Format(Info, sizeof(Info), "%i\\%s", CurrentGamemodeID, CurrentMapName);
7685
7686 DisplayTimedMapsMenu3FromInfo(client, Info);
7687
7688 return Plugin_Handled;
7689}
7690
7691// Generate the SHOWMAPTIME display menu.
7692public Action:cmd_ShowMapTimes(client, args)
7693{
7694 if (client == 0 || !IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7695 return Plugin_Handled;
7696
7697 StatsPrintToChatPreFormatted2(client, true, "\x05NOT IMPLEMENTED YET");
7698
7699 return Plugin_Handled;
7700}
7701
7702// Generate the SHOWPPM display menu.
7703public Action:cmd_ShowPPMs(client, args)
7704{
7705 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7706 return Plugin_Handled;
7707
7708 decl String:query[1024];
7709 //Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers", DbPrefix);
7710 //SQL_TQuery(db, GetRankTotal, query);
7711
7712 Format(query, sizeof(query), "SELECT steamid, name, (%s) / (%s) AS ppm FROM %splayers WHERE ", DB_PLAYERS_TOTALPOINTS, DB_PLAYERS_TOTALPLAYTIME, DbPrefix);
7713
7714 new maxplayers = GetMaxClients();
7715 decl String:SteamID[MAX_LINE_WIDTH], String:where[512];
7716 new counter = 0;
7717
7718 for (new i = 1; i <= maxplayers; i++)
7719 {
7720 if (IsClientBot(i) || !IsClientConnected(i) || !IsClientInGame(i))
7721 continue;
7722
7723 if (counter++ > 0)
7724 StrCat(query, sizeof(query), "OR ");
7725
7726 GetClientRankAuthString(i, SteamID, sizeof(SteamID));
7727 Format(where, sizeof(where), "steamid = '%s' ", SteamID);
7728 StrCat(query, sizeof(query), where);
7729 }
7730
7731 if (counter == 0)
7732 return Plugin_Handled;
7733
7734 if (counter == 1)
7735 {
7736 cmd_ShowRank(client, 0);
7737 return Plugin_Handled;
7738 }
7739
7740 StrCat(query, sizeof(query), "ORDER BY ppm DESC");
7741
7742 SQL_TQuery(db, CreatePPMMenu, query, client);
7743
7744 return Plugin_Handled;
7745}
7746
7747// Generate the SHOWRANK display menu.
7748public Action:cmd_ShowRanks(client, args)
7749{
7750 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7751 return Plugin_Handled;
7752
7753 decl String:query[1024];
7754 //Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers", DbPrefix);
7755 //SQL_TQuery(db, GetRankTotal, query);
7756
7757 Format(query, sizeof(query), "SELECT steamid, name, %s AS totalpoints FROM %splayers WHERE ", DB_PLAYERS_TOTALPOINTS, DbPrefix);
7758
7759 new maxplayers = GetMaxClients();
7760 decl String:SteamID[MAX_LINE_WIDTH], String:where[512];
7761 new counter = 0;
7762
7763 for (new i = 1; i <= maxplayers; i++)
7764 {
7765 if (IsClientBot(i) || !IsClientConnected(i) || !IsClientInGame(i))
7766 continue;
7767
7768 if (counter++ > 0)
7769 StrCat(query, sizeof(query), "OR ");
7770
7771 GetClientRankAuthString(i, SteamID, sizeof(SteamID));
7772 Format(where, sizeof(where), "steamid = '%s' ", SteamID);
7773 StrCat(query, sizeof(query), where);
7774 }
7775
7776 if (counter == 0)
7777 return Plugin_Handled;
7778
7779 if (counter == 1)
7780 {
7781 cmd_ShowRank(client, 0);
7782 return Plugin_Handled;
7783 }
7784
7785 StrCat(query, sizeof(query), "ORDER BY totalpoints DESC");
7786
7787 SQL_TQuery(db, CreateRanksMenu, query, client);
7788
7789 return Plugin_Handled;
7790}
7791
7792// Generate the TOPPPM display panel.
7793public Action:cmd_ShowTop10PPM(client, args)
7794{
7795 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7796 return Plugin_Handled;
7797
7798 decl String:query[1024];
7799 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers", DbPrefix);
7800 SQL_TQuery(db, GetRankTotal, query);
7801
7802 Format(query, sizeof(query), "SELECT name, (%s) / (%s) AS ppm FROM %splayers WHERE (%s) >= %i ORDER BY ppm DESC, (%s) DESC LIMIT 10", DB_PLAYERS_TOTALPOINTS, DB_PLAYERS_TOTALPLAYTIME, DbPrefix, DB_PLAYERS_TOTALPLAYTIME, GetConVarInt(cvar_Top10PPMMin), DB_PLAYERS_TOTALPLAYTIME);
7803 SQL_TQuery(db, DisplayTop10PPM, query, client);
7804
7805 return Plugin_Handled;
7806}
7807
7808// Generate the TOP10 display panel.
7809public Action:cmd_ShowTop10(client, args)
7810{
7811 if (!IsClientConnected(client) && !IsClientInGame(client) && IsClientBot(client))
7812 return Plugin_Handled;
7813
7814 decl String:query[512];
7815 Format(query, sizeof(query), "SELECT COUNT(*) FROM %splayers", DbPrefix);
7816 SQL_TQuery(db, GetRankTotal, query);
7817
7818 Format(query, sizeof(query), "SELECT name FROM %splayers ORDER BY %s DESC LIMIT 10", DbPrefix, DB_PLAYERS_TOTALPOINTS);
7819 SQL_TQuery(db, DisplayTop10, query, client);
7820
7821 return Plugin_Handled;
7822}
7823
7824// Find a player from Top 10 ranking.
7825public GetClientFromTop10(client, rank)
7826{
7827 decl String:query[512];
7828 Format(query, sizeof(query), "SELECT (%s) as totalpoints, steamid FROM %splayers ORDER BY totalpoints DESC LIMIT %i,1", DB_PLAYERS_TOTALPOINTS, DbPrefix, rank);
7829 SQL_TQuery(db, GetClientTop10, query, client);
7830}
7831
7832// Find a player from Top 10 PPM ranking.
7833public GetClientFromTop10PPM(client, rank)
7834{
7835 decl String:query[1024];
7836 Format(query, sizeof(query), "SELECT (%s) AS totalpoints, steamid, (%s) AS totalplaytime FROM %splayers WHERE (%s) >= %i ORDER BY (totalpoints / totalplaytime) DESC, totalplaytime DESC LIMIT %i,1", DB_PLAYERS_TOTALPOINTS, DB_PLAYERS_TOTALPLAYTIME, DbPrefix, DB_PLAYERS_TOTALPLAYTIME, GetConVarInt(cvar_Top10PPMMin), rank);
7837 SQL_TQuery(db, GetClientTop10, query, client);
7838}
7839
7840// Send the Top 10 player's info to the client.
7841public GetClientTop10(Handle:owner, Handle:hndl, const String:error[], any:client)
7842{
7843 if (!client || hndl == INVALID_HANDLE)
7844 {
7845 LogError("GetClientTop10 failed! Reason: %s", error);
7846 return;
7847 }
7848
7849 decl String:SteamID[MAX_LINE_WIDTH];
7850
7851 while (SQL_FetchRow(hndl))
7852 {
7853 SQL_FetchString(hndl, 1, SteamID, sizeof(SteamID));
7854
7855 QueryClientStatsSteamID(client, SteamID, CM_TOP10);
7856 }
7857}
7858
7859public ExecuteTeamShuffle(Handle:owner, Handle:hndl, const String:error[], any:data)
7860{
7861 if (hndl == INVALID_HANDLE)
7862 {
7863 LogError("ExecuteTeamShuffle failed! Reason: %s", error);
7864 return;
7865 }
7866
7867 decl String:SteamID[MAX_LINE_WIDTH];
7868 new i, team, maxplayers = GetMaxClients(), client, topteam;
7869 new SurvivorsLimit = GetConVarInt(cvar_SurvivorLimit), InfectedLimit = GetConVarInt(cvar_InfectedLimit);
7870 new Handle:PlayersTrie = CreateTrie();
7871 new Handle:InfectedArray = CreateArray();
7872 new Handle:SurvivorArray = CreateArray();
7873
7874 for (i = 1; i <= maxplayers; i++)
7875 {
7876 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
7877 {
7878 GetClientRankAuthString(i, SteamID, sizeof(SteamID));
7879
7880 if (!SetTrieValue(PlayersTrie, SteamID, i, false))
7881 {
7882 LogError("ExecuteTeamShuffle failed! Reason: Duplicate SteamID while generating shuffled teams.");
7883 StatsPrintToChatAllPreFormatted2(true, "Team shuffle failed in an error.");
7884
7885 SetConVarBool(cvar_EnableRankVote, false);
7886
7887 ClearTrie(PlayersTrie);
7888 CloseHandle(PlayersTrie);
7889
7890 CloseHandle(hndl);
7891
7892 return;
7893 }
7894
7895 switch (GetClientTeam(i))
7896 {
7897 case TEAM_SURVIVORS:
7898 PushArrayCell(SurvivorArray, i);
7899 case TEAM_INFECTED:
7900 PushArrayCell(InfectedArray, i);
7901 }
7902 }
7903 }
7904
7905 new SurvivorCounter = GetArraySize(SurvivorArray);
7906 new InfectedCounter = GetArraySize(InfectedArray);
7907
7908 i = 0;
7909 topteam = 0;
7910
7911 while (SQL_FetchRow(hndl))
7912 {
7913 SQL_FetchString(hndl, 0, SteamID, sizeof(SteamID));
7914
7915 if (GetTrieValue(PlayersTrie, SteamID, client))
7916 {
7917 team = GetClientTeam(client);
7918
7919 if (i == 0)
7920 {
7921 if (team == TEAM_SURVIVORS)
7922 {
7923 RemoveFromArray(SurvivorArray, FindValueInArray(SurvivorArray, client));
7924 }
7925 else
7926 {
7927 RemoveFromArray(InfectedArray, FindValueInArray(InfectedArray, client));
7928 }
7929
7930 topteam = team;
7931 i++;
7932
7933 continue;
7934 }
7935
7936 if (i++ % 2)
7937 {
7938 if (topteam == TEAM_SURVIVORS && team == TEAM_INFECTED)
7939 {
7940 RemoveFromArray(InfectedArray, FindValueInArray(InfectedArray, client));
7941 }
7942 else if (topteam == TEAM_INFECTED && team == TEAM_SURVIVORS)
7943 {
7944 RemoveFromArray(SurvivorArray, FindValueInArray(SurvivorArray, client));
7945 }
7946 }
7947 else
7948 {
7949 if (topteam == TEAM_SURVIVORS && team == TEAM_SURVIVORS)
7950 {
7951 RemoveFromArray(SurvivorArray, FindValueInArray(SurvivorArray, client));
7952 }
7953 else if (topteam == TEAM_INFECTED && team == TEAM_INFECTED)
7954 {
7955 RemoveFromArray(InfectedArray, FindValueInArray(InfectedArray, client));
7956 }
7957 }
7958 }
7959 }
7960
7961 if (GetArraySize(SurvivorArray) > 0 || GetArraySize(InfectedArray) > 0)
7962 {
7963 new NewSurvivorCounter = SurvivorCounter - GetArraySize(SurvivorArray) + GetArraySize(InfectedArray);
7964 new NewInfectedCounter = InfectedCounter - GetArraySize(InfectedArray) + GetArraySize(SurvivorArray);
7965
7966 if (NewSurvivorCounter > SurvivorsLimit || NewInfectedCounter > InfectedLimit)
7967 {
7968 LogError("ExecuteTeamShuffle failed! Reason: Team size limits block Rank Vote functionality. (Survivors Limit = %i [%i] / Infected Limit = %i [%i])", SurvivorsLimit, NewSurvivorCounter, InfectedLimit, NewInfectedCounter);
7969 StatsPrintToChatAllPreFormatted2(true, "Team shuffle failed in an error.");
7970
7971 SetConVarBool(cvar_EnableRankVote, false);
7972 }
7973 else
7974 {
7975 CampaignOver = true;
7976
7977 decl String:Name[32];
7978
7979 // Change Survivors team to Spectators (TEMPORARILY)
7980 for (i = 0; i < GetArraySize(SurvivorArray); i++)
7981 {
7982 ChangeRankPlayerTeam(GetArrayCell(SurvivorArray, i), TEAM_SPECTATORS);
7983 }
7984
7985 // Change Infected team to Survivors
7986 for (i = 0; i < GetArraySize(InfectedArray); i++)
7987 {
7988 client = GetArrayCell(InfectedArray, i);
7989 GetClientName(client, Name, sizeof(Name));
7990
7991 ChangeRankPlayerTeam(client, TEAM_SURVIVORS);
7992
7993 StatsPrintToChatAll2(true, "\x05%s \x01was swapped to team \x03Survivors\x01!", Name);
7994 }
7995
7996 // Change Spectators (TEMPORARILY) team to Infected
7997 for (i = 0; i < GetArraySize(SurvivorArray); i++)
7998 {
7999 client = GetArrayCell(SurvivorArray, i);
8000 GetClientName(client, Name, sizeof(Name));
8001
8002 ChangeRankPlayerTeam(client, TEAM_INFECTED);
8003
8004 StatsPrintToChatAll2(true, "\x05%s \x01was swapped to team \x03Infected\x01!", Name);
8005 }
8006
8007 StatsPrintToChatAllPreFormatted2(true, "Team shuffle by player PPM \x03DONE\x01.");
8008
8009 if (EnableSounds_Rankvote && GetConVarBool(cvar_SoundsEnabled))
8010 EmitSoundToAll(SOUND_RANKVOTE);
8011 }
8012 }
8013 else
8014 {
8015 StatsPrintToChatAllPreFormatted2(true, "Teams are already even by player PPM.");
8016 }
8017
8018 ClearArray(SurvivorArray);
8019 ClearArray(InfectedArray);
8020 ClearTrie(PlayersTrie);
8021
8022 CloseHandle(SurvivorArray);
8023 CloseHandle(InfectedArray);
8024 CloseHandle(PlayersTrie);
8025
8026 CloseHandle(hndl);
8027}
8028
8029public CreateRanksMenu(Handle:owner, Handle:hndl, const String:error[], any:client)
8030{
8031 if (!client || hndl == INVALID_HANDLE)
8032 {
8033 LogError("CreateRanksMenu failed! Reason: %s", error);
8034 return;
8035 }
8036
8037 decl String:SteamID[MAX_LINE_WIDTH];
8038 new Handle:menu = CreateMenu(Menu_CreateRanksMenuHandler);
8039
8040 decl String:Name[32], String:DisplayName[MAX_LINE_WIDTH];
8041
8042 SetMenuTitle(menu, "Player Ranks:");
8043 SetMenuExitBackButton(menu, false);
8044 SetMenuExitButton(menu, true);
8045
8046 while (SQL_FetchRow(hndl))
8047 {
8048 SQL_FetchString(hndl, 0, SteamID, sizeof(SteamID));
8049 SQL_FetchString(hndl, 1, Name, sizeof(Name));
8050
8051 ReplaceString(Name, sizeof(Name), "<", "<");
8052 ReplaceString(Name, sizeof(Name), ">", ">");
8053 ReplaceString(Name, sizeof(Name), "%", "%");
8054 ReplaceString(Name, sizeof(Name), "=", "=");
8055 ReplaceString(Name, sizeof(Name), "*", "*");
8056
8057 Format(DisplayName, sizeof(DisplayName), "%s (%i points)", Name, SQL_FetchInt(hndl, 2));
8058
8059 AddMenuItem(menu, SteamID, DisplayName);
8060 }
8061
8062 DisplayMenu(menu, client, 30);
8063}
8064
8065public CreateTimedMapsMenu(Handle:owner, Handle:hndl, const String:error[], any:client)
8066{
8067 if (!client || hndl == INVALID_HANDLE)
8068 {
8069 LogError("CreateTimedMapsMenu failed! Reason: %s", error);
8070 return;
8071 }
8072
8073 if (SQL_GetRowCount(hndl) <= 0)
8074 {
8075 new Handle:TimedMapsPanel = CreatePanel();
8076 SetPanelTitle(TimedMapsPanel, "Timed Maps:");
8077
8078 DrawPanelText(TimedMapsPanel, "There are no recorded map timings!");
8079 DrawPanelItem(TimedMapsPanel, "Close");
8080
8081 SendPanelToClient(TimedMapsPanel, client, TimedMapsPanelHandler, 30);
8082 CloseHandle(TimedMapsPanel);
8083
8084 return;
8085 }
8086
8087 new Gamemode;
8088 new Handle:menu = CreateMenu(Menu_CreateTimedMapsMenuHandler);
8089 decl String:GamemodeTitle[32], String:GamemodeInfo[2]; //, MutationInfo[MAX_LINE_WIDTH];
8090
8091 SetMenuTitle(menu, "Timed Maps:");
8092 SetMenuExitBackButton(menu, false);
8093 SetMenuExitButton(menu, true);
8094
8095 while (SQL_FetchRow(hndl))
8096 {
8097 Gamemode = SQL_FetchInt(hndl, 0);
8098 IntToString(Gamemode, GamemodeInfo, sizeof(GamemodeInfo));
8099
8100 switch (Gamemode)
8101 {
8102 case GAMEMODE_COOP:
8103 strcopy(GamemodeTitle, sizeof(GamemodeTitle), "Co-op");
8104 case GAMEMODE_SURVIVAL:
8105 strcopy(GamemodeTitle, sizeof(GamemodeTitle), "Survival");
8106 case GAMEMODE_REALISM:
8107 strcopy(GamemodeTitle, sizeof(GamemodeTitle), "Realism");
8108 case GAMEMODE_OTHERMUTATIONS:
8109 {
8110 strcopy(GamemodeTitle, sizeof(GamemodeTitle), "Mutations");
8111 //SQL_FetchString(hndl, 1, MutationInfo, sizeof(MutationInfo));
8112 //Format(GamemodeTitle, sizeof(GamemodeTitle), "Mutations (%s)", MutationInfo);
8113 }
8114 default:
8115 continue;
8116 }
8117
8118 if (CurrentGamemodeID == Gamemode)
8119 StrCat(GamemodeTitle, sizeof(GamemodeTitle), TM_MENU_CURRENT);
8120
8121 AddMenuItem(menu, GamemodeInfo, GamemodeTitle);
8122 }
8123
8124 DisplayMenu(menu, client, 30);
8125
8126 return;
8127}
8128
8129public Menu_CreateTimedMapsMenuHandler(Handle:menu, MenuAction:action, param1, param2)
8130{
8131 if (menu == INVALID_HANDLE)
8132 return;
8133
8134 if (action == MenuAction_End)
8135 CloseHandle(menu);
8136
8137 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
8138 return;
8139
8140 decl String:Info[2];
8141 new bool:found = GetMenuItem(menu, param2, Info, sizeof(Info));
8142
8143 if (!found)
8144 return;
8145
8146 DisplayTimedMapsMenu2FromInfo(param1, Info);
8147}
8148
8149bool:TimedMapsMenuInfoMarker(String:Info[], MenuNumber)
8150{
8151 if (Info[0] == '\0' || MenuNumber < 2)
8152 return false;
8153
8154 new Position = -1, TempPosition;
8155
8156 for (new i = 0; i < MenuNumber; i++)
8157 {
8158 TempPosition = FindCharInString(Info[Position + 1], '\\');
8159
8160 if (TempPosition < 0)
8161 {
8162 if (i + 2 == MenuNumber)
8163 return true;
8164 else
8165 return false;
8166 }
8167
8168 Position += 1 + TempPosition;
8169
8170 if (i + 2 >= MenuNumber)
8171 {
8172 Info[Position] = '\0';
8173 return true;
8174 }
8175 }
8176
8177 return false;
8178}
8179
8180public DisplayTimedMapsMenu2FromInfo(client, String:Info[])
8181{
8182 if (!TimedMapsMenuInfoMarker(Info, 2))
8183 {
8184 cmd_TimedMaps(client, 0);
8185 return;
8186 }
8187
8188 strcopy(MapTimingMenuInfo[client], MAX_LINE_WIDTH, Info);
8189
8190 new Gamemode = StringToInt(Info);
8191
8192 DisplayTimedMapsMenu2(client, Gamemode);
8193}
8194
8195public DisplayTimedMapsMenu2(client, Gamemode)
8196{
8197 decl String:query[256];
8198 Format(query, sizeof(query), "SELECT DISTINCT tm.gamemode, tm.map FROM %stimedmaps AS tm INNER JOIN %splayers AS p ON tm.steamid = p.steamid WHERE tm.gamemode = %i ORDER BY tm.map ASC", DbPrefix, DbPrefix, Gamemode);
8199 SQL_TQuery(db, CreateTimedMapsMenu2, query, client);
8200}
8201
8202public CreateTimedMapsMenu2(Handle:owner, Handle:hndl, const String:error[], any:client)
8203{
8204 if (!client || hndl == INVALID_HANDLE)
8205 {
8206 LogError("CreateTimedMapsMenu2 failed! Reason: %s", error);
8207 return;
8208 }
8209
8210 if (SQL_GetRowCount(hndl) <= 0)
8211 {
8212 new Handle:TimedMapsPanel = CreatePanel();
8213 SetPanelTitle(TimedMapsPanel, "Timed Maps:");
8214
8215 DrawPanelText(TimedMapsPanel, "There are no recorded times for this gamemode!");
8216 DrawPanelItem(TimedMapsPanel, "Close");
8217
8218 SendPanelToClient(TimedMapsPanel, client, TimedMapsPanelHandler, 30);
8219 CloseHandle(TimedMapsPanel);
8220
8221 return;
8222 }
8223
8224 new Handle:menu = CreateMenu(Menu_CreateTimedMapsMenu2Hndl), Gamemode;
8225 decl String:Map[MAX_LINE_WIDTH], String:Info[MAX_LINE_WIDTH], String:CurrentMapName[MAX_LINE_WIDTH];
8226
8227 GetCurrentMap(CurrentMapName, sizeof(CurrentMapName));
8228
8229 SetMenuTitle(menu, "Timed Maps:");
8230 SetMenuExitBackButton(menu, true);
8231 SetMenuExitButton(menu, true);
8232
8233 while (SQL_FetchRow(hndl))
8234 {
8235 Gamemode = SQL_FetchInt(hndl, 0);
8236 SQL_FetchString(hndl, 1, Map, sizeof(Map));
8237
8238 Format(Info, sizeof(Info), "%i\\%s", Gamemode, Map);
8239
8240 if (CurrentGamemodeID == Gamemode && StrEqual(CurrentMapName, Map))
8241 StrCat(Map, sizeof(Map), TM_MENU_CURRENT);
8242
8243 AddMenuItem(menu, Info, Map);
8244 }
8245
8246 DisplayMenu(menu, client, 30);
8247}
8248
8249public Menu_CreateTimedMapsMenu2Hndl(Handle:menu, MenuAction:action, param1, param2)
8250{
8251 if (menu == INVALID_HANDLE)
8252 return;
8253
8254 if (action == MenuAction_End)
8255 CloseHandle(menu);
8256
8257 if (action == MenuAction_Cancel)
8258 {
8259 if (param2 == MenuCancel_ExitBack)
8260 cmd_TimedMaps(param1, 0);
8261
8262 return;
8263 }
8264
8265 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
8266 return;
8267
8268 decl String:Info[MAX_LINE_WIDTH];
8269 new bool:found = GetMenuItem(menu, param2, Info, sizeof(Info));
8270
8271 if (!found)
8272 return;
8273
8274 DisplayTimedMapsMenu3FromInfo(param1, Info);
8275}
8276
8277public DisplayTimedMapsMenu3FromInfo(client, String:Info[])
8278{
8279 if (!TimedMapsMenuInfoMarker(Info, 3))
8280 {
8281 cmd_TimedMaps(client, 0);
8282 return;
8283 }
8284
8285 strcopy(MapTimingMenuInfo[client], MAX_LINE_WIDTH, Info);
8286
8287 decl String:GamemodeInfo[2], String:Map[MAX_LINE_WIDTH];
8288
8289 strcopy(GamemodeInfo, sizeof(GamemodeInfo), Info);
8290 GamemodeInfo[1] = 0;
8291
8292 strcopy(Map, sizeof(Map), Info[2]);
8293
8294 DisplayTimedMapsMenu3(client, StringToInt(GamemodeInfo), Map);
8295}
8296
8297public DisplayTimedMapsMenu3(client, Gamemode, const String:Map[])
8298{
8299 new Handle:dp = CreateDataPack();
8300
8301 WritePackCell(dp, client);
8302 WritePackCell(dp, Gamemode);
8303 WritePackString(dp, Map);
8304
8305 decl String:SteamID[MAX_LINE_WIDTH];
8306 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
8307
8308 decl String:query[256];
8309 Format(query, sizeof(query), "SELECT tm.time FROM %stimedmaps AS tm INNER JOIN %splayers AS p ON tm.steamid = p.steamid WHERE tm.gamemode = %i AND tm.map = '%s' AND p.steamid = '%s'", DbPrefix, DbPrefix, Gamemode, Map, SteamID);
8310 SQL_TQuery(db, DisplayTimedMapsMenu3_2, query, dp);
8311}
8312
8313public DisplayTimedMapsMenu3_2(Handle:owner, Handle:hndl, const String:error[], any:dp)
8314{
8315 if (hndl == INVALID_HANDLE)
8316 {
8317 if (dp != INVALID_HANDLE)
8318 CloseHandle(dp);
8319
8320 LogError("DisplayTimedMapsMenu3_2 failed! Reason: %s", error);
8321 return;
8322 }
8323
8324 ResetPack(dp);
8325
8326 new client = ReadPackCell(dp);
8327 new Gamemode = ReadPackCell(dp);
8328 decl String:Map[MAX_LINE_WIDTH];
8329 ReadPackString(dp, Map, sizeof(Map));
8330
8331 CloseHandle(dp);
8332
8333 if (SQL_FetchRow(hndl))
8334 ClientMapTime[client] = SQL_FetchFloat(hndl, 0);
8335 else
8336 ClientMapTime[client] = 0.0;
8337
8338 decl String:query[256];
8339 Format(query, sizeof(query), "SELECT DISTINCT tm.gamemode, tm.map, tm.time FROM %stimedmaps AS tm INNER JOIN %splayers AS p ON tm.steamid = p.steamid WHERE tm.gamemode = %i AND tm.map = '%s' ORDER BY tm.time %s", DbPrefix, DbPrefix, Gamemode, Map, (Gamemode == GAMEMODE_SURVIVAL ? "DESC" : "ASC"));
8340 SQL_TQuery(db, CreateTimedMapsMenu3, query, client);
8341}
8342
8343public CreateTimedMapsMenu3(Handle:owner, Handle:hndl, const String:error[], any:client)
8344{
8345 if (!client || hndl == INVALID_HANDLE)
8346 {
8347 LogError("CreateTimedMapsMenu3 failed! Reason: %s", error);
8348 return;
8349 }
8350
8351 if (SQL_GetRowCount(hndl) <= 0)
8352 {
8353 new Handle:TimedMapsPanel = CreatePanel();
8354 SetPanelTitle(TimedMapsPanel, "Timed Maps:");
8355
8356 DrawPanelText(TimedMapsPanel, "There are no recorded times for this map!");
8357 DrawPanelItem(TimedMapsPanel, "Close");
8358
8359 SendPanelToClient(TimedMapsPanel, client, TimedMapsPanelHandler, 30);
8360 CloseHandle(TimedMapsPanel);
8361
8362 return;
8363 }
8364
8365 new Handle:menu = CreateMenu(Menu_CreateTimedMapsMenu3Hndl), Float:MapTime;
8366 decl String:Map[MAX_LINE_WIDTH], String:Info[MAX_LINE_WIDTH], String:Value[MAX_LINE_WIDTH];
8367
8368 SetMenuTitle(menu, "Timed Maps:");
8369 SetMenuExitBackButton(menu, true);
8370 SetMenuExitButton(menu, true);
8371
8372 while (SQL_FetchRow(hndl))
8373 {
8374 SQL_FetchString(hndl, 1, Map, sizeof(Map));
8375 MapTime = SQL_FetchFloat(hndl, 2);
8376
8377 SetTimeLabel(MapTime, Value, sizeof(Value));
8378
8379 Format(Info, sizeof(Info), "%i\\%s\\%f", SQL_FetchInt(hndl, 0), Map, MapTime);
8380
8381 if (ClientMapTime[client] > 0.0 && ClientMapTime[client] == MapTime)
8382 StrCat(Value, sizeof(Value), TM_MENU_CURRENT);
8383
8384 AddMenuItem(menu, Info, Value);
8385 }
8386
8387 DisplayMenu(menu, client, 30);
8388}
8389
8390public Menu_CreateTimedMapsMenu3Hndl(Handle:menu, MenuAction:action, param1, param2)
8391{
8392 if (menu == INVALID_HANDLE)
8393 return;
8394
8395 if (action == MenuAction_End)
8396 CloseHandle(menu);
8397
8398 if (action == MenuAction_Cancel)
8399 {
8400 if (param2 == MenuCancel_ExitBack)
8401 DisplayTimedMapsMenu2FromInfo(param1, MapTimingMenuInfo[param1]);
8402
8403 return;
8404 }
8405
8406 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
8407 return;
8408
8409 decl String:Info[MAX_LINE_WIDTH];
8410 new bool:found = GetMenuItem(menu, param2, Info, sizeof(Info));
8411
8412 if (!found)
8413 return;
8414
8415 DisplayTimedMapsMenu4FromInfo(param1, Info);
8416}
8417
8418public DisplayTimedMapsMenu4FromInfo(client, String:Info[])
8419{
8420 if (!TimedMapsMenuInfoMarker(Info, 4))
8421 {
8422 cmd_TimedMaps(client, 0);
8423 return;
8424 }
8425
8426 strcopy(MapTimingMenuInfo[client], MAX_LINE_WIDTH, Info);
8427
8428 decl String:GamemodeInfo[2], String:Map[MAX_LINE_WIDTH];
8429
8430 strcopy(GamemodeInfo, sizeof(GamemodeInfo), Info);
8431 GamemodeInfo[1] = 0;
8432
8433 new Position = FindCharInString(Info[2], '\\');
8434
8435 if (Position < 0)
8436 {
8437 LogError("Timed Maps menu 4 error: Info = \"%s\"", Info);
8438 return;
8439 }
8440
8441 Position += 2;
8442
8443 strcopy(Map, sizeof(Map), Info[2]);
8444 Map[Position - 2] = '\0';
8445
8446 decl String:MapTime[MAX_LINE_WIDTH];
8447 strcopy(MapTime, sizeof(MapTime), Info[Position + 1]);
8448
8449 DisplayTimedMapsMenu4(client, StringToInt(GamemodeInfo), Map, StringToFloat(MapTime));
8450}
8451
8452public DisplayTimedMapsMenu4(client, Gamemode, const String:Map[], Float:MapTime)
8453{
8454 decl String:query[256];
8455 Format(query, sizeof(query), "SELECT tm.steamid, p.name FROM %stimedmaps AS tm INNER JOIN %splayers AS p ON tm.steamid = p.steamid WHERE tm.gamemode = %i AND tm.map = '%s' AND tm.time = %f ORDER BY p.name ASC", DbPrefix, DbPrefix, Gamemode, Map, MapTime);
8456 SQL_TQuery(db, CreateTimedMapsMenu4, query, client);
8457}
8458
8459public CreateTimedMapsMenu4(Handle:owner, Handle:hndl, const String:error[], any:client)
8460{
8461 if (!client || hndl == INVALID_HANDLE)
8462 {
8463 LogError("CreateTimedMapsMenu4 failed! Reason: %s", error);
8464 return;
8465 }
8466
8467 new Handle:menu = CreateMenu(Menu_CreateTimedMapsMenu4Hndl);
8468
8469 decl String:Name[32], String:SteamID[MAX_LINE_WIDTH];
8470
8471 SetMenuTitle(menu, "Timed Maps:");
8472 SetMenuExitBackButton(menu, true);
8473 SetMenuExitButton(menu, true);
8474
8475 while (SQL_FetchRow(hndl))
8476 {
8477 SQL_FetchString(hndl, 0, SteamID, sizeof(SteamID));
8478 SQL_FetchString(hndl, 1, Name, sizeof(Name));
8479
8480 ReplaceString(Name, sizeof(Name), "<", "<");
8481 ReplaceString(Name, sizeof(Name), ">", ">");
8482 ReplaceString(Name, sizeof(Name), "%", "%");
8483 ReplaceString(Name, sizeof(Name), "=", "=");
8484 ReplaceString(Name, sizeof(Name), "*", "*");
8485
8486 AddMenuItem(menu, SteamID, Name);
8487 }
8488
8489 DisplayMenu(menu, client, 30);
8490}
8491
8492public Menu_CreateTimedMapsMenu4Hndl(Handle:menu, MenuAction:action, param1, param2)
8493{
8494 if (menu == INVALID_HANDLE)
8495 return;
8496
8497 if (action == MenuAction_End)
8498 CloseHandle(menu);
8499
8500 if (action == MenuAction_Cancel)
8501 {
8502 if (param2 == MenuCancel_ExitBack)
8503 DisplayTimedMapsMenu3FromInfo(param1, MapTimingMenuInfo[param1]);
8504
8505 return;
8506 }
8507
8508 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
8509 return;
8510
8511 decl String:SteamID[MAX_LINE_WIDTH];
8512 new bool:found = GetMenuItem(menu, param2, SteamID, sizeof(SteamID));
8513
8514 if (!found)
8515 return;
8516
8517 QueryClientStatsSteamID(param1, SteamID, CM_RANK);
8518}
8519
8520public CreatePPMMenu(Handle:owner, Handle:hndl, const String:error[], any:client)
8521{
8522 if (!client || hndl == INVALID_HANDLE)
8523 {
8524 LogError("CreatePPMMenu failed! Reason: %s", error);
8525 return;
8526 }
8527
8528 decl String:SteamID[MAX_LINE_WIDTH];
8529 new Handle:menu = CreateMenu(Menu_CreateRanksMenuHandler);
8530
8531 decl String:Name[32], String:DisplayName[MAX_LINE_WIDTH];
8532
8533 SetMenuTitle(menu, "Player PPM:");
8534 SetMenuExitBackButton(menu, false);
8535 SetMenuExitButton(menu, true);
8536
8537 while (SQL_FetchRow(hndl))
8538 {
8539 SQL_FetchString(hndl, 0, SteamID, sizeof(SteamID));
8540 SQL_FetchString(hndl, 1, Name, sizeof(Name));
8541
8542 ReplaceString(Name, sizeof(Name), "<", "<");
8543 ReplaceString(Name, sizeof(Name), ">", ">");
8544 ReplaceString(Name, sizeof(Name), "%", "%");
8545 ReplaceString(Name, sizeof(Name), "=", "=");
8546 ReplaceString(Name, sizeof(Name), "*", "*");
8547
8548 Format(DisplayName, sizeof(DisplayName), "%s (PPM: %.2f)", Name, SQL_FetchFloat(hndl, 2));
8549
8550 AddMenuItem(menu, SteamID, DisplayName);
8551 }
8552
8553 DisplayMenu(menu, client, 30);
8554}
8555
8556public Menu_CreateRanksMenuHandler(Handle:menu, MenuAction:action, param1, param2)
8557{
8558 if (menu == INVALID_HANDLE)
8559 return;
8560
8561 if (action == MenuAction_End)
8562 CloseHandle(menu);
8563
8564 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
8565 return;
8566
8567 decl String:SteamID[MAX_LINE_WIDTH];
8568 new bool:found = GetMenuItem(menu, param2, SteamID, sizeof(SteamID));
8569
8570 if (!found)
8571 return;
8572
8573 QueryClientStatsSteamID(param1, SteamID, CM_RANK);
8574}
8575
8576public Menu_CreateRankMenuHandler(Handle:menu, MenuAction:action, param1, param2)
8577{
8578 if (menu == INVALID_HANDLE)
8579 return;
8580
8581 if (action == MenuAction_End)
8582 CloseHandle(menu);
8583
8584 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
8585 return;
8586
8587 decl String:Info[MAX_LINE_WIDTH];
8588 new bool:found = GetMenuItem(menu, param2, Info, sizeof(Info));
8589
8590 if (!found)
8591 return;
8592
8593 if (strcmp(Info, "rankabout", false) == 0)
8594 {
8595 DisplayAboutPanel(param1);
8596 return;
8597 }
8598
8599 else if (strcmp(Info, "showsettings", false) == 0)
8600 {
8601 DisplaySettingsPanel(param1);
8602 return;
8603 }
8604
8605 HandleCommands(param1, Info);
8606}
8607
8608// Send the RANKABOUT panel to the client's display.
8609public DisplayAboutPanel(client)
8610{
8611 decl String:Value[MAX_LINE_WIDTH];
8612
8613 new Handle:panel = CreatePanel();
8614
8615 Format(Value, sizeof(Value), "About %s:", PLUGIN_NAME);
8616 SetPanelTitle(panel, Value);
8617
8618 Format(Value, sizeof(Value), "Version: %s", PLUGIN_VERSION);
8619 DrawPanelText(panel, Value);
8620
8621 Format(Value, sizeof(Value), "Author: %s", "Mikko Andersson (muukis)");
8622 DrawPanelText(panel, Value);
8623
8624 Format(Value, sizeof(Value), "Description: %s", "Record player statistics.");
8625 DrawPanelText(panel, Value);
8626
8627 DrawPanelItem(panel, "Back");
8628 DrawPanelItem(panel, "Close");
8629
8630 SendPanelToClient(panel, client, AboutPanelHandler, 30);
8631 CloseHandle(panel);
8632}
8633
8634// Send the RANKABOUT panel to the client's display.
8635public DisplaySettingsPanel(client)
8636{
8637 decl String:Value[MAX_LINE_WIDTH];
8638
8639 new Handle:panel = CreatePanel();
8640
8641 Format(Value, sizeof(Value), "%s Settings:", PLUGIN_NAME);
8642 SetPanelTitle(panel, Value);
8643
8644 DrawPanelItem(panel, (ClientRankMute[client] ? "Unmute (Currently: Muted)" : "Mute (Currently: Not muted)"));
8645
8646 DrawPanelItem(panel, "Back");
8647 DrawPanelItem(panel, "Close");
8648
8649 SendPanelToClient(panel, client, SettingsPanelHandler, 30);
8650 CloseHandle(panel);
8651}
8652
8653// Send the TOP10 panel to the client's display.
8654public DisplayTop10(Handle:owner, Handle:hndl, const String:error[], any:client)
8655{
8656 if (!client || hndl == INVALID_HANDLE)
8657 {
8658 LogError("DisplayTop10 failed! Reason: %s", error);
8659 return;
8660 }
8661
8662 new String:Name[32];
8663
8664 new Handle:Top10Panel = CreatePanel();
8665 SetPanelTitle(Top10Panel, "Top 10 Players");
8666
8667 while (SQL_FetchRow(hndl))
8668 {
8669 SQL_FetchString(hndl, 0, Name, sizeof(Name));
8670
8671 ReplaceString(Name, sizeof(Name), "<", "<");
8672 ReplaceString(Name, sizeof(Name), ">", ">");
8673 ReplaceString(Name, sizeof(Name), "%", "%");
8674 ReplaceString(Name, sizeof(Name), "=", "=");
8675 ReplaceString(Name, sizeof(Name), "*", "*");
8676
8677 DrawPanelItem(Top10Panel, Name);
8678 }
8679
8680 SendPanelToClient(Top10Panel, client, Top10PanelHandler, 30);
8681 CloseHandle(Top10Panel);
8682}
8683
8684// Send the TOP10PPM panel to the client's display.
8685public DisplayTop10PPM(Handle:owner, Handle:hndl, const String:error[], any:client)
8686{
8687 if (!client || hndl == INVALID_HANDLE)
8688 {
8689 LogError("DisplayTop10PPM failed! Reason: %s", error);
8690 return;
8691 }
8692
8693 decl String:Name[32], String:Disp[MAX_LINE_WIDTH];
8694
8695 new Handle:TopPPMPanel = CreatePanel();
8696 SetPanelTitle(TopPPMPanel, "Top 10 PPM Players");
8697
8698 while (SQL_FetchRow(hndl))
8699 {
8700 SQL_FetchString(hndl, 0, Name, sizeof(Name));
8701
8702 ReplaceString(Name, sizeof(Name), "<", "<");
8703 ReplaceString(Name, sizeof(Name), ">", ">");
8704 ReplaceString(Name, sizeof(Name), "%", "%");
8705 ReplaceString(Name, sizeof(Name), "=", "=");
8706 ReplaceString(Name, sizeof(Name), "*", "*");
8707
8708 Format(Disp, sizeof(Disp), "%s (PPM: %.2f)", Name, SQL_FetchFloat(hndl, 1));
8709
8710 DrawPanelItem(TopPPMPanel, Disp);
8711 }
8712
8713 SendPanelToClient(TopPPMPanel, client, Top10PPMPanelHandler, 30);
8714 CloseHandle(TopPPMPanel);
8715}
8716
8717// Handler for RANK panel.
8718public RankPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8719{
8720}
8721
8722// Handler for NEXTRANK panel.
8723public NextRankPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8724{
8725 if (param2 == 1)
8726 QueryClientStats(param1, CM_NEXTRANKFULL);
8727}
8728
8729// Handler for NEXTRANK panel.
8730public NextRankFullPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8731{
8732}
8733
8734// Handler for TIMEDMAPS panel.
8735public TimedMapsPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8736{
8737}
8738
8739// Handler for RANKADMIN panel.
8740//public RankAdminPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8741//{
8742// if (action != MenuAction_Select)
8743// return;
8744//
8745// if (param2 == 1)
8746// DisplayClearPanel(param1);
8747// else if (param2 == 2)
8748// DisplayYesNoPanel(param1, "Do you really want to clear the player stats?", ClearPlayersPanelHandler);
8749// else if (param2 == 3)
8750// DisplayYesNoPanel(param1, "Do you really want to clear the map stats?", ClearMapsPanelHandler);
8751// else if (param2 == 4)
8752// DisplayYesNoPanel(param1, "Do you really want to clear all stats?", ClearAllPanelHandler);
8753//}
8754
8755// Handler for RANKADMIN panel.
8756public ClearPlayersPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8757{
8758 if (action != MenuAction_Select)
8759 return;
8760
8761 if (param2 == 1)
8762 {
8763 ClearStatsPlayers(param1);
8764 StatsPrintToChatPreFormatted(param1, "All player stats cleared!");
8765 }
8766 //else if (param2 == 2)
8767 // cmd_RankAdmin(param1, 0);
8768}
8769
8770// Handler for RANKADMIN panel.
8771public ClearMapsPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8772{
8773 if (action != MenuAction_Select)
8774 return;
8775
8776 if (param2 == 1)
8777 {
8778 ClearStatsMaps(param1);
8779 StatsPrintToChatPreFormatted(param1, "All map stats cleared!");
8780 }
8781 //else if (param2 == 2)
8782 // cmd_RankAdmin(param1, 0);
8783}
8784
8785// Handler for RANKADMIN panel.
8786public ClearAllPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8787{
8788 if (action != MenuAction_Select)
8789 return;
8790
8791 if (param2 == 1)
8792 {
8793 ClearStatsAll(param1);
8794 StatsPrintToChatPreFormatted(param1, "All stats cleared!");
8795 }
8796 //else if (param2 == 2)
8797 // cmd_RankAdmin(param1, 0);
8798}
8799
8800// Handler for RANKADMIN panel.
8801public CleanPlayersPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8802{
8803 if (action != MenuAction_Select)
8804 return;
8805
8806 if (param2 == 1)
8807 {
8808 new LastOnTimeMonths = GetConVarInt(cvar_AdminPlayerCleanLastOnTime);
8809 new PlaytimeMinutes = GetConVarInt(cvar_AdminPlayerCleanPlatime);
8810
8811 if (LastOnTimeMonths || PlaytimeMinutes)
8812 {
8813 new bool:Success = true;
8814
8815 if (LastOnTimeMonths)
8816 Success &= DoFastQuery(param1, "DELETE FROM %splayers WHERE lastontime < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL %i MONTH))", DbPrefix, LastOnTimeMonths);
8817
8818 if (PlaytimeMinutes)
8819 Success &= DoFastQuery(param1, "DELETE FROM %splayers WHERE %s < %i AND lastontime < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 1 HOUR))", DbPrefix, DB_PLAYERS_TOTALPLAYTIME, PlaytimeMinutes);
8820
8821 if (Success)
8822 StatsPrintToChatPreFormatted(param1, "Player cleaning successful!");
8823 else
8824 StatsPrintToChatPreFormatted(param1, "Player cleaning failed!");
8825 }
8826 else
8827 StatsPrintToChatPreFormatted(param1, "Player cleaning is disabled by configurations!");
8828 }
8829 //else if (param2 == 2)
8830 // cmd_RankAdmin(param1, 0);
8831}
8832
8833// Handler for RANKADMIN panel.
8834public RemoveCustomMapsPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8835{
8836 if (action != MenuAction_Select)
8837 return;
8838
8839 if (param2 == 1)
8840 {
8841 if (DoFastQuery(param1, "DELETE FROM %smaps WHERE custom = 1", DbPrefix))
8842 StatsPrintToChatPreFormatted(param1, "All custom maps removed!");
8843 else
8844 StatsPrintToChatPreFormatted(param1, "Removing custom maps failed!");
8845 }
8846 //else if (param2 == 2)
8847 // cmd_RankAdmin(param1, 0);
8848}
8849
8850// Handler for RANKADMIN panel.
8851public ClearTMAllPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8852{
8853 if (action != MenuAction_Select)
8854 return;
8855
8856 if (param2 == 1)
8857 {
8858 if (DoFastQuery(param1, "DELETE FROM %stimedmaps", DbPrefix))
8859 StatsPrintToChatPreFormatted(param1, "All map timings removed!");
8860 else
8861 StatsPrintToChatPreFormatted(param1, "Removing map timings failed!");
8862 }
8863 //else if (param2 == 2)
8864 // cmd_RankAdmin(param1, 0);
8865}
8866
8867// Handler for RANKADMIN panel.
8868public ClearTMCoopPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8869{
8870 if (action != MenuAction_Select)
8871 return;
8872
8873 if (param2 == 1)
8874 {
8875 if (DoFastQuery(param1, "DELETE FROM %stimedmaps WHERE gamemode = %i", DbPrefix, GAMEMODE_COOP))
8876 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Coop successful!");
8877 else
8878 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Coop failed!");
8879 }
8880 //else if (param2 == 2)
8881 // cmd_RankAdmin(param1, 0);
8882}
8883
8884// Handler for RANKADMIN panel.
8885public ClearTMSurvivalPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8886{
8887 if (action != MenuAction_Select)
8888 return;
8889
8890 if (param2 == 1)
8891 {
8892 if (DoFastQuery(param1, "DELETE FROM %stimedmaps WHERE gamemode = %i", DbPrefix, GAMEMODE_SURVIVAL))
8893 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Survival successful!");
8894 else
8895 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Survival failed!");
8896 }
8897 //else if (param2 == 2)
8898 // cmd_RankAdmin(param1, 0);
8899}
8900
8901// Handler for RANKADMIN panel.
8902public ClearTMRealismPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8903{
8904 if (action != MenuAction_Select)
8905 return;
8906
8907 if (param2 == 1)
8908 {
8909 if (DoFastQuery(param1, "DELETE FROM %stimedmaps WHERE gamemode = %i", DbPrefix, GAMEMODE_REALISM))
8910 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Realism successful!");
8911 else
8912 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Realism failed!");
8913 }
8914 //else if (param2 == 2)
8915 // cmd_RankAdmin(param1, 0);
8916}
8917
8918// Handler for RANKADMIN panel.
8919public ClearTMMutationsPanelHandler(Handle:menu, MenuAction:action, param1, param2)
8920{
8921 if (action != MenuAction_Select)
8922 return;
8923
8924 if (param2 == 1)
8925 {
8926 if (DoFastQuery(param1, "DELETE FROM %stimedmaps WHERE gamemode = %i", DbPrefix, GAMEMODE_OTHERMUTATIONS))
8927 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Mutations successful!");
8928 else
8929 StatsPrintToChatPreFormatted(param1, "Clearing map timings for Mutations failed!");
8930 }
8931 //else if (param2 == 2)
8932 // cmd_RankAdmin(param1, 0);
8933}
8934
8935// Handler for RANKVOTE panel.
8936public RankVotePanelHandler(Handle:menu, MenuAction:action, param1, param2)
8937{
8938 if (action != MenuAction_Select || RankVoteTimer == INVALID_HANDLE || param1 <= 0 || IsClientBot(param1))
8939 return;
8940
8941 if (param2 == 1 || param2 == 2)
8942 {
8943 new team = GetClientTeam(param1);
8944
8945 if (team != TEAM_SURVIVORS && team != TEAM_INFECTED)
8946 return;
8947
8948 new OldPlayerRankVote = PlayerRankVote[param1];
8949
8950 if (param2 == 1)
8951 PlayerRankVote[param1] = RANKVOTE_YES;
8952 else if (param2 == 2)
8953 PlayerRankVote[param1] = RANKVOTE_NO;
8954
8955 new humans = 0, votes = 0, yesvotes = 0, novotes = 0, WinningVoteCount = 0;
8956
8957 CheckRankVotes(humans, votes, yesvotes, novotes, WinningVoteCount);
8958
8959 if (yesvotes >= WinningVoteCount || novotes >= WinningVoteCount)
8960 {
8961 if (RankVoteTimer != INVALID_HANDLE)
8962 {
8963 CloseHandle(RankVoteTimer);
8964 RankVoteTimer = INVALID_HANDLE;
8965 }
8966
8967 StatsPrintToChatAll("Vote to shuffle teams by player PPM \x03%s \x01with \x04%i (yes) against %i (no)\x01.", (yesvotes >= WinningVoteCount ? "PASSED" : "DID NOT PASS"), yesvotes, novotes);
8968
8969 if (yesvotes >= WinningVoteCount)
8970 CreateTimer(2.0, timer_ShuffleTeams);
8971 }
8972
8973 if (OldPlayerRankVote != RANKVOTE_NOVOTE)
8974 return;
8975
8976 decl String:Name[32];
8977 GetClientName(param1, Name, sizeof(Name));
8978
8979 StatsPrintToChatAll("\x05%s \x01voted. \x04%i/%i \x01players have voted.", Name, votes, humans);
8980 }
8981}
8982
8983CheckRankVotes(&Humans, &Votes, &YesVotes, &NoVotes, &WinningVoteCount)
8984{
8985 Humans = 0;
8986 Votes = 0;
8987 YesVotes = 0;
8988 NoVotes = 0;
8989 WinningVoteCount = 0;
8990
8991 new i, team, maxplayers = GetMaxClients();
8992
8993 for (i = 1; i <= maxplayers; i++)
8994 {
8995 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
8996 {
8997 team = GetClientTeam(i);
8998
8999 if (team == TEAM_SURVIVORS || team == TEAM_INFECTED)
9000 {
9001 Humans++;
9002
9003 if (PlayerRankVote[i] != RANKVOTE_NOVOTE)
9004 {
9005 Votes++;
9006
9007 if (PlayerRankVote[i] == RANKVOTE_YES)
9008 YesVotes++;
9009 }
9010 }
9011 }
9012 }
9013
9014 // More than half of the players are needed to vot YES for rankvote pass
9015 WinningVoteCount = RoundToNearest(float(Humans) / 2) + 1 - (Humans % 2);
9016 NoVotes = Votes - YesVotes;
9017}
9018
9019DisplayClearPanel(client, delay=30)
9020{
9021 if (!client)
9022 return;
9023
9024 //if (ClearPlayerMenu != INVALID_HANDLE)
9025 //{
9026 // CloseHandle(ClearPlayerMenu);
9027 // ClearPlayerMenu = INVALID_HANDLE;
9028 //}
9029
9030 new Handle:ClearPlayerMenu = CreateMenu(DisplayClearPanelHandler);
9031 new maxplayers = GetMaxClients();
9032 decl String:id[3], String:Name[32];
9033
9034 for (new i = 1; i <= maxplayers; i++)
9035 {
9036 if (IsClientBot(i) || !IsClientConnected(i) || !IsClientInGame(i))
9037 continue;
9038
9039 GetClientName(i, Name, sizeof(Name));
9040 IntToString(i, id, sizeof(id));
9041
9042 AddMenuItem(ClearPlayerMenu, id, Name);
9043 }
9044
9045 SetMenuTitle(ClearPlayerMenu, "Clear player stats:");
9046 DisplayMenu(ClearPlayerMenu, client, delay);
9047}
9048
9049// Handler for RANKADMIN panel.
9050public DisplayClearPanelHandler(Handle:menu, MenuAction:action, param1, param2)
9051{
9052 if (menu == INVALID_HANDLE)
9053 return;
9054
9055 if (action == MenuAction_End)
9056 CloseHandle(menu);
9057
9058 if (action != MenuAction_Select || param1 <= 0 || IsClientBot(param1))
9059 return;
9060
9061 decl String:id[3];
9062 new bool:found = GetMenuItem(menu, param2, id, sizeof(id));
9063
9064 if (!found)
9065 return;
9066
9067 new client = StringToInt(id);
9068
9069 decl String:SteamID[MAX_LINE_WIDTH];
9070 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
9071
9072 if (DoFastQuery(param1, "DELETE FROM %splayers WHERE steamid = '%s'", DbPrefix, SteamID))
9073 {
9074 DoFastQuery(param1, "DELETE FROM %stimedmaps WHERE steamid = '%s'", DbPrefix, SteamID);
9075
9076 ClientPoints[client] = 0;
9077 ClientRank[client] = 0;
9078
9079 decl String:Name[32];
9080 GetClientName(client, Name, sizeof(Name));
9081
9082 StatsPrintToChatPreFormatted(client, "Your player stats were cleared!");
9083 if (client != param1)
9084 StatsPrintToChat(param1, "Player \x05%s \x01stats cleared!", Name);
9085 }
9086 else
9087 StatsPrintToChatPreFormatted(param1, "Clearing player stats failed!");
9088}
9089
9090// Handler for RANKABOUT panel.
9091public AboutPanelHandler(Handle:menu, MenuAction:action, param1, param2)
9092{
9093 if (action == MenuAction_Select)
9094 {
9095 if (param2 == 1)
9096 cmd_ShowRankMenu(param1, 0);
9097 }
9098}
9099
9100// Handler for RANK SETTINGS panel.
9101public SettingsPanelHandler(Handle:menu, MenuAction:action, param1, param2)
9102{
9103 if (action == MenuAction_Select)
9104 {
9105 if (param2 == 1)
9106 cmd_ToggleClientRankMute(param1, 0);
9107 if (param2 == 2)
9108 cmd_ShowRankMenu(param1, 0);
9109 }
9110}
9111
9112// Handler for TOP10 panel.
9113public Top10PanelHandler(Handle:menu, MenuAction:action, param1, param2)
9114{
9115 if (action == MenuAction_Select)
9116 {
9117 if (param2 == 0)
9118 param2 = 10;
9119
9120 GetClientFromTop10(param1, param2 - 1);
9121 }
9122}
9123
9124// Handler for TOP10PPM panel.
9125public Top10PPMPanelHandler(Handle:menu, MenuAction:action, param1, param2)
9126{
9127 if (action == MenuAction_Select)
9128 {
9129 if (param2 == 0)
9130 param2 = 10;
9131
9132 GetClientFromTop10PPM(param1, param2 - 1);
9133 }
9134}
9135
9136/*
9137-----------------------------------------------------------------------------
9138Private functions
9139-----------------------------------------------------------------------------
9140*/
9141
9142HunterSmokerSave(Savior, Victim, BasePoints, AdvMult, ExpertMult, String:SaveFrom[], String:SQLField[])
9143{
9144 if (StatsDisabled())
9145 return;
9146
9147 if (IsClientBot(Savior) || IsClientBot(Victim))
9148 return;
9149
9150 new Mode = GetConVarInt(cvar_AnnounceMode);
9151
9152 decl String:SaviorName[MAX_LINE_WIDTH];
9153 GetClientName(Savior, SaviorName, sizeof(SaviorName));
9154 decl String:SaviorID[MAX_LINE_WIDTH];
9155 GetClientRankAuthString(Savior, SaviorID, sizeof(SaviorID));
9156
9157 decl String:VictimName[MAX_LINE_WIDTH];
9158 GetClientName(Victim, VictimName, sizeof(VictimName));
9159 decl String:VictimID[MAX_LINE_WIDTH];
9160 GetClientRankAuthString(Victim, VictimID, sizeof(VictimID));
9161
9162 if (StrEqual(SaviorID, VictimID))
9163 return;
9164
9165 new Score = ModifyScoreDifficulty(BasePoints, AdvMult, ExpertMult, TEAM_SURVIVORS);
9166 decl String:UpdatePoints[32];
9167
9168 switch (CurrentGamemodeID)
9169 {
9170 case GAMEMODE_VERSUS:
9171 {
9172 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
9173 }
9174 case GAMEMODE_REALISM:
9175 {
9176 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
9177 }
9178 case GAMEMODE_SURVIVAL:
9179 {
9180 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
9181 }
9182 case GAMEMODE_SCAVENGE:
9183 {
9184 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
9185 }
9186 case GAMEMODE_REALISMVERSUS:
9187 {
9188 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
9189 }
9190 case GAMEMODE_OTHERMUTATIONS:
9191 {
9192 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
9193 }
9194 default:
9195 {
9196 Format(UpdatePoints, sizeof(UpdatePoints), "points");
9197 }
9198 }
9199
9200 decl String:query[1024];
9201 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i, %s = %s + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, SQLField, SQLField, SaviorID);
9202 SendSQLUpdate(query);
9203
9204 if (Score <= 0)
9205 return;
9206
9207 if (Mode)
9208 StatsPrintToChat(Savior, "You have earned \x04%i \x01points for saving \x05%s\x01 from \x04%s\x01!", Score, VictimName, SaveFrom);
9209
9210 UpdateMapStat("points", Score);
9211 AddScore(Savior, Score);
9212}
9213
9214bool:IsClientBot(client)
9215{
9216 if (client == 0 || !IsClientConnected(client) || IsFakeClient(client))
9217 {
9218 return true;
9219 }
9220
9221 decl String:SteamID[MAX_LINE_WIDTH];
9222 GetClientRankAuthString(client, SteamID, sizeof(SteamID));
9223
9224 if (StrEqual(SteamID, "BOT", false))
9225 {
9226 return true;
9227 }
9228
9229 return false;
9230}
9231
9232ModifyScoreRealism(BaseScore, ClientTeam, bool:ToCeil=true)
9233{
9234 if (ServerVersion != SERVER_VERSION_L4D1)
9235 {
9236 decl Handle:Multiplier;
9237
9238 if (CurrentGamemodeID == GAMEMODE_REALISM)
9239 Multiplier = cvar_RealismMultiplier;
9240 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
9241 {
9242 if (ClientTeam == TEAM_SURVIVORS)
9243 Multiplier = cvar_RealismVersusSurMultiplier;
9244 else if(ClientTeam == TEAM_INFECTED)
9245 Multiplier = cvar_RealismVersusInfMultiplier;
9246 else
9247 return BaseScore;
9248 }
9249 else
9250 return BaseScore;
9251
9252 if (ToCeil)
9253 BaseScore = RoundToCeil(GetConVarFloat(Multiplier) * BaseScore);
9254 else
9255 BaseScore = RoundToFloor(GetConVarFloat(Multiplier) * BaseScore);
9256 }
9257
9258 return BaseScore;
9259}
9260
9261ModifyScoreDifficultyFloatNR(BaseScore, Float:AdvMult, Float:ExpMult, ClientTeam, bool:ToCeil=true)
9262{
9263 return ModifyScoreDifficultyFloat(BaseScore, AdvMult, ExpMult, ClientTeam, ToCeil, false);
9264}
9265
9266ModifyScoreDifficultyFloat(BaseScore, Float:AdvMult, Float:ExpMult, ClientTeam, bool:ToCeil=true, bool:Reduction = true)
9267{
9268 if (BaseScore <= 0)
9269 {
9270 return 0;
9271 }
9272
9273 decl String:Difficulty[MAX_LINE_WIDTH];
9274 GetConVarString(cvar_Difficulty, Difficulty, sizeof(Difficulty));
9275
9276 new Float:ModifiedScore;
9277
9278 if (StrEqual(Difficulty, "Hard", false)) ModifiedScore = BaseScore * AdvMult;
9279 else if (StrEqual(Difficulty, "Impossible", false)) ModifiedScore = BaseScore * ExpMult;
9280 else return ModifyScoreRealism(BaseScore, ClientTeam);
9281
9282 new Score = 0;
9283 if (ToCeil)
9284 {
9285 Score = RoundToCeil(ModifiedScore);
9286 }
9287 else
9288 {
9289 Score = RoundToFloor(ModifiedScore);
9290 }
9291
9292 if (ClientTeam == TEAM_SURVIVORS && Reduction)
9293 {
9294 Score = GetMedkitPointReductionScore(Score);
9295 }
9296
9297 return ModifyScoreRealism(Score, ClientTeam, ToCeil);
9298}
9299
9300// Score modifier without point reduction. Usable for minus points.
9301
9302ModifyScoreDifficultyNR(BaseScore, AdvMult, ExpMult, ClientTeam)
9303{
9304 return ModifyScoreDifficulty(BaseScore, AdvMult, ExpMult, ClientTeam, false);
9305}
9306
9307ModifyScoreDifficulty(BaseScore, AdvMult, ExpMult, ClientTeam, bool:Reduction = true)
9308{
9309 decl String:Difficulty[MAX_LINE_WIDTH];
9310 GetConVarString(cvar_Difficulty, Difficulty, sizeof(Difficulty));
9311
9312 if (StrEqual(Difficulty, "hard", false)) BaseScore = BaseScore * AdvMult;
9313 if (StrEqual(Difficulty, "impossible", false)) BaseScore = BaseScore * ExpMult;
9314
9315 if (ClientTeam == TEAM_SURVIVORS && Reduction)
9316 {
9317 BaseScore = GetMedkitPointReductionScore(BaseScore);
9318 }
9319
9320 return ModifyScoreRealism(BaseScore, ClientTeam);
9321}
9322
9323IsDifficultyEasy()
9324{
9325 decl String:Difficulty[MAX_LINE_WIDTH];
9326 GetConVarString(cvar_Difficulty, Difficulty, sizeof(Difficulty));
9327
9328 if (StrEqual(Difficulty, "easy", false))
9329 {
9330 return true;
9331 }
9332
9333 return false;
9334}
9335
9336InvalidGameMode()
9337{
9338 // Currently will always return False in Survival and Versus gamemodes.
9339 // This will be removed in a future version when stats for those versions work.
9340
9341 if (CurrentGamemodeID == GAMEMODE_COOP && GetConVarBool(cvar_EnableCoop))
9342 {
9343 return false;
9344 }
9345 else if (CurrentGamemodeID == GAMEMODE_SURVIVAL && GetConVarBool(cvar_EnableSv))
9346 {
9347 return false;
9348 }
9349 else if (CurrentGamemodeID == GAMEMODE_VERSUS && GetConVarBool(cvar_EnableVersus))
9350 {
9351 return false;
9352 }
9353 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE && GetConVarBool(cvar_EnableScavenge))
9354 {
9355 return false;
9356 }
9357 else if (CurrentGamemodeID == GAMEMODE_REALISM && GetConVarBool(cvar_EnableRealism))
9358 {
9359 return false;
9360 }
9361 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS && GetConVarBool(cvar_EnableRealismVersus))
9362 {
9363 return false;
9364 }
9365 else if (CurrentGamemodeID == GAMEMODE_OTHERMUTATIONS && GetConVarBool(cvar_EnableMutations))
9366 {
9367 return false;
9368 }
9369
9370 return true;
9371}
9372
9373bool:CheckHumans()
9374{
9375 new MinHumans = GetConVarInt(cvar_HumansNeeded);
9376 new Humans = 0;
9377 new maxplayers = GetMaxClients();
9378
9379 for (new i = 1; i <= maxplayers; i++)
9380 {
9381 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
9382 {
9383 Humans++;
9384 }
9385 }
9386
9387 if (Humans < MinHumans)
9388 {
9389 return true;
9390 }
9391 else
9392 {
9393 return false;
9394 }
9395}
9396
9397ResetInfVars()
9398{
9399 new i;
9400
9401 // Reset all Infected variables
9402 for (i = 0; i < MAXPLAYERS + 1; i++)
9403 {
9404 BoomerHitCounter[i] = 0;
9405 BoomerVomitUpdated[i] = false;
9406 InfectedDamageCounter[i] = 0;
9407 SmokerDamageCounter[i] = 0;
9408 SpitterDamageCounter[i] = 0;
9409 JockeyDamageCounter[i] = 0;
9410 ChargerDamageCounter[i] = 0;
9411 ChargerImpactCounter[i] = 0;
9412 TankPointsCounter[i] = 0;
9413 TankDamageCounter[i] = 0;
9414 ClientInfectedType[i] = 0;
9415 TankSurvivorKillCounter[i] = 0;
9416 TankDamageTotalCounter[i] = 0;
9417 ChargerCarryVictim[i] = 0;
9418 ChargerPlummelVictim[i] = 0;
9419 JockeyVictim[i] = 0;
9420 JockeyRideStartTime[i] = 0;
9421
9422 PlayerBlinded[i][0] = 0;
9423 PlayerBlinded[i][1] = 0;
9424 PlayerParalyzed[i][0] = 0;
9425 PlayerParalyzed[i][1] = 0;
9426 PlayerLunged[i][0] = 0;
9427 PlayerLunged[i][1] = 0;
9428 PlayerPlummeled[i][0] = 0;
9429 PlayerPlummeled[i][1] = 0;
9430 PlayerCarried[i][0] = 0;
9431 PlayerCarried[i][1] = 0;
9432 PlayerJockied[i][0] = 0;
9433 PlayerJockied[i][1] = 0;
9434
9435 TimerBoomerPerfectCheck[i] = INVALID_HANDLE;
9436 TimerInfectedDamageCheck[i] = INVALID_HANDLE;
9437
9438 TimerProtectedFriendly[i] = INVALID_HANDLE;
9439 ProtectedFriendlyCounter[i] = 0;
9440
9441 if (ChargerImpactCounterTimer[i] != INVALID_HANDLE)
9442 {
9443 CloseHandle(ChargerImpactCounterTimer[i]);
9444 }
9445
9446 ChargerImpactCounterTimer[i] = INVALID_HANDLE;
9447 }
9448}
9449
9450ResetVars()
9451{
9452 ClearTrie(FriendlyFireDamageTrie);
9453 ClearTrie(PlayerRankVoteTrie);
9454
9455 PlayerVomited = false;
9456 PlayerVomitedIncap = false;
9457 PanicEvent = false;
9458 PanicEventIncap = false;
9459 CampaignOver = false;
9460 WitchExists = false;
9461 WitchDisturb = false;
9462 MedkitsUsedCounter = 0;
9463
9464 // Reset kill/point score timer amount
9465 CreateTimer(1.0, InitPlayers);
9466
9467 TankCount = 0;
9468
9469 new i, j, maxplayers = GetMaxClients();
9470 for (i = 1; i <= maxplayers; i++)
9471 {
9472 AnnounceCounter[i] = 0;
9473 CurrentPoints[i] = 0;
9474 ClientRankMute[i] = false;
9475 }
9476
9477 for (i = 0; i < MAXPLAYERS + 1; i++)
9478 {
9479 if (TimerRankChangeCheck[i] != INVALID_HANDLE)
9480 CloseHandle(TimerRankChangeCheck[i]);
9481
9482 TimerRankChangeCheck[i] = INVALID_HANDLE;
9483
9484 for (j = 0; j < MAXPLAYERS + 1; j++)
9485 {
9486 FriendlyFireCooldown[i][j] = false;
9487 FriendlyFireTimer[i][j] = INVALID_HANDLE;
9488 }
9489
9490 if (MeleeKillTimer[i] != INVALID_HANDLE)
9491 CloseHandle(MeleeKillTimer[i]);
9492 MeleeKillTimer[i] = INVALID_HANDLE;
9493 MeleeKillCounter[i] = 0;
9494
9495 PostAdminCheckRetryCounter[i] = 0;
9496 }
9497
9498 ResetInfVars();
9499}
9500
9501public ResetRankChangeCheck()
9502{
9503 new maxplayers = GetMaxClients();
9504
9505 for (new i = 1; i <= maxplayers; i++)
9506 StartRankChangeCheck(i);
9507}
9508
9509public StartRankChangeCheck(Client)
9510{
9511 if (TimerRankChangeCheck[Client] != INVALID_HANDLE)
9512 CloseHandle(TimerRankChangeCheck[Client]);
9513
9514 TimerRankChangeCheck[Client] = INVALID_HANDLE;
9515
9516 if (Client == 0 || IsClientBot(Client))
9517 return;
9518
9519 RankChangeFirstCheck[Client] = true;
9520 DoShowRankChange(Client);
9521 TimerRankChangeCheck[Client] = CreateTimer(GetConVarFloat(cvar_AnnounceRankChangeIVal), timer_ShowRankChange, Client, TIMER_REPEAT);
9522}
9523
9524StatsDisabled(bool:MapCheck = false)
9525{
9526 if (!GetConVarBool(cvar_Enable))
9527 return true;
9528
9529 if (InvalidGameMode())
9530 return true;
9531
9532 if (!MapCheck && IsDifficultyEasy())
9533 return true;
9534
9535 if (!MapCheck && CheckHumans())
9536 return true;
9537
9538 if (!MapCheck && GetConVarBool(cvar_Cheats))
9539 return true;
9540
9541 if (db == INVALID_HANDLE)
9542 return true;
9543
9544 return false;
9545}
9546
9547// Check that player the score is in the map score limits and return the value that is addable.
9548
9549public AddScore(Client, Score)
9550{
9551 // ToDo: use cvar_MaxPoints to check if the score is within the map limits
9552 CurrentPoints[Client] += Score;
9553
9554 //if (GetConVarBool(cvar_AnnounceRankChange))
9555 //{
9556 //}
9557
9558 return Score;
9559}
9560
9561public UpdateSmokerDamage(Client, Damage)
9562{
9563 if (Client <= 0 || Damage <= 0 || IsClientBot(Client))
9564 return;
9565
9566 decl String:iID[MAX_LINE_WIDTH];
9567 GetClientRankAuthString(Client, iID, sizeof(iID));
9568
9569 decl String:query[1024];
9570 Format(query, sizeof(query), "UPDATE %splayers SET infected_smoker_damage = infected_smoker_damage + %i WHERE steamid = '%s'", DbPrefix, Damage, iID);
9571 SendSQLUpdate(query);
9572
9573 UpdateMapStat("infected_smoker_damage", Damage);
9574}
9575
9576public UpdateSpitterDamage(Client, Damage)
9577{
9578 if (Client <= 0 || Damage <= 0 || IsClientBot(Client))
9579 return;
9580
9581 decl String:iID[MAX_LINE_WIDTH];
9582 GetClientRankAuthString(Client, iID, sizeof(iID));
9583
9584 decl String:query[1024];
9585 Format(query, sizeof(query), "UPDATE %splayers SET infected_spitter_damage = infected_spitter_damage + %i WHERE steamid = '%s'", DbPrefix, Damage, iID);
9586 SendSQLUpdate(query);
9587
9588 UpdateMapStat("infected_spitter_damage", Damage);
9589}
9590
9591public UpdateJockeyDamage(Client, Damage)
9592{
9593 if (Client <= 0 || Damage <= 0 || IsClientBot(Client))
9594 return;
9595
9596 decl String:iID[MAX_LINE_WIDTH];
9597 GetClientRankAuthString(Client, iID, sizeof(iID));
9598
9599 decl String:query[1024];
9600 Format(query, sizeof(query), "UPDATE %splayers SET infected_jockey_damage = infected_jockey_damage + %i WHERE steamid = '%s'", DbPrefix, Damage, iID);
9601 SendSQLUpdate(query);
9602
9603 UpdateMapStat("infected_jockey_damage", Damage);
9604}
9605
9606UpdateJockeyRideLength(Client, Float:RideLength=-1.0)
9607{
9608 if (Client <= 0 || RideLength == 0 || IsClientBot(Client) || (RideLength < 0 && JockeyRideStartTime[Client] <= 0))
9609 return;
9610
9611 if (RideLength < 0)
9612 RideLength = float(GetTime() - JockeyRideStartTime[Client]);
9613
9614 decl String:iID[MAX_LINE_WIDTH];
9615 GetClientRankAuthString(Client, iID, sizeof(iID));
9616
9617 decl String:query[1024];
9618 Format(query, sizeof(query), "UPDATE %splayers SET infected_jockey_ridetime = infected_jockey_ridetime + %f WHERE steamid = '%s'", DbPrefix, RideLength, iID);
9619 SendSQLUpdate(query);
9620
9621 UpdateMapStatFloat("infected_jockey_ridetime", RideLength);
9622}
9623
9624public UpdateChargerDamage(Client, Damage)
9625{
9626 if (Client <= 0 || Damage <= 0 || IsClientBot(Client))
9627 return;
9628
9629 decl String:iID[MAX_LINE_WIDTH];
9630 GetClientRankAuthString(Client, iID, sizeof(iID));
9631
9632 decl String:query[1024];
9633 Format(query, sizeof(query), "UPDATE %splayers SET infected_charger_damage = infected_charger_damage + %i WHERE steamid = '%s'", DbPrefix, Damage, iID);
9634 SendSQLUpdate(query);
9635
9636 UpdateMapStat("infected_charger_damage", Damage);
9637}
9638
9639public CheckSurvivorsWin()
9640{
9641 if (CampaignOver)
9642 return;
9643
9644 CampaignOver = true;
9645
9646 StopMapTiming();
9647
9648 // Return if gamemode is Scavenge or Survival
9649 if (CurrentGamemodeID == GAMEMODE_SCAVENGE ||
9650 CurrentGamemodeID == GAMEMODE_SURVIVAL)
9651 return;
9652
9653 new Score = ModifyScoreDifficulty(GetConVarInt(cvar_Witch), 5, 10, TEAM_SURVIVORS);
9654 new Mode = GetConVarInt(cvar_AnnounceMode);
9655 decl String:iID[MAX_LINE_WIDTH];
9656 decl String:query[1024];
9657 new maxplayers = GetMaxClients();
9658 decl String:UpdatePoints[32], String:UpdatePointsPenalty[32];
9659 new ClientTeam, bool:NegativeScore = GetConVarBool(cvar_EnableNegativeScore);
9660
9661 switch (CurrentGamemodeID)
9662 {
9663 case GAMEMODE_VERSUS:
9664 {
9665 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
9666 Format(UpdatePointsPenalty, sizeof(UpdatePointsPenalty), "points_infected");
9667 }
9668 case GAMEMODE_REALISM:
9669 {
9670 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
9671 }
9672 case GAMEMODE_REALISMVERSUS:
9673 {
9674 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
9675 Format(UpdatePointsPenalty, sizeof(UpdatePointsPenalty), "points_realism_infected");
9676 }
9677 case GAMEMODE_OTHERMUTATIONS:
9678 {
9679 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
9680 }
9681 default:
9682 {
9683 Format(UpdatePoints, sizeof(UpdatePoints), "points");
9684 }
9685 }
9686
9687 if (Score > 0 && WitchExists && !WitchDisturb)
9688 {
9689 for (new i = 1; i <= maxplayers; i++)
9690 {
9691 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i) && GetClientTeam(i) == TEAM_SURVIVORS)
9692 {
9693 GetClientRankAuthString(i, iID, sizeof(iID));
9694 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
9695 SendSQLUpdate(query);
9696 UpdateMapStat("points", Score);
9697 AddScore(i, Score);
9698 }
9699 }
9700
9701 if (Mode)
9702 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have earned \x04%i \x01points for \x05Not Disturbing A Witch!", Score);
9703 }
9704
9705 Score = 0;
9706 new Deaths = 0;
9707 new BaseScore = ModifyScoreDifficulty(GetConVarInt(cvar_SafeHouse), 2, 5, TEAM_SURVIVORS);
9708
9709 for (new i = 1; i <= maxplayers; i++)
9710 {
9711 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i) && GetClientTeam(i) == TEAM_SURVIVORS)
9712 {
9713 if (IsPlayerAlive(i))
9714 Score = Score + BaseScore;
9715 else
9716 Deaths++;
9717 }
9718 }
9719
9720 new String:All4Safe[64] = "";
9721 if (Deaths == 0)
9722 Format(All4Safe, sizeof(All4Safe), ", award_allinsafehouse = award_allinsafehouse + 1");
9723
9724 for (new i = 1; i <= maxplayers; i++)
9725 {
9726 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
9727 {
9728 ClientTeam = GetClientTeam(i);
9729
9730 if (ClientTeam == TEAM_SURVIVORS)
9731 {
9732 InterstitialPlayerUpdate(i);
9733
9734 GetClientRankAuthString(i, iID, sizeof(iID));
9735 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i%s WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, All4Safe, iID);
9736 SendSQLUpdate(query);
9737 UpdateMapStat("points", Score);
9738 AddScore(i, Score);
9739 }
9740 else if (ClientTeam == TEAM_INFECTED && NegativeScore)
9741 {
9742 DoInfectedFinalChecks(i);
9743
9744 GetClientRankAuthString(i, iID, sizeof(iID));
9745 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, iID);
9746 SendSQLUpdate(query);
9747 AddScore(i, Score * (-1));
9748 }
9749
9750 if (TimerRankChangeCheck[i] != INVALID_HANDLE)
9751 TriggerTimer(TimerRankChangeCheck[i], true);
9752 }
9753 }
9754
9755 if (Mode && Score > 0)
9756 {
9757 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have earned \x04%i \x01points for reaching a Safe House with \x05%i Deaths!", Score, Deaths);
9758
9759 if (NegativeScore)
9760 StatsPrintToChatTeam(TEAM_INFECTED, "\x03ALL INFECTED \x01have \x03LOST \x04%i \x01points for letting the survivors reach a Safe House!", Score);
9761 }
9762
9763 PlayerVomited = false;
9764 PanicEvent = false;
9765}
9766
9767IsSingleTeamGamemode()
9768{
9769 if (CurrentGamemodeID == GAMEMODE_SCAVENGE ||
9770 CurrentGamemodeID == GAMEMODE_VERSUS ||
9771 CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
9772 return false;
9773
9774 return true;
9775}
9776
9777CheckSurvivorsAllDown()
9778{
9779 if (CampaignOver ||
9780 CurrentGamemodeID == GAMEMODE_COOP ||
9781 CurrentGamemodeID == GAMEMODE_REALISM)
9782 return;
9783
9784 new maxplayers = GetMaxClients();
9785 new ClientTeam;
9786 new bool:ClientIsAlive, bool:ClientIsBot, bool:ClientIsIncap;
9787 new KilledSurvivor[MaxClients];
9788 new AliveInfected[MaxClients];
9789 new Infected[MaxClients];
9790 new InfectedCounter = 0, AliveInfectedCounter = 0;
9791 new i;
9792
9793 // Add to killing score on all incapacitated surviviors
9794 new IncapCounter = 0;
9795
9796 for (i = 1; i <= maxplayers; i++)
9797 {
9798 if (!IsClientInGame(i))
9799 continue;
9800
9801 ClientIsBot = IsClientBot(i);
9802 ClientIsIncap = IsClientIncapacitated(i);
9803 ClientIsAlive = IsClientAlive(i);
9804
9805 if (ClientIsBot || IsClientInGame(i))
9806 ClientTeam = GetClientTeam(i);
9807 else
9808 continue;
9809
9810 // Client is not dead and not incapped -> game continues!
9811 if (ClientTeam == TEAM_SURVIVORS && ClientIsAlive && !ClientIsIncap)
9812 return;
9813
9814 if (ClientTeam == TEAM_INFECTED && !ClientIsBot)
9815 {
9816 if (ClientIsAlive)
9817 AliveInfected[AliveInfectedCounter++] = i;
9818
9819 Infected[InfectedCounter++] = i;
9820 }
9821 else if (ClientTeam == TEAM_SURVIVORS && ClientIsAlive)
9822 KilledSurvivor[IncapCounter++] = i;
9823 }
9824
9825 // If we ever get this far it means the surviviors are all down or dead!
9826
9827 CampaignOver = true;
9828
9829 // Stop the timer and return if gamemode is Survival
9830 if (CurrentGamemodeID == GAMEMODE_SURVIVAL)
9831 {
9832 SurvivalStarted = false;
9833 StopMapTiming();
9834 return;
9835 }
9836
9837 // If we ever get this far it means the current gamemode is NOT Survival
9838
9839 for (i = 1; i <= maxplayers; i++)
9840 {
9841 if (IsClientConnected(i) && !IsFakeClient(i) && IsClientInGame(i))
9842 {
9843 if (GetClientTeam(i) == TEAM_SURVIVORS)
9844 InterstitialPlayerUpdate(i);
9845
9846 if (TimerRankChangeCheck[i] != INVALID_HANDLE)
9847 TriggerTimer(TimerRankChangeCheck[i], true);
9848 }
9849 }
9850
9851 decl String:query[1024];
9852 decl String:ClientID[MAX_LINE_WIDTH];
9853 new Mode = GetConVarInt(cvar_AnnounceMode);
9854
9855 for (i = 0; i < AliveInfectedCounter; i++)
9856 DoInfectedFinalChecks(AliveInfected[i]);
9857
9858 new Score = ModifyScoreDifficultyFloat(GetConVarInt(cvar_VictoryInfected), 0.75, 0.5, TEAM_INFECTED) * IncapCounter;
9859
9860 if (Score > 0)
9861 for (i = 0; i < InfectedCounter; i++)
9862 {
9863 GetClientRankAuthString(Infected[i], ClientID, sizeof(ClientID));
9864
9865 if (CurrentGamemodeID == GAMEMODE_VERSUS)
9866 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_infected_win = award_infected_win + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9867 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
9868 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_infected_win = award_infected_win + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9869 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
9870 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_scavenge_infected_win = award_scavenge_infected_win + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9871 else
9872 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_scavenge_infected_win = award_scavenge_infected_win + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9873
9874 SendSQLUpdate(query);
9875 }
9876
9877 UpdateMapStat("infected_win", 1);
9878 if (IncapCounter > 0)
9879 UpdateMapStat("survivor_kills", IncapCounter);
9880 if (Score > 0)
9881 UpdateMapStat("points_infected", Score);
9882
9883 if (Score > 0 && Mode)
9884 StatsPrintToChatTeam(TEAM_INFECTED, "\x03ALL INFECTED \x01have earned \x04%i \x01points for killing all survivors!", Score);
9885
9886 if (!GetConVarBool(cvar_EnableNegativeScore))
9887 return;
9888
9889 if (CurrentGamemodeID == GAMEMODE_VERSUS)
9890 {
9891 Score = ModifyScoreDifficultyFloatNR(GetConVarInt(cvar_Restart), 0.75, 0.5, TEAM_SURVIVORS);
9892 }
9893 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
9894 {
9895 Score = ModifyScoreDifficultyFloatNR(GetConVarInt(cvar_Restart), 0.6, 0.3, TEAM_SURVIVORS);
9896 }
9897 else
9898 {
9899 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_Restart), 2, 3, TEAM_SURVIVORS);
9900 Score = 400 - Score;
9901 }
9902
9903 for (i = 0; i < IncapCounter; i++)
9904 {
9905 GetClientRankAuthString(KilledSurvivor[i], ClientID, sizeof(ClientID));
9906
9907 if (CurrentGamemodeID == GAMEMODE_VERSUS)
9908 Format(query, sizeof(query), "UPDATE %splayers SET points_survivors = points_survivors - %i WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9909 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
9910 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_survivors = points_realism_survivors - %i WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9911 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
9912 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_survivors = points_scavenge_survivors - %i WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9913 else
9914 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations - %i WHERE steamid = '%s'", DbPrefix, Score, ClientID);
9915
9916 SendSQLUpdate(query);
9917 }
9918
9919 if (Mode)
9920 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01have \x03LOST \x04%i \x01points for \x03All Survivors Dying\x01!", Score);
9921}
9922
9923bool:IsClientIncapacitated(client)
9924{
9925 return GetEntProp(client, Prop_Send, "m_isIncapacitated") != 0 ||
9926 GetEntProp(client, Prop_Send, "m_isHangingFromLedge") != 0;
9927}
9928
9929bool:IsClientAlive(client)
9930{
9931 if (!IsClientConnected(client))
9932 return false;
9933
9934 if (IsFakeClient(client))
9935 return GetClientHealth(client) > 0 && GetEntProp(client, Prop_Send, "m_lifeState") == 0;
9936 else if (!IsClientInGame(client))
9937 return false;
9938
9939 return IsPlayerAlive(client);
9940}
9941
9942bool:IsGamemode(const String:Gamemode[])
9943{
9944 if (StrContains(CurrentGamemode, Gamemode, false) != -1)
9945 {
9946 return true;
9947 }
9948
9949 return false;
9950}
9951
9952GetGamemodeID(const String:Gamemode[])
9953{
9954 if (StrEqual(Gamemode, "coop", false))
9955 {
9956 return GAMEMODE_COOP;
9957 }
9958 else if (StrEqual(Gamemode, "survival", false))
9959 {
9960 return GAMEMODE_SURVIVAL;
9961 }
9962 else if (StrEqual(Gamemode, "versus", false))
9963 {
9964 return GAMEMODE_VERSUS;
9965 }
9966 else if (StrEqual(Gamemode, "teamversus", false) && GetConVarInt(cvar_EnableTeamVersus))
9967 {
9968 return GAMEMODE_VERSUS;
9969 }
9970 else if (StrEqual(Gamemode, "scavenge", false))
9971 {
9972 return GAMEMODE_SCAVENGE;
9973 }
9974 else if (StrEqual(Gamemode, "teamscavenge", false) && GetConVarInt(cvar_EnableTeamScavenge))
9975 {
9976 return GAMEMODE_SCAVENGE;
9977 }
9978 else if (StrEqual(Gamemode, "realism", false))
9979 {
9980 return GAMEMODE_REALISM;
9981 }
9982 else if (StrEqual(Gamemode, "mutation12", false))
9983 {
9984 return GAMEMODE_REALISMVERSUS;
9985 }
9986 else if (StrEqual(Gamemode, "teamrealismversus", false) && GetConVarInt(cvar_EnableTeamRealismVersus))
9987 {
9988 return GAMEMODE_REALISMVERSUS;
9989 }
9990 else if (StrContains(Gamemode, "mutation", false) == 0 ||
9991 StrContains(Gamemode, "community", false) == 0)
9992 {
9993 return GAMEMODE_OTHERMUTATIONS;
9994 }
9995
9996 return GAMEMODE_UNKNOWN;
9997}
9998
9999GetCurrentGamemodeID()
10000{
10001 new String:CurrentMode[16];
10002 GetConVarString(cvar_Gamemode, CurrentMode, sizeof(CurrentMode));
10003
10004 return GetGamemodeID(CurrentMode);
10005}
10006
10007IsGamemodeVersus()
10008{
10009 return IsGamemode("versus") || (IsGamemode("teamversus") && GetConVarBool(cvar_EnableTeamVersus));
10010}
10011/*
10012IsGamemodeRealism()
10013{
10014 return IsGamemode("realism");
10015}
10016
10017IsGamemodeRealismVersus()
10018{
10019 return IsGamemode("mutation12");
10020}
10021
10022IsGamemodeScavenge()
10023{
10024 return IsGamemode("scavege") || (IsGamemode("teamscavege") && GetConVarBool(cvar_EnableTeamScavenge));
10025}
10026
10027IsGamemodeCoop()
10028{
10029 return IsGamemode("coop");
10030}
10031*/
10032GetSurvivorKillScore()
10033{
10034 return ModifyScoreDifficultyFloat(GetConVarInt(cvar_SurvivorDeath), 0.75, 0.5, TEAM_INFECTED);
10035}
10036
10037DoInfectedFinalChecks(Client, ClientInfType = -1)
10038{
10039 if (Client == 0)
10040 return;
10041
10042 if (ClientInfType < 0)
10043 ClientInfType = ClientInfectedType[Client];
10044
10045 if (ClientInfType == INF_ID_SMOKER)
10046 {
10047 new Damage = SmokerDamageCounter[Client];
10048 SmokerDamageCounter[Client] = 0;
10049 UpdateSmokerDamage(Client, Damage);
10050 }
10051 else if (ServerVersion != SERVER_VERSION_L4D1 && ClientInfType == INF_ID_SPITTER_L4D2)
10052 {
10053 new Damage = SpitterDamageCounter[Client];
10054 SpitterDamageCounter[Client] = 0;
10055 UpdateSpitterDamage(Client, Damage);
10056 }
10057 else if (ServerVersion != SERVER_VERSION_L4D1 && ClientInfType == INF_ID_JOCKEY_L4D2)
10058 {
10059 new Damage = JockeyDamageCounter[Client];
10060 JockeyDamageCounter[Client] = 0;
10061 UpdateJockeyDamage(Client, Damage);
10062 UpdateJockeyRideLength(Client);
10063 }
10064 else if (ServerVersion != SERVER_VERSION_L4D1 && ClientInfType == INF_ID_CHARGER_L4D2)
10065 {
10066 new Damage = ChargerDamageCounter[Client];
10067 ChargerDamageCounter[Client] = 0;
10068 UpdateChargerDamage(Client, Damage);
10069 }
10070}
10071
10072GetInfType(Client)
10073{
10074 // Client > 0 && ClientTeam == TEAM_INFECTED checks are done by the caller
10075
10076 new InfType = GetEntProp(Client, Prop_Send, "m_zombieClass");
10077
10078 // Make the conversion so that everything gets stored in the correct fields
10079 if (ServerVersion == SERVER_VERSION_L4D1)
10080 {
10081 if (InfType == INF_ID_WITCH_L4D1)
10082 return INF_ID_WITCH_L4D2;
10083
10084 if (InfType == INF_ID_TANK_L4D1)
10085 return INF_ID_TANK_L4D2;
10086 }
10087
10088 return InfType;
10089}
10090
10091SetClientInfectedType(Client)
10092{
10093 // Bot check is done by the caller
10094
10095 if (Client <= 0)
10096 return;
10097
10098 new ClientTeam = GetClientTeam(Client);
10099
10100 if (ClientTeam == TEAM_INFECTED)
10101 {
10102 ClientInfectedType[Client] = GetInfType(Client);
10103
10104 if (ClientInfectedType[Client] != INF_ID_SMOKER
10105 && ClientInfectedType[Client] != INF_ID_BOOMER
10106 && ClientInfectedType[Client] != INF_ID_HUNTER
10107 && ClientInfectedType[Client] != INF_ID_SPITTER_L4D2
10108 && ClientInfectedType[Client] != INF_ID_JOCKEY_L4D2
10109 && ClientInfectedType[Client] != INF_ID_CHARGER_L4D2
10110 && ClientInfectedType[Client] != INF_ID_TANK_L4D2)
10111 return;
10112
10113 decl String:ClientID[MAX_LINE_WIDTH];
10114 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
10115
10116 decl String:query[1024];
10117 Format(query, sizeof(query), "UPDATE %splayers SET infected_spawn_%i = infected_spawn_%i + 1 WHERE steamid = '%s'", DbPrefix, ClientInfectedType[Client], ClientInfectedType[Client], ClientID);
10118 SendSQLUpdate(query);
10119
10120 new String:Spawn[32];
10121 Format(Spawn, sizeof(Spawn), "infected_spawn_%i", ClientInfectedType[Client]);
10122 UpdateMapStat(Spawn, 1);
10123 }
10124 else
10125 ClientInfectedType[Client] = 0;
10126}
10127
10128TankDamage(Client, Damage)
10129{
10130 if (Client <= 0 || Damage <= 0)
10131 return 0;
10132
10133 // Update only the Tank inflicted damage related statistics
10134 UpdateTankDamage(Client, Damage);
10135
10136 // If value is negative then client has already received the Bulldozer Award
10137 if (TankDamageTotalCounter[Client] >= 0)
10138 {
10139 TankDamageTotalCounter[Client] += Damage;
10140 new TankDamageTotal = GetConVarInt(cvar_TankDamageTotal);
10141
10142 if (TankDamageTotalCounter[Client] >= TankDamageTotal)
10143 {
10144 TankDamageTotalCounter[Client] = -1; // Just one award per Tank
10145 new Score = ModifyScoreDifficultyFloat(GetConVarInt(cvar_TankDamageTotalSuccess), 0.75, 0.5, TEAM_INFECTED);
10146
10147 if (Score > 0)
10148 {
10149 decl String:ClientID[MAX_LINE_WIDTH];
10150 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
10151
10152 decl String:query[1024];
10153
10154 if (CurrentGamemodeID == GAMEMODE_VERSUS)
10155 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_bulldozer = award_bulldozer + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
10156 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
10157 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_bulldozer = award_bulldozer + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
10158 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
10159 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_bulldozer = award_bulldozer + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
10160 else
10161 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_bulldozer = award_bulldozer + 1 WHERE steamid = '%s'", DbPrefix, Score, ClientID);
10162
10163 SendSQLUpdate(query);
10164
10165 UpdateMapStat("points_infected", Score);
10166
10167 new Mode = GetConVarInt(cvar_AnnounceMode);
10168
10169 if (Mode == 1 || Mode == 2)
10170 StatsPrintToChat(Client, "You have earned \x04%i \x01points for Bulldozing the Survivors worth %i points of damage!", Score, TankDamageTotal);
10171 else if (Mode == 3)
10172 {
10173 decl String:Name[MAX_LINE_WIDTH];
10174 GetClientName(Client, Name, sizeof(Name));
10175 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Bulldozing the Survivors worth %i points of damage!", Name, Score, TankDamageTotal);
10176 }
10177
10178 if (EnableSounds_Tank_Bulldozer && GetConVarBool(cvar_SoundsEnabled))
10179 EmitSoundToAll(StatsSound_Tank_Bulldozer);
10180 }
10181 }
10182 }
10183
10184 new DamageLimit = GetConVarInt(cvar_TankDamageCap);
10185
10186 if (TankDamageCounter[Client] >= DamageLimit)
10187 return 0;
10188
10189 TankDamageCounter[Client] += Damage;
10190
10191 if (TankDamageCounter[Client] > DamageLimit)
10192 Damage -= TankDamageCounter[Client] - DamageLimit;
10193
10194 return Damage;
10195}
10196
10197UpdateFriendlyFire(Attacker, Victim)
10198{
10199 decl String:AttackerName[MAX_LINE_WIDTH];
10200 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
10201 decl String:AttackerID[MAX_LINE_WIDTH];
10202 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
10203
10204 decl String:VictimName[MAX_LINE_WIDTH];
10205 GetClientName(Victim, VictimName, sizeof(VictimName));
10206
10207 new Score = 0;
10208 if (GetConVarBool(cvar_EnableNegativeScore))
10209 {
10210 if (!IsClientBot(Victim))
10211 Score = ModifyScoreDifficultyNR(GetConVarInt(cvar_FFire), 2, 4, TEAM_SURVIVORS);
10212 else
10213 {
10214 new Float:BotScoreMultiplier = GetConVarFloat(cvar_BotScoreMultiplier);
10215
10216 if (BotScoreMultiplier > 0.0)
10217 Score = RoundToNearest(ModifyScoreDifficultyNR(GetConVarInt(cvar_FFire), 2, 4, TEAM_SURVIVORS) * BotScoreMultiplier);
10218 }
10219 }
10220
10221 decl String:UpdatePoints[32];
10222
10223 switch (CurrentGamemodeID)
10224 {
10225 case GAMEMODE_VERSUS:
10226 {
10227 Format(UpdatePoints, sizeof(UpdatePoints), "points_survivors");
10228 }
10229 case GAMEMODE_REALISM:
10230 {
10231 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism");
10232 }
10233 case GAMEMODE_SURVIVAL:
10234 {
10235 Format(UpdatePoints, sizeof(UpdatePoints), "points_survival");
10236 }
10237 case GAMEMODE_SCAVENGE:
10238 {
10239 Format(UpdatePoints, sizeof(UpdatePoints), "points_scavenge_survivors");
10240 }
10241 case GAMEMODE_REALISMVERSUS:
10242 {
10243 Format(UpdatePoints, sizeof(UpdatePoints), "points_realism_survivors");
10244 }
10245 case GAMEMODE_OTHERMUTATIONS:
10246 {
10247 Format(UpdatePoints, sizeof(UpdatePoints), "points_mutations");
10248 }
10249 default:
10250 {
10251 Format(UpdatePoints, sizeof(UpdatePoints), "points");
10252 }
10253 }
10254
10255 decl String:query[1024];
10256 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s - %i, award_friendlyfire = award_friendlyfire + 1 WHERE steamid = '%s'", DbPrefix, UpdatePoints, UpdatePoints, Score, AttackerID);
10257 SendSQLUpdate(query);
10258
10259 new Mode = 0;
10260 if (Score > 0)
10261 Mode = GetConVarInt(cvar_AnnounceMode);
10262
10263 if (Mode == 1 || Mode == 2)
10264 StatsPrintToChat(Attacker, "You have \x03LOST \x04%i \x01points for \x03Friendly Firing \x05%s\x01!", Score, VictimName);
10265 else if (Mode == 3)
10266 StatsPrintToChatAll("\x05%s \x01has \x03LOST \x04%i \x01points for \x03Friendly Firing \x05%s\x01!", AttackerName, Score, VictimName);
10267}
10268
10269UpdateHunterDamage(Client, Damage)
10270{
10271 if (Damage <= 0)
10272 return;
10273
10274 decl String:ClientID[MAX_LINE_WIDTH];
10275 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
10276
10277 decl String:query[1024];
10278 Format(query, sizeof(query), "UPDATE %splayers SET infected_hunter_pounce_dmg = infected_hunter_pounce_dmg + %i, infected_hunter_pounce_counter = infected_hunter_pounce_counter + 1 WHERE steamid = '%s'", DbPrefix, Damage, ClientID);
10279 SendSQLUpdate(query);
10280
10281 UpdateMapStat("infected_hunter_pounce_counter", 1);
10282 UpdateMapStat("infected_hunter_pounce_damage", Damage);
10283}
10284
10285UpdateTankDamage(Client, Damage)
10286{
10287 if (Damage <= 0)
10288 return;
10289
10290 decl String:ClientID[MAX_LINE_WIDTH];
10291 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
10292
10293 decl String:query[1024];
10294 Format(query, sizeof(query), "UPDATE %splayers SET infected_tank_damage = infected_tank_damage + %i WHERE steamid = '%s'", DbPrefix, Damage, ClientID);
10295 SendSQLUpdate(query);
10296
10297 UpdateMapStat("infected_tank_damage", Damage);
10298}
10299/*
10300UpdatePlayerScore(Client, Score)
10301{
10302 if (Score == 0)
10303 return;
10304
10305 switch (CurrentGamemodeID)
10306 {
10307 case GAMEMODE_VERSUS:
10308 {
10309 UpdatePlayerScoreVersus(Client, GetClientTeam(Client), Score);
10310 }
10311 case GAMEMODE_REALISM:
10312 {
10313 UpdatePlayerScoreRealismVersus(Client, GetClientTeam(Client), Score);
10314 }
10315 case GAMEMODE_SURVIVAL:
10316 {
10317 UpdatePlayerScore2(Client, Score, "points_survival");
10318 }
10319 case GAMEMODE_SCAVENGE:
10320 {
10321 UpdatePlayerScoreScavenge(Client, GetClientTeam(Client), Score);
10322 }
10323 case GAMEMODE_OTHERMUTATIONS:
10324 {
10325 UpdatePlayerScore2(Client, Score, "points_mutations");
10326 }
10327 default:
10328 {
10329 UpdatePlayerScore2(Client, Score, "points");
10330 }
10331 }
10332}
10333
10334UpdatePlayerScoreVersus(Client, ClientTeam, Score)
10335{
10336 if (Score == 0)
10337 return;
10338
10339 if (ClientTeam == TEAM_SURVIVORS)
10340 UpdatePlayerScore2(Client, Score, "points_survivors");
10341 else if (ClientTeam == TEAM_INFECTED)
10342 UpdatePlayerScore2(Client, Score, "points_infected");
10343}
10344
10345UpdatePlayerScoreRealismVersus(Client, ClientTeam, Score)
10346{
10347 if (Score == 0)
10348 return;
10349
10350 if (ClientTeam == TEAM_SURVIVORS)
10351 UpdatePlayerScore2(Client, Score, "points_realism_survivors");
10352 else if (ClientTeam == TEAM_INFECTED)
10353 UpdatePlayerScore2(Client, Score, "points_realism_infected");
10354}
10355
10356UpdatePlayerScoreScavenge(Client, ClientTeam, Score)
10357{
10358 if (Score == 0)
10359 return;
10360
10361 if (ClientTeam == TEAM_SURVIVORS)
10362 UpdatePlayerScore2(Client, Score, "points_scavenge_survivors");
10363 else if (ClientTeam == TEAM_INFECTED)
10364 UpdatePlayerScore2(Client, Score, "points_scavenge_infected");
10365}
10366*/
10367UpdatePlayerScore2(Client, Score, const String:Points[])
10368{
10369 if (Score == 0)
10370 return;
10371
10372 decl String:ClientID[MAX_LINE_WIDTH];
10373 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
10374
10375 decl String:query[1024];
10376 Format(query, sizeof(query), "UPDATE %splayers SET %s = %s + %i WHERE steamid = '%s'", DbPrefix, Points, Points, Score, ClientID);
10377 SendSQLUpdate(query);
10378
10379 if (Score > 0)
10380 UpdateMapStat("points", Score);
10381
10382 AddScore(Client, Score);
10383}
10384
10385UpdateTankSniper(Client)
10386{
10387 if (Client <= 0)
10388 return;
10389
10390 decl String:ClientID[MAX_LINE_WIDTH];
10391 GetClientRankAuthString(Client, ClientID, sizeof(ClientID));
10392
10393 UpdateTankSniperSteamID(ClientID);
10394}
10395
10396UpdateTankSniperSteamID(const String:ClientID[])
10397{
10398 decl String:query[1024];
10399 Format(query, sizeof(query), "UPDATE %splayers SET infected_tanksniper = infected_tanksniper + 1 WHERE steamid = '%s'", DbPrefix, ClientID);
10400 SendSQLUpdate(query);
10401
10402 UpdateMapStat("infected_tanksniper", 1);
10403}
10404
10405// Survivor died.
10406
10407SurvivorDied(Attacker, Victim, AttackerInfType = -1, Mode = -1)
10408{
10409 if (!Attacker || !Victim || StatsGetClientTeam(Attacker) != TEAM_INFECTED || StatsGetClientTeam(Victim) != TEAM_SURVIVORS)
10410 return;
10411
10412 decl String:AttackerID[MAX_LINE_WIDTH];
10413 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
10414
10415 decl String:VictimName[MAX_LINE_WIDTH];
10416 GetClientName(Victim, VictimName, sizeof(VictimName));
10417
10418 SurvivorDiedNamed(Attacker, Victim, VictimName, AttackerID, AttackerInfType, Mode);
10419}
10420
10421// An Infected player killed a Survivor.
10422
10423SurvivorDiedNamed(Attacker, Victim, const String:VictimName[], const String:AttackerID[], AttackerInfType = -1, Mode = -1)
10424{
10425 if (!Attacker || !Victim || StatsGetClientTeam(Attacker) != TEAM_INFECTED || StatsGetClientTeam(Victim) != TEAM_SURVIVORS)
10426 return;
10427
10428//LogError("SurvivorDiedNamed - VictimName = %s", VictimName);
10429
10430 if (AttackerInfType < 0)
10431 {
10432 if (ClientInfectedType[Attacker] == 0)
10433 SetClientInfectedType(Attacker);
10434
10435 AttackerInfType = ClientInfectedType[Attacker];
10436 }
10437
10438 if (ServerVersion == SERVER_VERSION_L4D1)
10439 {
10440 if (AttackerInfType != INF_ID_SMOKER
10441 && AttackerInfType != INF_ID_BOOMER
10442 && AttackerInfType != INF_ID_HUNTER
10443 && AttackerInfType != INF_ID_TANK_L4D2) // SetClientInfectedType sets tank id to L4D2
10444 return;
10445 }
10446 else
10447 {
10448 if (AttackerInfType != INF_ID_SMOKER
10449 && AttackerInfType != INF_ID_BOOMER
10450 && AttackerInfType != INF_ID_HUNTER
10451 && AttackerInfType != INF_ID_SPITTER_L4D2
10452 && AttackerInfType != INF_ID_JOCKEY_L4D2
10453 && AttackerInfType != INF_ID_CHARGER_L4D2
10454 && AttackerInfType != INF_ID_TANK_L4D2)
10455 return;
10456 }
10457
10458 new Score = GetSurvivorKillScore();
10459
10460 new len = 0;
10461 decl String:query[1024];
10462
10463 if (CurrentGamemodeID == GAMEMODE_VERSUS)
10464 len += Format(query[len], sizeof(query)-len, "UPDATE %splayers SET points_infected = points_infected + %i, versus_kills_survivors = versus_kills_survivors + 1 ", DbPrefix, Score);
10465 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
10466 len += Format(query[len], sizeof(query)-len, "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, realism_kills_survivors = realism_kills_survivors + 1 ", DbPrefix, Score);
10467 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
10468 len += Format(query[len], sizeof(query)-len, "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, scavenge_kills_survivors = scavenge_kills_survivors + 1 ", DbPrefix, Score);
10469 else
10470 len += Format(query[len], sizeof(query)-len, "UPDATE %splayers SET points_mutations = points_mutations + %i, mutations_kills_survivors = mutations_kills_survivors + 1 ", DbPrefix, Score);
10471 len += Format(query[len], sizeof(query)-len, "WHERE steamid = '%s'", AttackerID);
10472 SendSQLUpdate(query);
10473
10474 if (Mode < 0)
10475 Mode = GetConVarInt(cvar_AnnounceMode);
10476
10477 if (Mode)
10478 {
10479 if (Mode > 2)
10480 {
10481 decl String:AttackerName[MAX_LINE_WIDTH];
10482 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
10483 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for killing \x05%s\x01!", AttackerName, Score, VictimName);
10484 }
10485 else
10486 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for killing \x05%s\x01!", Score, VictimName);
10487 }
10488
10489 UpdateMapStat("survivor_kills", 1);
10490 UpdateMapStat("points_infected", Score);
10491 AddScore(Attacker, Score);
10492}
10493
10494// Survivor got hurt.
10495
10496SurvivorHurt(Attacker, Victim, Damage, AttackerInfType = -1, Handle:event = INVALID_HANDLE)
10497{
10498 if (!Attacker || !Victim || Damage <= 0 || Attacker == Victim)
10499 return;
10500
10501 if (AttackerInfType < 0)
10502 {
10503 new AttackerTeam = GetClientTeam(Attacker);
10504
10505 if (Attacker > 0 && AttackerTeam == TEAM_INFECTED)
10506 AttackerInfType = GetInfType(Attacker);
10507 }
10508
10509 if (AttackerInfType != INF_ID_SMOKER
10510 && AttackerInfType != INF_ID_BOOMER
10511 && AttackerInfType != INF_ID_HUNTER
10512 && AttackerInfType != INF_ID_SPITTER_L4D2
10513 && AttackerInfType != INF_ID_JOCKEY_L4D2
10514 && AttackerInfType != INF_ID_CHARGER_L4D2
10515 && AttackerInfType != INF_ID_TANK_L4D2)
10516 return;
10517
10518 if (TimerInfectedDamageCheck[Attacker] != INVALID_HANDLE)
10519 {
10520 CloseHandle(TimerInfectedDamageCheck[Attacker]);
10521 TimerInfectedDamageCheck[Attacker] = INVALID_HANDLE;
10522 }
10523
10524 new VictimHealth = GetClientHealth(Victim);
10525
10526 if (VictimHealth < 0)
10527 Damage += VictimHealth;
10528
10529 if (Damage <= 0)
10530 return;
10531
10532 if (AttackerInfType == INF_ID_TANK_L4D2 && event != INVALID_HANDLE)
10533 {
10534 InfectedDamageCounter[Attacker] += TankDamage(Attacker, Damage);
10535
10536 decl String:Weapon[16];
10537 GetEventString(event, "weapon", Weapon, sizeof(Weapon));
10538
10539 new RockHit = GetConVarInt(cvar_TankThrowRockSuccess);
10540
10541 if (RockHit > 0 && strcmp(Weapon, "tank_rock", false) == 0)
10542 {
10543 if (CurrentGamemodeID == GAMEMODE_VERSUS)
10544 UpdatePlayerScore2(Attacker, RockHit, "points_infected");
10545 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
10546 UpdatePlayerScore2(Attacker, RockHit, "points_realism_infected");
10547 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
10548 UpdatePlayerScore2(Attacker, RockHit, "points_scavenge_infected");
10549 else
10550 UpdatePlayerScore2(Attacker, RockHit, "points_mutations");
10551 UpdateTankSniper(Attacker);
10552
10553 decl String:VictimName[MAX_LINE_WIDTH];
10554
10555 if (Victim > 0)
10556 GetClientName(Victim, VictimName, sizeof(VictimName));
10557 else
10558 Format(VictimName, sizeof(VictimName), "UNKNOWN");
10559
10560 new Mode = GetConVarInt(cvar_AnnounceMode);
10561
10562 if (Mode == 1 || Mode == 2)
10563 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for throwing a rock at \x05%s\x01!", RockHit, VictimName);
10564 else if (Mode == 3)
10565 {
10566 decl String:AttackerName[MAX_LINE_WIDTH];
10567 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
10568 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for throwing a rock at \x05%s\x01!", AttackerName, RockHit, VictimName);
10569 }
10570 }
10571 }
10572 else
10573 InfectedDamageCounter[Attacker] += Damage;
10574
10575 if (AttackerInfType == INF_ID_SMOKER)
10576 SmokerDamageCounter[Attacker] += Damage;
10577 else if (AttackerInfType == INF_ID_SPITTER_L4D2)
10578 SpitterDamageCounter[Attacker] += Damage;
10579 else if (AttackerInfType == INF_ID_JOCKEY_L4D2)
10580 JockeyDamageCounter[Attacker] += Damage;
10581 else if (AttackerInfType == INF_ID_CHARGER_L4D2)
10582 ChargerDamageCounter[Attacker] += Damage;
10583
10584 TimerInfectedDamageCheck[Attacker] = CreateTimer(5.0, timer_InfectedDamageCheck, Attacker);
10585}
10586
10587// Survivor was hurt by normal infected while being blinded and/or paralyzed.
10588
10589SurvivorHurtExternal(Handle:event, Victim)
10590{
10591 if (event == INVALID_HANDLE || !Victim)
10592 return;
10593
10594 new Damage = GetEventInt(event, "dmg_health");
10595
10596 new VictimHealth = GetClientHealth(Victim);
10597
10598 if (VictimHealth < 0)
10599 Damage += VictimHealth;
10600
10601 if (Damage <= 0)
10602 return;
10603
10604 new Attacker;
10605
10606 if (PlayerBlinded[Victim][0] && PlayerBlinded[Victim][1])
10607 {
10608 Attacker = PlayerBlinded[Victim][1];
10609
10610 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10611 SurvivorHurt(Attacker, Victim, Damage);
10612 }
10613
10614 if (PlayerParalyzed[Victim][0] && PlayerParalyzed[Victim][1])
10615 {
10616 Attacker = PlayerParalyzed[Victim][1];
10617
10618 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10619 SurvivorHurt(Attacker, Victim, Damage);
10620 }
10621 else if (PlayerLunged[Victim][0] && PlayerLunged[Victim][1])
10622 {
10623 Attacker = PlayerLunged[Victim][1];
10624
10625 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10626 SurvivorHurt(Attacker, Victim, Damage);
10627 }
10628 else if (PlayerPlummeled[Victim][0] && PlayerPlummeled[Victim][1])
10629 {
10630 Attacker = PlayerPlummeled[Victim][1];
10631
10632 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10633 SurvivorHurt(Attacker, Victim, Damage);
10634 }
10635 else if (PlayerCarried[Victim][0] && PlayerCarried[Victim][1])
10636 {
10637 Attacker = PlayerCarried[Victim][1];
10638
10639 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10640 SurvivorHurt(Attacker, Victim, Damage);
10641 }
10642 else if (PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
10643 {
10644 Attacker = PlayerJockied[Victim][1];
10645
10646 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10647 SurvivorHurt(Attacker, Victim, Damage);
10648 }
10649}
10650
10651PlayerDeathExternal(Victim)
10652{
10653 if (!Victim || StatsGetClientTeam(Victim) != TEAM_SURVIVORS)
10654 return;
10655
10656 CheckSurvivorsAllDown();
10657
10658 new Attacker = 0;
10659
10660 if (PlayerBlinded[Victim][0] && PlayerBlinded[Victim][1])
10661 {
10662 Attacker = PlayerBlinded[Victim][1];
10663
10664 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10665 SurvivorDied(Attacker, Victim, INF_ID_BOOMER);
10666 }
10667
10668 if (PlayerParalyzed[Victim][0] && PlayerParalyzed[Victim][1])
10669 {
10670 Attacker = PlayerParalyzed[Victim][1];
10671
10672 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10673 SurvivorDied(Attacker, Victim, INF_ID_SMOKER);
10674 }
10675 else if (PlayerLunged[Victim][0] && PlayerLunged[Victim][1])
10676 {
10677 Attacker = PlayerLunged[Victim][1];
10678
10679 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10680 SurvivorDied(Attacker, Victim, INF_ID_HUNTER);
10681 }
10682 else if (PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
10683 {
10684 Attacker = PlayerJockied[Victim][1];
10685
10686 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10687 SurvivorDied(Attacker, Victim, INF_ID_HUNTER);
10688 }
10689 else if (PlayerCarried[Victim][0] && PlayerCarried[Victim][1])
10690 {
10691 Attacker = PlayerCarried[Victim][1];
10692
10693 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10694 SurvivorDied(Attacker, Victim, INF_ID_HUNTER);
10695 }
10696 else if (PlayerPlummeled[Victim][0] && PlayerPlummeled[Victim][1])
10697 {
10698 Attacker = PlayerPlummeled[Victim][1];
10699
10700 if (Attacker && !IsClientBot(Attacker) && IsClientInGame(Attacker))
10701 SurvivorDied(Attacker, Victim, INF_ID_HUNTER);
10702 }
10703}
10704
10705PlayerIncapExternal(Victim)
10706{
10707 if (!Victim || StatsGetClientTeam(Victim) != TEAM_SURVIVORS)
10708 return;
10709
10710 CheckSurvivorsAllDown();
10711
10712 new Attacker = 0;
10713
10714 if (PlayerBlinded[Victim][0] && PlayerBlinded[Victim][1])
10715 {
10716 Attacker = PlayerBlinded[Victim][1];
10717 SurvivorIncappedByInfected(Attacker, Victim);
10718 }
10719
10720 if (PlayerParalyzed[Victim][0] && PlayerParalyzed[Victim][1])
10721 {
10722 Attacker = PlayerParalyzed[Victim][1];
10723 SurvivorIncappedByInfected(Attacker, Victim);
10724 }
10725 else if (PlayerLunged[Victim][0] && PlayerLunged[Victim][1])
10726 {
10727 Attacker = PlayerLunged[Victim][1];
10728 SurvivorIncappedByInfected(Attacker, Victim);
10729 }
10730 else if (PlayerPlummeled[Victim][0] && PlayerPlummeled[Victim][1])
10731 {
10732 Attacker = PlayerPlummeled[Victim][1];
10733 SurvivorIncappedByInfected(Attacker, Victim);
10734 }
10735 else if (PlayerCarried[Victim][0] && PlayerCarried[Victim][1])
10736 {
10737 Attacker = PlayerCarried[Victim][1];
10738 SurvivorIncappedByInfected(Attacker, Victim);
10739 }
10740 else if (PlayerJockied[Victim][0] && PlayerJockied[Victim][1])
10741 {
10742 Attacker = PlayerJockied[Victim][1];
10743 SurvivorIncappedByInfected(Attacker, Victim);
10744 }
10745}
10746
10747SurvivorIncappedByInfected(Attacker, Victim, Mode = -1)
10748{
10749 if (Attacker > 0 && !IsClientConnected(Attacker) || Attacker > 0 && IsClientBot(Attacker))
10750 return;
10751
10752 decl String:AttackerID[MAX_LINE_WIDTH];
10753 GetClientRankAuthString(Attacker, AttackerID, sizeof(AttackerID));
10754 decl String:AttackerName[MAX_LINE_WIDTH];
10755 GetClientName(Attacker, AttackerName, sizeof(AttackerName));
10756
10757 decl String:VictimName[MAX_LINE_WIDTH];
10758 GetClientName(Victim, VictimName, sizeof(VictimName));
10759
10760 new Score = ModifyScoreDifficultyFloat(GetConVarInt(cvar_SurvivorIncap), 0.75, 0.5, TEAM_INFECTED);
10761
10762 if (Score <= 0)
10763 return;
10764
10765 decl String:query[512];
10766
10767 if (CurrentGamemodeID == GAMEMODE_VERSUS)
10768 Format(query, sizeof(query), "UPDATE %splayers SET points_infected = points_infected + %i, award_survivor_down = award_survivor_down + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
10769 else if (CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
10770 Format(query, sizeof(query), "UPDATE %splayers SET points_realism_infected = points_realism_infected + %i, award_survivor_down = award_survivor_down + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
10771 else if (CurrentGamemodeID == GAMEMODE_SCAVENGE)
10772 Format(query, sizeof(query), "UPDATE %splayers SET points_scavenge_infected = points_scavenge_infected + %i, award_survivor_down = award_survivor_down + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
10773 else
10774 Format(query, sizeof(query), "UPDATE %splayers SET points_mutations = points_mutations + %i, award_survivor_down = award_survivor_down + 1 WHERE steamid = '%s'", DbPrefix, Score, AttackerID);
10775 SendSQLUpdate(query);
10776
10777 UpdateMapStat("points_infected", Score);
10778
10779 if (Mode < 0)
10780 Mode = GetConVarInt(cvar_AnnounceMode);
10781
10782 if (Mode == 1 || Mode == 2)
10783 StatsPrintToChat(Attacker, "You have earned \x04%i \x01points for Incapacitating \x05%s\x01!", Score, VictimName);
10784 else if (Mode == 3)
10785 StatsPrintToChatAll("\x05%s \x01has earned \x04%i \x01points for Incapacitating \x05%s\x01!", AttackerName, Score, VictimName);
10786}
10787
10788Float:GetMedkitPointReductionFactor()
10789{
10790 if (MedkitsUsedCounter <= 0)
10791 return 1.0;
10792
10793 new Float:Penalty = GetConVarFloat(cvar_MedkitUsedPointPenalty);
10794
10795 // If Penalty is set to ZERO: There is no reduction.
10796 if (Penalty <= 0.0)
10797 return 1.0;
10798
10799 new PenaltyFree = -1;
10800
10801 if (CurrentGamemodeID == GAMEMODE_REALISM || CurrentGamemodeID == GAMEMODE_REALISMVERSUS)
10802 PenaltyFree = GetConVarInt(cvar_MedkitUsedRealismFree);
10803
10804 if (PenaltyFree < 0)
10805 PenaltyFree = GetConVarInt(cvar_MedkitUsedFree);
10806
10807 if (PenaltyFree >= MedkitsUsedCounter)
10808 return 1.0;
10809
10810 Penalty *= MedkitsUsedCounter - PenaltyFree;
10811
10812 new Float:PenaltyMax = GetConVarFloat(cvar_MedkitUsedPointPenaltyMax);
10813
10814 if (Penalty > PenaltyMax)
10815 return 1.0 - PenaltyMax;
10816
10817 return 1.0 - Penalty;
10818}
10819
10820// Calculate the score with the medkit point reduction
10821
10822GetMedkitPointReductionScore(Score, bool:ToCeil = false)
10823{
10824 new Float:ReductionFactor = GetMedkitPointReductionFactor();
10825
10826 if (ReductionFactor == 1.0)
10827 return Score;
10828
10829 if (ToCeil)
10830 return RoundToCeil(Score * ReductionFactor);
10831 else
10832 return RoundToFloor(Score * ReductionFactor);
10833}
10834
10835AnnounceMedkitPenalty(Mode = -1)
10836{
10837 new Float:ReductionFactor = GetMedkitPointReductionFactor();
10838
10839 if (ReductionFactor == 1.0)
10840 return;
10841
10842 if (Mode < 0)
10843 Mode = GetConVarInt(cvar_AnnounceMode);
10844
10845 if (Mode)
10846 StatsPrintToChatTeam(TEAM_SURVIVORS, "\x03ALL SURVIVORS \x01now earns only \x04%i percent \x01of their normal points after using their \x05%i%s Medkit%s\x01!", RoundToNearest(ReductionFactor * 100), MedkitsUsedCounter, (MedkitsUsedCounter == 1 ? "st" : (MedkitsUsedCounter == 2 ? "nd" : (MedkitsUsedCounter == 3 ? "rd" : "th"))), (ServerVersion == SERVER_VERSION_L4D1 ? "" : " or Defibrillator"));
10847}
10848
10849GetClientInfectedType(Client)
10850{
10851 if (Client > 0 && GetClientTeam(Client) == TEAM_INFECTED)
10852 return GetInfType(Client);
10853
10854 return 0;
10855}
10856
10857InitializeClientInf(Client)
10858{
10859 for (new i = 1; i <= MAXPLAYERS; i++)
10860 {
10861 if (PlayerParalyzed[i][1] == Client)
10862 {
10863 PlayerParalyzed[i][0] = 0;
10864 PlayerParalyzed[i][1] = 0;
10865 }
10866 if (PlayerLunged[i][1] == Client)
10867 {
10868 PlayerLunged[i][0] = 0;
10869 PlayerLunged[i][1] = 0;
10870 }
10871 if (PlayerCarried[i][1] == Client)
10872 {
10873 PlayerCarried[i][0] = 0;
10874 PlayerCarried[i][1] = 0;
10875 }
10876 if (PlayerPlummeled[i][1] == Client)
10877 {
10878 PlayerPlummeled[i][0] = 0;
10879 PlayerPlummeled[i][1] = 0;
10880 }
10881 if (PlayerJockied[i][1] == Client)
10882 {
10883 PlayerJockied[i][0] = 0;
10884 PlayerJockied[i][1] = 0;
10885 }
10886 }
10887}
10888
10889// Print a chat message to a specific team instead of all players
10890
10891public StatsPrintToChatTeam(Team, const String:Message[], any:...)
10892{
10893 new String:FormattedMessage[MAX_MESSAGE_WIDTH];
10894 VFormat(FormattedMessage, sizeof(FormattedMessage), Message, 3);
10895
10896 new AnnounceToTeam = GetConVarInt(cvar_AnnounceToTeam);
10897
10898 if (Team > 0 && AnnounceToTeam)
10899 {
10900 new maxplayers = GetMaxClients();
10901 new ClientTeam;
10902
10903 for (new i = 1; i <= maxplayers; i++)
10904 {
10905 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
10906 {
10907 ClientTeam = GetClientTeam(i);
10908 if (ClientTeam == Team || (ClientTeam == TEAM_SPECTATORS && AnnounceToTeam == 2))
10909 {
10910 StatsPrintToChatPreFormatted(i, FormattedMessage);
10911 }
10912 }
10913 }
10914 }
10915 else
10916 StatsPrintToChatAllPreFormatted(FormattedMessage);
10917}
10918
10919// Debugging...
10920
10921public PrintToConsoleAll(const String:Message[], any:...)
10922{
10923 new String:FormattedMessage[MAX_MESSAGE_WIDTH];
10924 VFormat(FormattedMessage, sizeof(FormattedMessage), Message, 2);
10925
10926 PrintToConsole(0, FormattedMessage);
10927
10928 new maxplayers = GetMaxClients();
10929
10930 for (new i = 1; i <= maxplayers; i++)
10931 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
10932 PrintToConsole(i, FormattedMessage);
10933}
10934
10935// Disable map timings when opposing team has human players. The time is too much depending on opposing team that is is comparable.
10936
10937MapTimingEnabled()
10938{
10939 return MapTimingBlocked || CurrentGamemodeID == GAMEMODE_COOP || CurrentGamemodeID == GAMEMODE_SURVIVAL || CurrentGamemodeID == GAMEMODE_REALISM || CurrentGamemodeID == GAMEMODE_OTHERMUTATIONS;
10940}
10941
10942public StartMapTiming()
10943{
10944 if (!MapTimingEnabled() || MapTimingStartTime != 0.0 || StatsDisabled())
10945 {
10946 return;
10947 }
10948
10949 MapTimingStartTime = GetEngineTime();
10950
10951 new ClientTeam, maxplayers = GetMaxClients();
10952 decl String:ClientID[MAX_LINE_WIDTH];
10953
10954 ClearTrie(MapTimingSurvivors);
10955 ClearTrie(MapTimingInfected);
10956
10957 new bool:SoundsEnabled = EnableSounds_Maptime_Start && GetConVarBool(cvar_SoundsEnabled);
10958
10959 for (new i = 1; i <= maxplayers; i++)
10960 {
10961 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
10962 {
10963 ClientTeam = GetClientTeam(i);
10964
10965 if (ClientTeam == TEAM_SURVIVORS)
10966 {
10967 GetClientRankAuthString(i, ClientID, sizeof(ClientID));
10968 SetTrieValue(MapTimingSurvivors, ClientID, 1, true);
10969
10970 if (SoundsEnabled)
10971 EmitSoundToClient(i, StatsSound_MapTime_Start);
10972 }
10973 else if (ClientTeam == TEAM_INFECTED)
10974 {
10975 GetClientRankAuthString(i, ClientID, sizeof(ClientID));
10976 SetTrieValue(MapTimingInfected, ClientID, 1, true);
10977 }
10978 }
10979 }
10980}
10981
10982GetCurrentDifficulty()
10983{
10984 decl String:Difficulty[MAX_LINE_WIDTH];
10985 GetConVarString(cvar_Difficulty, Difficulty, sizeof(Difficulty));
10986
10987 if (StrEqual(Difficulty, "normal", false)) return 1;
10988 else if (StrEqual(Difficulty, "hard", false)) return 2;
10989 else if (StrEqual(Difficulty, "impossible", false)) return 3;
10990 else return 0;
10991}
10992
10993public StopMapTiming()
10994{
10995 if (!MapTimingEnabled() || MapTimingStartTime <= 0.0 || StatsDisabled())
10996 {
10997 return;
10998 }
10999
11000 new Float:TotalTime = GetEngineTime() - MapTimingStartTime;
11001 MapTimingStartTime = -1.0;
11002 MapTimingBlocked = true;
11003
11004 new Handle:dp = INVALID_HANDLE;
11005 new ClientTeam, enabled, maxplayers = GetMaxClients();
11006 decl String:ClientID[MAX_LINE_WIDTH], String:MapName[MAX_LINE_WIDTH], String:query[512];
11007
11008 GetCurrentMap(MapName, sizeof(MapName));
11009
11010 new i, PlayerCounter = 0, InfectedCounter = (CurrentGamemodeID == GAMEMODE_VERSUS || CurrentGamemodeID == GAMEMODE_SCAVENGE ? 0 : 1);
11011
11012 for (i = 1; i <= maxplayers; i++)
11013 {
11014 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
11015 {
11016 ClientTeam = GetClientTeam(i);
11017 GetClientRankAuthString(i, ClientID, sizeof(ClientID));
11018
11019 if (ClientTeam == TEAM_SURVIVORS && GetTrieValue(MapTimingSurvivors, ClientID, enabled))
11020 {
11021 if (enabled)
11022 PlayerCounter++;
11023 }
11024 else if (ClientTeam == TEAM_INFECTED)
11025 {
11026 InfectedCounter++;
11027 if (GetTrieValue(MapTimingInfected, ClientID, enabled))
11028 {
11029 if (enabled)
11030 PlayerCounter++;
11031 }
11032 }
11033 }
11034 }
11035
11036 // Game ended because all of the infected team left the server... don't record the time!
11037 if (InfectedCounter <= 0)
11038 return;
11039
11040 new GameDifficulty = GetCurrentDifficulty();
11041
11042 for (i = 1; i <= maxplayers; i++)
11043 {
11044 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
11045 {
11046 ClientTeam = GetClientTeam(i);
11047
11048 if (ClientTeam == TEAM_SURVIVORS)
11049 {
11050 GetClientRankAuthString(i, ClientID, sizeof(ClientID));
11051
11052 if (GetTrieValue(MapTimingSurvivors, ClientID, enabled))
11053 {
11054 if (enabled)
11055 {
11056 dp = CreateDataPack();
11057
11058 WritePackString(dp, MapName);
11059 WritePackCell(dp, CurrentGamemodeID);
11060 WritePackString(dp, ClientID);
11061 WritePackFloat(dp, TotalTime);
11062 WritePackCell(dp, i);
11063 WritePackCell(dp, PlayerCounter);
11064 WritePackCell(dp, GameDifficulty);
11065 WritePackString(dp, CurrentMutation);
11066
11067 Format(query, sizeof(query), "SELECT time FROM %stimedmaps WHERE map = '%s' AND gamemode = %i AND difficulty = %i AND mutation = '%s' AND steamid = '%s'", DbPrefix, MapName, CurrentGamemodeID, GameDifficulty, CurrentMutation, ClientID);
11068
11069 SQL_TQuery(db, UpdateMapTimingStat, query, dp);
11070 }
11071 }
11072 }
11073 }
11074 }
11075
11076 ClearTrie(MapTimingSurvivors);
11077}
11078
11079public UpdateMapTimingStat(Handle:owner, Handle:hndl, const String:error[], any:dp)
11080{
11081 if (hndl == INVALID_HANDLE)
11082 {
11083 if (dp != INVALID_HANDLE)
11084 CloseHandle(dp);
11085
11086 LogError("UpdateMapTimingStat Query failed: %s", error);
11087 return;
11088 }
11089
11090 ResetPack(dp);
11091
11092 decl String:MapName[MAX_LINE_WIDTH], String:ClientID[MAX_LINE_WIDTH], String:query[512], String:TimeLabel[32], String:Mutation[MAX_LINE_WIDTH];
11093 new GamemodeID, Float:TotalTime, Float:OldTime, Client, PlayerCounter, GameDifficulty;
11094
11095 ReadPackString(dp, MapName, sizeof(MapName));
11096 GamemodeID = ReadPackCell(dp);
11097 ReadPackString(dp, ClientID, sizeof(ClientID));
11098 TotalTime = ReadPackFloat(dp);
11099 Client = ReadPackCell(dp);
11100 PlayerCounter = ReadPackCell(dp);
11101 GameDifficulty = ReadPackCell(dp);
11102 ReadPackString(dp, Mutation, sizeof(Mutation));
11103
11104 CloseHandle(dp);
11105
11106 // Return if client is not a human player
11107 if (IsClientBot(Client) || !IsClientInGame(Client))
11108 return;
11109
11110 new Mode = GetConVarInt(cvar_AnnounceMode);
11111
11112 if (SQL_GetRowCount(hndl) > 0)
11113 {
11114 SQL_FetchRow(hndl);
11115 OldTime = SQL_FetchFloat(hndl, 0);
11116
11117 if ((CurrentGamemodeID != GAMEMODE_SURVIVAL && OldTime <= TotalTime) || (CurrentGamemodeID == GAMEMODE_SURVIVAL && OldTime >= TotalTime))
11118 {
11119 if (Mode)
11120 {
11121 SetTimeLabel(OldTime, TimeLabel, sizeof(TimeLabel));
11122 StatsPrintToChat(Client, "You did not improve your best time \x04%s \x01to finish this map!", TimeLabel);
11123 }
11124
11125 Format(query, sizeof(query), "UPDATE %stimedmaps SET plays = plays + 1, modified = NOW() WHERE map = '%s' AND gamemode = %i AND difficulty = %i AND mutation = '%s' AND steamid = '%s'", DbPrefix, MapName, GamemodeID, GameDifficulty, Mutation, ClientID);
11126 }
11127 else
11128 {
11129 if (Mode)
11130 {
11131 SetTimeLabel(TotalTime, TimeLabel, sizeof(TimeLabel));
11132 StatsPrintToChat(Client, "Your new best time to finish this map is \x04%s\x01!", TimeLabel);
11133 }
11134
11135 Format(query, sizeof(query), "UPDATE %stimedmaps SET plays = plays + 1, time = %f, players = %i, modified = NOW() WHERE map = '%s' AND gamemode = %i AND difficulty = %i AND mutation = '%s' AND steamid = '%s'", DbPrefix, TotalTime, PlayerCounter, MapName, GamemodeID, GameDifficulty, Mutation, ClientID);
11136
11137 if (EnableSounds_Maptime_Improve && GetConVarBool(cvar_SoundsEnabled))
11138 EmitSoundToClient(Client, StatsSound_MapTime_Improve);
11139 }
11140 }
11141 else
11142 {
11143 if (Mode)
11144 {
11145 SetTimeLabel(TotalTime, TimeLabel, sizeof(TimeLabel));
11146 StatsPrintToChat(Client, "It took \x04%s \x01to finish this map!", TimeLabel);
11147 }
11148
11149 Format(query, sizeof(query), "INSERT INTO %stimedmaps (map, gamemode, difficulty, mutation, steamid, plays, time, players, modified, created) VALUES ('%s', %i, %i, '%s', '%s', 1, %f, %i, NOW(), NOW())", DbPrefix, MapName, GamemodeID, GameDifficulty, Mutation, ClientID, TotalTime, PlayerCounter);
11150 }
11151
11152 SendSQLUpdate(query);
11153}
11154
11155public SetTimeLabel(Float:TheSeconds, String:TimeLabel[], maxsize)
11156{
11157 new FlooredSeconds = RoundToFloor(TheSeconds);
11158 new FlooredSecondsMod = FlooredSeconds % 60;
11159 new Float:Seconds = TheSeconds - float(FlooredSeconds) + float(FlooredSecondsMod);
11160 new Minutes = (TheSeconds < 60.0 ? 0 : RoundToNearest(float(FlooredSeconds - FlooredSecondsMod) / 60));
11161 new MinutesMod = Minutes % 60;
11162 new Hours = (Minutes < 60 ? 0 : RoundToNearest(float(Minutes - MinutesMod) / 60));
11163 Minutes = MinutesMod;
11164
11165 if (Hours > 0)
11166 Format(TimeLabel, maxsize, "%ih %im %.1fs", Hours, Minutes, Seconds);
11167 else if (Minutes > 0)
11168 Format(TimeLabel, maxsize, "%i min %.1f sec", Minutes, Seconds);
11169 else
11170 Format(TimeLabel, maxsize, "%.1f seconds", Seconds);
11171}
11172
11173public DisplayRankVote(client)
11174{
11175 DisplayYesNoPanel(client, RANKVOTE_QUESTION, RankVotePanelHandler, RoundToNearest(GetConVarFloat(cvar_RankVoteTime)));
11176}
11177
11178// Initialize RANKVOTE
11179public InitializeRankVote(client)
11180{
11181 if (StatsDisabled())
11182 {
11183 if (client == 0)
11184 {
11185 PrintToConsole(0, "[RANK] Cannot initiate vote when the plugin is disabled!");
11186 }
11187 else
11188 {
11189 StatsPrintToChatPreFormatted2(client, true, "Cannot initiate vote when the plugin is disabled!");
11190 }
11191
11192 return;
11193 }
11194
11195 // No TEAM gamemodes are allowed
11196 if (!IsTeamGamemode())
11197 {
11198 if (client == 0)
11199 {
11200 PrintToConsole(0, "[RANK] The Rank Vote is not enabled in this gamemode!");
11201 }
11202 else
11203 {
11204 if (ServerVersion == SERVER_VERSION_L4D1)
11205 {
11206 StatsPrintToChatPreFormatted2(client, true, "The \x04Rank Vote \x01is enabled in \x03Versus \x01gamemode!");
11207 }
11208 else
11209 {
11210 StatsPrintToChatPreFormatted2(client, true, "The \x04Rank Vote \x01is enabled in \x03Versus\x01, \x03Realism Versus \x01and \x03Scavenge \x01gamemodes!");
11211 }
11212 }
11213
11214 return;
11215 }
11216
11217 if (RankVoteTimer != INVALID_HANDLE)
11218 {
11219 if (client > 0)
11220 {
11221 DisplayRankVote(client);
11222 }
11223 else
11224 {
11225 PrintToConsole(client, "[RANK] The Rank Vote is already initiated!");
11226 }
11227
11228 return;
11229 }
11230
11231 new bool:IsAdmin = (client > 0 ? ((GetUserFlagBits(client) & ADMFLAG_GENERIC) == ADMFLAG_GENERIC) : true);
11232
11233 new team;
11234 decl String:ClientID[MAX_LINE_WIDTH];
11235
11236 if (!IsAdmin && client > 0 && GetTrieValue(PlayerRankVoteTrie, ClientID, team))
11237 {
11238 StatsPrintToChatPreFormatted2(client, true, "You can initiate a \x04Rank Vote \x01only once per map!");
11239 return;
11240 }
11241
11242 if (!IsAdmin && client > 0)
11243 {
11244 GetClientRankAuthString(client, ClientID, sizeof(ClientID));
11245 SetTrieValue(PlayerRankVoteTrie, ClientID, 1, true);
11246 }
11247
11248 RankVoteTimer = CreateTimer(GetConVarFloat(cvar_RankVoteTime), timer_RankVote);
11249
11250 new i;
11251
11252 for (i = 0; i <= MAXPLAYERS; i++)
11253 {
11254 PlayerRankVote[i] = RANKVOTE_NOVOTE;
11255 }
11256
11257 new maxplayers = GetMaxClients();
11258
11259 for (i = 1; i <= maxplayers; i++)
11260 {
11261 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
11262 {
11263 team = GetClientTeam(i);
11264
11265 if (team == TEAM_SURVIVORS || team == TEAM_INFECTED)
11266 {
11267 DisplayRankVote(i);
11268 }
11269 }
11270 }
11271
11272 if (client > 0)
11273 {
11274 decl String:UserName[MAX_LINE_WIDTH];
11275 GetClientName(client, UserName, sizeof(UserName));
11276
11277 StatsPrintToChatAll2(true, "The \x04Rank Vote \x01was initiated by \x05%s\x01!", UserName);
11278 }
11279 else
11280 {
11281 StatsPrintToChatAllPreFormatted2(true, "The \x04Rank Vote \x01was initiated from Server Console!");
11282 }
11283}
11284
11285/*
11286 From plugin:
11287 name = "L4D2 Score/Team Manager",
11288 author = "Downtown1 & AtomicStryker",
11289 description = "Manage teams and scores in L4D2",
11290 version = 1.1.2,
11291 url = "http://forums.alliedmods.net/showthread.php?p=1029519"
11292*/
11293
11294stock bool:ChangeRankPlayerTeam(client, team)
11295{
11296 if(GetClientTeam(client) == team) return true;
11297
11298 if(team != TEAM_SURVIVORS)
11299 {
11300 //we can always swap to infected or spectator, it has no actual limit
11301 ChangeClientTeam(client, team);
11302 return true;
11303 }
11304
11305 if(GetRankTeamHumanCount(team) == GetRankTeamMaxHumans(team))
11306 return false;
11307
11308 new bot;
11309 //for survivors its more tricky
11310 for (bot = 1; bot < MaxClients + 1 && (!IsClientConnected(bot) || !IsFakeClient(bot) || (GetClientTeam(bot) != TEAM_SURVIVORS)); bot++) {}
11311
11312 if (bot == MaxClients + 1)
11313 {
11314 new String:command[] = "sb_add";
11315 new flags = GetCommandFlags(command);
11316 SetCommandFlags(command, flags & ~FCVAR_CHEAT);
11317
11318 ServerCommand("sb_add");
11319
11320 SetCommandFlags(command, flags);
11321
11322 return false;
11323 }
11324
11325 //have to do this to give control of a survivor bot
11326 SDKCall(L4DStatsSHS, bot, client);
11327 SDKCall(L4DStatsTOB, client, true);
11328
11329 return true;
11330}
11331
11332/*
11333 From plugin:
11334 name = "L4D2 Score/Team Manager",
11335 author = "Downtown1 & AtomicStryker",
11336 description = "Manage teams and scores in L4D2",
11337 version = 1.1.2,
11338 url = "http://forums.alliedmods.net/showthread.php?p=1029519"
11339*/
11340
11341stock bool:IsRankClientInGameHuman(client)
11342{
11343 if (client > 0) return IsClientInGame(client) && !IsFakeClient(client);
11344 else return false;
11345}
11346
11347/*
11348 From plugin:
11349 name = "L4D2 Score/Team Manager",
11350 author = "Downtown1 & AtomicStryker",
11351 description = "Manage teams and scores in L4D2",
11352 version = 1.1.2,
11353 url = "http://forums.alliedmods.net/showthread.php?p=1029519"
11354*/
11355
11356stock GetRankTeamHumanCount(team)
11357{
11358 new humans = 0;
11359
11360 for(new i = 1; i < MaxClients + 1; i++)
11361 {
11362 if(IsRankClientInGameHuman(i) && GetClientTeam(i) == team)
11363 {
11364 humans++;
11365 }
11366 }
11367
11368 return humans;
11369}
11370
11371stock GetRankTeamMaxHumans(team)
11372{
11373 switch (team)
11374 {
11375 case TEAM_SURVIVORS:
11376 return GetConVarInt(cvar_SurvivorLimit);
11377 case TEAM_INFECTED:
11378 return GetConVarInt(cvar_InfectedLimit);
11379 case TEAM_SPECTATORS:
11380 return MaxClients;
11381 }
11382
11383 return -1;
11384}
11385
11386GetClientRankAuthString(client, String:auth[], maxlength)
11387{
11388 if (GetConVarInt(cvar_Lan))
11389 {
11390 GetClientAuthString(client, auth, maxlength);
11391
11392 if (!StrEqual(auth, "BOT", false))
11393 {
11394 GetClientIP(client, auth, maxlength);
11395 }
11396 }
11397 else
11398 {
11399 GetClientAuthString(client, auth, maxlength);
11400
11401 if (StrEqual(auth, "STEAM_ID_LAN", false))
11402 {
11403 GetClientIP(client, auth, maxlength);
11404 }
11405 }
11406}
11407
11408public StatsPrintToChatAll(const String:Message[], any:...)
11409{
11410 new String:FormattedMessage[MAX_MESSAGE_WIDTH];
11411 VFormat(FormattedMessage, sizeof(FormattedMessage), Message, 2);
11412
11413 StatsPrintToChatAllPreFormatted(FormattedMessage);
11414}
11415
11416public StatsPrintToChatAll2(bool:Forced, const String:Message[], any:...)
11417{
11418 new String:FormattedMessage[MAX_MESSAGE_WIDTH];
11419 VFormat(FormattedMessage, sizeof(FormattedMessage), Message, 2);
11420
11421 StatsPrintToChatAllPreFormatted2(Forced, FormattedMessage);
11422}
11423
11424public StatsPrintToChatAllPreFormatted(const String:Message[])
11425{
11426 new maxplayers = GetMaxClients();
11427
11428 for (new i = 1; i <= maxplayers; i++)
11429 {
11430 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
11431 {
11432 StatsPrintToChatPreFormatted(i, Message);
11433 }
11434 }
11435}
11436
11437public StatsPrintToChatAllPreFormatted2(bool:Forced, const String:Message[])
11438{
11439 new maxplayers = GetMaxClients();
11440
11441 for (new i = 1; i <= maxplayers; i++)
11442 {
11443 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
11444 {
11445 StatsPrintToChatPreFormatted2(i, Forced, Message);
11446 }
11447 }
11448}
11449
11450public StatsPrintToChat(Client, const String:Message[], any:...)
11451{
11452 // CHECK IF CLIENT HAS MUTED THE PLUGIN
11453 if (ClientRankMute[Client])
11454 {
11455 return;
11456 }
11457
11458 new String:FormattedMessage[MAX_MESSAGE_WIDTH];
11459 VFormat(FormattedMessage, sizeof(FormattedMessage), Message, 3);
11460
11461 StatsPrintToChatPreFormatted(Client, FormattedMessage);
11462}
11463
11464public StatsPrintToChat2(Client, bool:Forced, const String:Message[], any:...)
11465{
11466 // CHECK IF CLIENT HAS MUTED THE PLUGIN
11467 if (!Forced && ClientRankMute[Client])
11468 {
11469 return;
11470 }
11471
11472 new String:FormattedMessage[MAX_MESSAGE_WIDTH];
11473 VFormat(FormattedMessage, sizeof(FormattedMessage), Message, 4);
11474
11475 StatsPrintToChatPreFormatted2(Client, Forced, FormattedMessage);
11476}
11477
11478public StatsPrintToChatPreFormatted(Client, const String:Message[])
11479{
11480 StatsPrintToChatPreFormatted2(Client, false, Message);
11481}
11482
11483public StatsPrintToChatPreFormatted2(Client, bool:Forced, const String:Message[])
11484{
11485 // CHECK IF CLIENT HAS MUTED THE PLUGIN
11486 if (!Forced && ClientRankMute[Client])
11487 {
11488 return;
11489 }
11490
11491 PrintToChat(Client, "\x04[\x03RANK\x04] \x01%s", Message);
11492}
11493
11494stock StatsGetClientTeam(client)
11495{
11496 if (client <= 0 || !IsClientConnected(client))
11497 {
11498 return TEAM_UNDEFINED;
11499 }
11500
11501 if (IsFakeClient(client) || IsClientInGame(client))
11502 {
11503 return GetClientTeam(client);
11504 }
11505
11506 return TEAM_UNDEFINED;
11507}
11508
11509bool:UpdateServerSettings(Client, const String:Key[], const String:Value[], const String:Desc[])
11510{
11511 new Handle:statement = INVALID_HANDLE;
11512 decl String:error[1024], String:query[2048];
11513
11514 // Add a row if it does not previously exist
11515 if (!DoFastQuery(Client, "INSERT IGNORE INTO %sserver_settings SET sname = '%s', svalue = ''", DbPrefix, Key))
11516 {
11517 PrintToConsole(Client, "[RANK] %s: Setting a new MOTD value failure!", Desc);
11518 return false;
11519 }
11520
11521 Format(query, sizeof(query), "UPDATE %sserver_settings SET svalue = ? WHERE sname = '%s'", DbPrefix, Key);
11522
11523 statement = SQL_PrepareQuery(db, query, error, sizeof(error));
11524
11525 if (statement == INVALID_HANDLE)
11526 {
11527 PrintToConsole(Client, "[RANK] %s: Update failed! (Reason: Cannot create SQL statement)");
11528 return false;
11529 }
11530
11531 new bool:retval = true;
11532 SQL_BindParamString(statement, 0, Value, false);
11533
11534 if (!SQL_Execute(statement))
11535 {
11536 if (SQL_GetError(db, error, sizeof(error)))
11537 {
11538 PrintToConsole(Client, "[RANK] %s: Update failed! (Error = \"%s\")", Desc, error);
11539 LogError("%s: Update failed! (Error = \"%s\")", Desc, error);
11540 }
11541 else
11542 {
11543 PrintToConsole(Client, "[RANK] %s: Update failed!", Desc);
11544 LogError("%s: Update failed!", Desc);
11545 }
11546
11547 retval = false;
11548 }
11549 else
11550 {
11551 PrintToConsole(Client, "[RANK] %s: Update successful!", Desc);
11552
11553 if (StrEqual(Key, "motdmessage", false))
11554 {
11555 strcopy(MessageOfTheDay, sizeof(MessageOfTheDay), Value);
11556 ShowMOTDAll();
11557 }
11558 }
11559
11560 CloseHandle(statement);
11561
11562 return retval;
11563}
11564
11565ShowMOTDAll()
11566{
11567 new maxplayers = GetMaxClients();
11568
11569 for (new i = 1; i <= maxplayers; i++)
11570 {
11571 if (IsClientConnected(i) && IsClientInGame(i) && !IsClientBot(i))
11572 {
11573 ShowMOTD(i);
11574 }
11575 }
11576}
11577
11578ShowMOTD(client, bool:forced=false)
11579{
11580 if (!forced && !GetConVarBool(cvar_AnnounceMotd))
11581 {
11582 return;
11583 }
11584
11585 StatsPrintToChat2(client, forced, "\x05%s: \x01%s", MOTD_TITLE, MessageOfTheDay);
11586}
11587
11588AnnouncePlayerConnect(client)
11589{
11590 if (!GetConVarBool(cvar_AnnouncePlayerJoined))
11591 {
11592 return;
11593 }
11594
11595 DoShowPlayerJoined(client);
11596}
11597
11598ReadDb()
11599{
11600 ReadDbMotd();
11601}
11602
11603ReadDbMotd()
11604{
11605 decl String:query[512];
11606 Format(query, sizeof(query), "SELECT svalue FROM %sserver_settings WHERE sname = 'motdmessage' LIMIT 1", DbPrefix);
11607 SQL_TQuery(db, ReadDbMotdCallback, query);
11608}
11609
11610public ReadDbMotdCallback(Handle:owner, Handle:hndl, const String:error[], any:data)
11611{
11612 if (hndl == INVALID_HANDLE)
11613 {
11614 LogError("ReadDbMotdCallback Query failed: %s", error);
11615 return;
11616 }
11617
11618 if (SQL_FetchRow(hndl))
11619 {
11620 SQL_FetchString(hndl, 0, MessageOfTheDay, sizeof(MessageOfTheDay));
11621 }
11622}