Advertisement
Guest User

Untitled

a guest
Sep 27th, 2019
135
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 128.33 KB | None | 0 0
  1. /**
  2.  *  L4D2_skill_detect
  3.  *
  4.  *  Plugin to detect and forward reports about 'skill'-actions,
  5.  *  such as skeets, crowns, levels, dp's.
  6.  *  Works in campaign and versus modes.
  7.  *
  8.  *  m_isAttemptingToPounce  can only be trusted for
  9.  *  AI hunters -- for human hunters this gets cleared
  10.  *  instantly on taking killing damage
  11.  *
  12.  *  Shotgun skeets and teamskeets are only counted if the
  13.  *  added up damage to pounce_interrupt is done by shotguns
  14.  *  only. 'Skeeting' chipped hunters shouldn't count, IMO.
  15.  *
  16.  *  This performs global forward calls to:
  17.  *      OnSkeet( survivor, hunter )
  18.  *      OnSkeetMelee( survivor, hunter )
  19.  *      OnSkeetGL( survivor, hunter )
  20.  *      OnSkeetSniper( survivor, hunter )
  21.  *      OnSkeetHurt( survivor, hunter, damage, isOverkill )
  22.  *      OnSkeetMeleeHurt( survivor, hunter, damage, isOverkill )
  23.  *      OnSkeetSniperHurt( survivor, hunter, damage, isOverkill )
  24.  *      OnHunterDeadstop( survivor, hunter )
  25.  *      OnBoomerPop( survivor, boomer, shoveCount, Float:timeAlive )
  26.  *      OnChargerLevel( survivor, charger )
  27.  *      OnChargerLevelHurt( survivor, charger, damage )
  28.  *      OnWitchCrown( survivor, damage )
  29.  *      OnWitchCrownHurt( survivor, damage, chipdamage )
  30.  *      OnTongueCut( survivor, smoker )
  31.  *      OnSmokerSelfClear( survivor, smoker, withShove )
  32.  *      OnTankRockSkeeted( survivor, tank )
  33.  *      OnTankRockEaten( tank, survivor )
  34.  *      OnHunterHighPounce( hunter, victim, actualDamage, Float:calculatedDamage, Float:height, bool:bReportedHigh, bool:bPlayerIncapped )
  35.  *      OnJockeyHighPounce( jockey, victim, Float:height, bool:bReportedHigh )
  36.  *      OnDeathCharge( charger, victim, Float: height, Float: distance, wasCarried )
  37.  *      OnSpecialShoved( survivor, infected, zombieClass )
  38.  *      OnSpecialClear( clearer, pinner, pinvictim, zombieClass, Float:timeA, Float:timeB, withShove )
  39.  *      OnBoomerVomitLanded( boomer, amount )
  40.  *      OnBunnyHopStreak( survivor, streak, Float:maxVelocity )
  41.  *      OnCarAlarmTriggered( survivor, infected, reason )
  42.  *
  43.  *      OnDeathChargeAssist( assister, charger, victim )    [ not done yet ]
  44.  *      OnBHop( player, isInfected, speed, streak )         [ not done yet ]
  45.  *
  46.  *  Where survivor == -2 if it was a team effort, -1 or 0 if unknown or invalid client.
  47.  *  damage is the amount of damage done (that didn't add up to skeeting damage),
  48.  *  and isOverkill indicates whether the shot would've been a skeet if the hunter
  49.  *  had not been chipped.
  50.  *
  51.  *  @author         Tabun
  52.  *  @libraryname    skill_detect
  53.  */
  54.  
  55. #pragma semicolon 1
  56.  
  57. #include <sourcemod>
  58. #include <sdkhooks>
  59. #include <sdktools>
  60. #include <l4d2_direct>
  61.  
  62. #define PLUGIN_VERSION "0.9.19"
  63.  
  64. #define IS_VALID_CLIENT(%1)     (%1 > 0 && %1 <= MaxClients)
  65. #define IS_SURVIVOR(%1)         (GetClientTeam(%1) == 2)
  66. #define IS_INFECTED(%1)         (GetClientTeam(%1) == 3)
  67. #define IS_VALID_INGAME(%1)     (IS_VALID_CLIENT(%1) && IsClientInGame(%1))
  68. #define IS_VALID_SURVIVOR(%1)   (IS_VALID_INGAME(%1) && IS_SURVIVOR(%1))
  69. #define IS_VALID_INFECTED(%1)   (IS_VALID_INGAME(%1) && IS_INFECTED(%1))
  70. #define IS_SURVIVOR_ALIVE(%1)   (IS_VALID_SURVIVOR(%1) && IsPlayerAlive(%1))
  71. #define IS_INFECTED_ALIVE(%1)   (IS_VALID_INFECTED(%1) && IsPlayerAlive(%1))
  72. #define QUOTES(%1)              (%1)
  73.  
  74. #define SHOTGUN_BLAST_TIME      0.1
  75. #define POUNCE_CHECK_TIME       0.1
  76. #define HOP_CHECK_TIME          0.1
  77. #define HOPEND_CHECK_TIME       0.1     // after streak end (potentially) detected, to check for realz?
  78. #define SHOVE_TIME              0.05
  79. #define MAX_CHARGE_TIME         12.0    // maximum time to pass before charge checking ends
  80. #define CHARGE_CHECK_TIME       0.25    // check interval for survivors flying from impacts
  81. #define CHARGE_END_CHECK        2.5     // after client hits ground after getting impact-charged: when to check whether it was a death
  82. #define CHARGE_END_RECHECK      3.0     // safeguard wait to recheck on someone getting incapped out of bounds
  83. #define VOMIT_DURATION_TIME     2.25    // how long the boomer vomit stream lasts -- when to check for boom count
  84. #define ROCK_CHECK_TIME         0.34    // how long to wait after rock entity is destroyed before checking for skeet/eat (high to avoid lag issues)
  85. #define CARALARM_MIN_TIME       0.11    // maximum time after touch/shot => alarm to connect the two events (test this for LAG)
  86.  
  87. #define WITCH_CHECK_TIME        0.1     // time to wait before checking for witch crown after shoots fired
  88. #define WITCH_DELETE_TIME       0.15    // time to wait before deleting entry from witch trie after entity is destroyed
  89.  
  90. #define MIN_DC_TRIGGER_DMG      300     // minimum amount a 'trigger' / drown must do before counted as a death action
  91. #define MIN_DC_FALL_DMG         175     // minimum amount of fall damage counts as death-falling for a deathcharge
  92. #define WEIRD_FLOW_THRESH       900.0   // -9999 seems to be break flow.. but meh
  93. #define MIN_FLOWDROPHEIGHT      350.0   // minimum height a survivor has to have dropped before a WEIRD_FLOW value is treated as a DC spot
  94. #define MIN_DC_RECHECK_DMG      100     // minimum damage from map to have taken on first check, to warrant recheck
  95.  
  96. #define HOP_ACCEL_THRESH        0.01    // bhop speed increase must be higher than this for it to count as part of a hop streak
  97.  
  98. #define ZC_SMOKER       1
  99. #define ZC_BOOMER       2
  100. #define ZC_HUNTER       3
  101. #define ZC_JOCKEY       5
  102. #define ZC_CHARGER      6
  103. #define ZC_TANK         8
  104. #define HITGROUP_HEAD   1
  105.  
  106. #define DMG_CRUSH               (1 << 0)        // crushed by falling or moving object.
  107. #define DMG_BULLET              (1 << 1)        // shot
  108. #define DMG_SLASH               (1 << 2)        // cut, clawed, stabbed
  109. #define DMG_CLUB                (1 << 7)        // crowbar, punch, headbutt
  110. #define DMG_BUCKSHOT            (1 << 29)       // not quite a bullet. Little, rounder, different.
  111.  
  112. #define DMGARRAYEXT     7                       // MAXPLAYERS+# -- extra indices in witch_dmg_array + 1
  113.  
  114. #define CUT_SHOVED      1                       // smoker got shoved
  115. #define CUT_SHOVEDSURV  2                       // survivor got shoved
  116. #define CUT_KILL        3                       // reason for tongue break (release_type)
  117. #define CUT_SLASH       4                       // this is used for others shoving a survivor free too, don't trust .. it involves tongue damage?
  118.  
  119. #define VICFLG_CARRIED          (1 << 0)        // was the one that the charger carried (not impacted)
  120. #define VICFLG_FALL             (1 << 1)        // flags stored per charge victim, to check for deathchargeroony -- fallen
  121. #define VICFLG_DROWN            (1 << 2)        // drowned
  122. #define VICFLG_HURTLOTS         (1 << 3)        // whether the victim was hurt by 400 dmg+ at once
  123. #define VICFLG_TRIGGER          (1 << 4)        // killed by trigger_hurt
  124. #define VICFLG_AIRDEATH         (1 << 5)        // died before they hit the ground (impact check)
  125. #define VICFLG_KILLEDBYOTHER    (1 << 6)        // if the survivor was killed by an SI other than the charger
  126. #define VICFLG_WEIRDFLOW        (1 << 7)        // when survivors get out of the map and such
  127. #define VICFLG_WEIRDFLOWDONE    (1 << 8)        //      checked, don't recheck for this
  128.  
  129. #define REP_SKEET               (1 << 0)
  130. #define REP_HURTSKEET           (1 << 1)
  131. #define REP_LEVEL               (1 << 2)
  132. #define REP_HURTLEVEL           (1 << 3)
  133. #define REP_CROWN               (1 << 4)
  134. #define REP_DRAWCROWN           (1 << 5)
  135. #define REP_TONGUECUT           (1 << 6)
  136. #define REP_SELFCLEAR           (1 << 7)
  137. #define REP_SELFCLEARSHOVE      (1 << 8)
  138. #define REP_ROCKSKEET           (1 << 9)
  139. #define REP_DEADSTOP            (1 << 10)
  140. #define REP_POP                 (1 << 11)
  141. #define REP_SHOVE               (1 << 12)
  142. #define REP_HUNTERDP            (1 << 13)
  143. #define REP_JOCKEYDP            (1 << 14)
  144. #define REP_DEATHCHARGE         (1 << 15)
  145. #define REP_DC_ASSIST           (1 << 16)
  146. #define REP_INSTACLEAR          (1 << 17)       // 131072
  147. #define REP_BHOPSTREAK          (1 << 18)       // 262144
  148. #define REP_CARALARM            (1 << 19)       // 524288
  149.  
  150. #define REP_DEFAULT             "581685"        // (REP_SKEET | REP_LEVEL | REP_CROWN | REP_DRAWCROWN | REP_HUNTERDP | REP_JOCKEYDP | REP_DEATHCHARGE | REP_CARALARM)
  151.                                                 //  1 4 16 32 8192 16384 32768 65536 (122933 with ASSIST, 57397 without); 131072 for instaclears + 524288 for car alarm
  152.  
  153.  
  154. // trie values: weapon type
  155. enum strWeaponType
  156. {
  157.     WPTYPE_SNIPER,
  158.     WPTYPE_MAGNUM,
  159.     WPTYPE_GL
  160. };
  161.  
  162. // trie values: OnEntityCreated classname
  163. enum strOEC
  164. {
  165.     OEC_WITCH,
  166.     OEC_TANKROCK,
  167.     OEC_TRIGGER,
  168.     OEC_CARALARM,
  169.     OEC_CARGLASS
  170. };
  171.  
  172. // trie values: special abilities
  173. enum strAbility
  174. {
  175.     ABL_HUNTERLUNGE,
  176.     ABL_ROCKTHROW
  177. };
  178.  
  179. enum strRockData
  180. {
  181.     rckDamage,
  182.     rckTank,
  183.     rckSkeeter
  184. };
  185.  
  186. // witch array entries (maxplayers+index)
  187. enum strWitchArray
  188. {
  189.     WTCH_NONE,
  190.     WTCH_HEALTH,
  191.     WTCH_GOTSLASH,
  192.     WTCH_STARTLED,
  193.     WTCH_CROWNER,
  194.     WTCH_CROWNSHOT,
  195.     WTCH_CROWNTYPE
  196. };
  197.  
  198. enum enAlarmReasons
  199. {
  200.     CALARM_UNKNOWN,
  201.     CALARM_HIT,
  202.     CALARM_TOUCHED,
  203.     CALARM_EXPLOSION,
  204.     CALARM_BOOMER
  205. };
  206.  
  207. new const String: g_csSIClassName[][] =
  208. {
  209.     "",
  210.     "smoker",
  211.     "boomer",
  212.     "hunter",
  213.     "spitter",
  214.     "jockey",
  215.     "charger",
  216.     "witch",
  217.     "tank"
  218. };
  219.  
  220. new     bool:           g_bLateLoad                                         = false;
  221.  
  222. new     Handle:         g_hForwardSkeet                                     = INVALID_HANDLE;
  223. new     Handle:         g_hForwardSkeetHurt                                 = INVALID_HANDLE;
  224. new     Handle:         g_hForwardSkeetMelee                                = INVALID_HANDLE;
  225. new     Handle:         g_hForwardSkeetMeleeHurt                            = INVALID_HANDLE;
  226. new     Handle:         g_hForwardSkeetSniper                               = INVALID_HANDLE;
  227. new     Handle:         g_hForwardSkeetSniperHurt                           = INVALID_HANDLE;
  228. new     Handle:         g_hForwardSkeetGL                                   = INVALID_HANDLE;
  229. new     Handle:         g_hForwardHunterDeadstop                            = INVALID_HANDLE;
  230. new     Handle:         g_hForwardSIShove                                   = INVALID_HANDLE;
  231. new     Handle:         g_hForwardBoomerPop                                 = INVALID_HANDLE;
  232. new     Handle:         g_hForwardLevel                                     = INVALID_HANDLE;
  233. new     Handle:         g_hForwardLevelHurt                                 = INVALID_HANDLE;
  234. new     Handle:         g_hForwardCrown                                     = INVALID_HANDLE;
  235. new     Handle:         g_hForwardDrawCrown                                 = INVALID_HANDLE;
  236. new     Handle:         g_hForwardTongueCut                                 = INVALID_HANDLE;
  237. new     Handle:         g_hForwardSmokerSelfClear                           = INVALID_HANDLE;
  238. new     Handle:         g_hForwardRockSkeeted                               = INVALID_HANDLE;
  239. new     Handle:         g_hForwardRockEaten                                 = INVALID_HANDLE;
  240. new     Handle:         g_hForwardHunterDP                                  = INVALID_HANDLE;
  241. new     Handle:         g_hForwardJockeyDP                                  = INVALID_HANDLE;
  242. new     Handle:         g_hForwardDeathCharge                               = INVALID_HANDLE;
  243. new     Handle:         g_hForwardClear                                     = INVALID_HANDLE;
  244. new     Handle:         g_hForwardVomitLanded                               = INVALID_HANDLE;
  245. new     Handle:         g_hForwardBHopStreak                                = INVALID_HANDLE;
  246. new     Handle:         g_hForwardAlarmTriggered                            = INVALID_HANDLE;
  247.  
  248. new     Handle:         g_hTrieWeapons                                      = INVALID_HANDLE;   // weapon check
  249. new     Handle:         g_hTrieEntityCreated                                = INVALID_HANDLE;   // getting classname of entity created
  250. new     Handle:         g_hTrieAbility                                      = INVALID_HANDLE;   // ability check
  251. new     Handle:         g_hWitchTrie                                        = INVALID_HANDLE;   // witch tracking (Crox)
  252. new     Handle:         g_hRockTrie                                         = INVALID_HANDLE;   // tank rock tracking
  253. new     Handle:         g_hCarTrie                                          = INVALID_HANDLE;   // car alarm tracking
  254.  
  255. // all SI / pinners
  256. new     Float:          g_fSpawnTime            [MAXPLAYERS + 1];                               // time the SI spawned up
  257. new     Float:          g_fPinTime              [MAXPLAYERS + 1][2];                            // time the SI pinned a target: 0 = start of pin (tongue pull, charger carry); 1 = carry end / tongue reigned in
  258. new                     g_iSpecialVictim        [MAXPLAYERS + 1];                               // current victim (set in traceattack, so we can check on death)
  259.  
  260. // hunters: skeets/pounces
  261. new                     g_iHunterShotDmgTeam    [MAXPLAYERS + 1];                               // counting shotgun blast damage for hunter, counting entire survivor team's damage
  262. new                     g_iHunterShotDmg        [MAXPLAYERS + 1][MAXPLAYERS + 1];               // counting shotgun blast damage for hunter / skeeter combo
  263. new     Float:          g_fHunterShotStart      [MAXPLAYERS + 1][MAXPLAYERS + 1];               // when the last shotgun blast on hunter started (if at any time) by an attacker
  264. new     Float:          g_fHunterTracePouncing  [MAXPLAYERS + 1];                               // time when the hunter was still pouncing (in traceattack) -- used to detect pouncing status
  265. new     Float:          g_fHunterLastShot       [MAXPLAYERS + 1];                               // when the last shotgun damage was done (by anyone) on a hunter
  266. new                     g_iHunterLastHealth     [MAXPLAYERS + 1];                               // last time hunter took any damage, how much health did it have left?
  267. new                     g_iHunterOverkill       [MAXPLAYERS + 1];                               // how much more damage a hunter would've taken if it wasn't already dead
  268. new     bool:           g_bHunterKilledPouncing [MAXPLAYERS + 1];                               // whether the hunter was killed when actually pouncing
  269. new                     g_iPounceDamage         [MAXPLAYERS + 1];                               // how much damage on last 'highpounce' done
  270. new     Float:          g_fPouncePosition       [MAXPLAYERS + 1][3];                            // position that a hunter (jockey?) pounced from (or charger started his carry)
  271.  
  272. // deadstops
  273. new     Float:          g_fVictimLastShove      [MAXPLAYERS + 1][MAXPLAYERS + 1];               // when was the player shoved last by attacker? (to prevent doubles)
  274.  
  275. // levels / charges
  276. new                     g_iChargerHealth        [MAXPLAYERS + 1];                               // how much health the charger had the last time it was seen taking damage
  277. new     Float:          g_fChargeTime           [MAXPLAYERS + 1];                               // time the charger's charge last started, or if victim, when impact started
  278. new                     g_iChargeVictim         [MAXPLAYERS + 1];                               // who got charged
  279. new     Float:          g_fChargeVictimPos      [MAXPLAYERS + 1][3];                            // location of each survivor when it got hit by the charger
  280. new                     g_iVictimCharger        [MAXPLAYERS + 1];                               // for a victim, by whom they got charge(impacted)
  281. new                     g_iVictimFlags          [MAXPLAYERS + 1];                               // flags stored per charge victim: VICFLAGS_
  282. new                     g_iVictimMapDmg         [MAXPLAYERS + 1];                               // for a victim, how much the cumulative map damage is so far (trigger hurt / drowning)
  283.  
  284. // pops
  285. new     bool:           g_bBoomerHitSomebody    [MAXPLAYERS + 1];                               // false if boomer didn't puke/exploded on anybody
  286. new                     g_iBoomerGotShoved      [MAXPLAYERS + 1];                               // count boomer was shoved at any point
  287. new                     g_iBoomerVomitHits      [MAXPLAYERS + 1];                               // how many booms in one vomit so far
  288.  
  289. // crowns
  290. new     Float:          g_fWitchShotStart       [MAXPLAYERS + 1];                               // when the last shotgun blast from a survivor started (on any witch)
  291.  
  292. // smoker clears
  293. new     bool:           g_bSmokerClearCheck     [MAXPLAYERS + 1];                               // [smoker] smoker dies and this is set, it's a self-clear if g_iSmokerVictim is the killer
  294. new                     g_iSmokerVictim         [MAXPLAYERS + 1];                               // [smoker] the one that's being pulled
  295. new                     g_iSmokerVictimDamage   [MAXPLAYERS + 1];                               // [smoker] amount of damage done to a smoker by the one he pulled
  296. new     bool:           g_bSmokerShoved         [MAXPLAYERS + 1];                               // [smoker] set if the victim of a pull manages to shove the smoker
  297.  
  298. // rocks
  299. new                     g_iTankRock             [MAXPLAYERS + 1];                               // rock entity per tank
  300. new                     g_iRocksBeingThrown     [10];                                           // 10 tanks max simultanously throwing rocks should be ok (this stores the tank client)
  301. new                     g_iRocksBeingThrownCount                            = 0;                // so we can do a push/pop type check for who is throwing a created rock
  302.  
  303. // hops
  304. new     bool:           g_bIsHopping            [MAXPLAYERS + 1];                               // currently in a hop streak
  305. new     bool:           g_bHopCheck             [MAXPLAYERS + 1];                               // flag to check whether a hopstreak has ended (if on ground for too long.. ends)
  306. new                     g_iHops                 [MAXPLAYERS + 1];                               // amount of hops in streak
  307. new     Float:          g_fLastHop              [MAXPLAYERS + 1][3];                            // velocity vector of last jump
  308. new     Float:          g_fHopTopVelocity       [MAXPLAYERS + 1];                               // maximum velocity in hopping streak
  309.  
  310. // alarms
  311. new     Float:          g_fLastCarAlarm                                     = 0.0;              // time when last car alarm went off
  312. new                     g_iLastCarAlarmReason   [MAXPLAYERS + 1];                               // what this survivor did to set the last alarm off
  313. new                     g_iLastCarAlarmBoomer;                                                  // if a boomer triggered an alarm, remember it
  314.  
  315. // cvars
  316. new     Handle:         g_hCvarReport                                       = INVALID_HANDLE;   // cvar whether to report at all
  317. new     Handle:         g_hCvarReportFlags                                  = INVALID_HANDLE;   // cvar what to report
  318.  
  319. new     Handle:         g_hCvarAllowMelee                                   = INVALID_HANDLE;   // cvar whether to count melee skeets
  320. new     Handle:         g_hCvarAllowSniper                                  = INVALID_HANDLE;   // cvar whether to count sniper headshot skeets
  321. new     Handle:         g_hCvarAllowGLSkeet                                 = INVALID_HANDLE;   // cvar whether to count direct hit GL skeets
  322. new     Handle:         g_hCvarDrawCrownThresh                              = INVALID_HANDLE;   // cvar damage in final shot for drawcrown-req.
  323. new     Handle:         g_hCvarSelfClearThresh                              = INVALID_HANDLE;   // cvar damage while self-clearing from smokers
  324. new     Handle:         g_hCvarHunterDPThresh                               = INVALID_HANDLE;   // cvar damage for hunter highpounce
  325. new     Handle:         g_hCvarJockeyDPThresh                               = INVALID_HANDLE;   // cvar distance for jockey highpounce
  326. new     Handle:         g_hCvarHideFakeDamage                               = INVALID_HANDLE;   // cvar damage while self-clearing from smokers
  327. new     Handle:         g_hCvarDeathChargeHeight                            = INVALID_HANDLE;   // cvar how high a charger must have come in order for a DC to count
  328. new     Handle:         g_hCvarInstaTime                                    = INVALID_HANDLE;   // cvar clear within this time or lower for instaclear
  329. new     Handle:         g_hCvarBHopMinStreak                                = INVALID_HANDLE;   // cvar this many hops in a row+ = streak
  330. new     Handle:         g_hCvarBHopMinInitSpeed                             = INVALID_HANDLE;   // cvar lower than this and the first jump won't be seen as the start of a streak
  331. new     Handle:         g_hCvarBHopContSpeed                                = INVALID_HANDLE;   // cvar
  332.  
  333. new     Handle:         g_hCvarPounceInterrupt                              = INVALID_HANDLE;   // z_pounce_damage_interrupt
  334. new                     g_iPounceInterrupt                                  = 150;
  335. new     Handle:         g_hCvarChargerHealth                                = INVALID_HANDLE;   // z_charger_health
  336. new     Handle:         g_hCvarWitchHealth                                  = INVALID_HANDLE;   // z_witch_health
  337. new     Handle:         g_hCvarMaxPounceDistance                            = INVALID_HANDLE;   // z_pounce_damage_range_max
  338. new     Handle:         g_hCvarMinPounceDistance                            = INVALID_HANDLE;   // z_pounce_damage_range_min
  339. new     Handle:         g_hCvarMaxPounceDamage                              = INVALID_HANDLE;   // z_hunter_max_pounce_bonus_damage;
  340.  
  341.  
  342. /*
  343.     Reports:
  344.     --------
  345.     Damage shown is damage done in the last shot/slash. So for crowns, this means
  346.     that the 'damage' value is one shotgun blast
  347.    
  348.  
  349.     Quirks:
  350.     -------
  351.     Does not report people cutting smoker tongues that target players other
  352.     than themselves. Could be done, but would require (too much) tracking.
  353.    
  354.     Actual damage done, on Hunter DPs, is low when the survivor gets incapped
  355.     by (a fraction of) the total pounce damage.
  356.    
  357.    
  358.     Fake Damage
  359.     -----------
  360.     Hiding of fake damage has the following consequences:
  361.         - Drawcrowns are less likely to be registered: if a witch takes too
  362.           much chip before the crowning shot, the final shot will be considered
  363.           as doing too little damage for a crown (even if it would have been a crown
  364.           had the witch had more health).
  365.         - Charger levels are harder to get on chipped chargers. Any charger that
  366.           has taken (600 - 390 =) 210 damage or more cannot be leveled (even if
  367.           the melee swing would've killed the charger (1559 damage) if it'd have
  368.           had full health).
  369.     I strongly recommend leaving fakedamage visible: it will offer more feedback on
  370.     the survivor's action and reward survivors doing (what would be) full crowns and
  371.     levels on chipped targets.
  372.    
  373.    
  374.     To Do
  375.     -----
  376.    
  377.     - fix:  tank rock owner is not reliable for the RockEaten forward
  378.     - fix:  tank rock skeets still unreliable detection (often triggers a 'skeet' when actually landed on someone)
  379.    
  380.     - fix:  apparently some HR4 cars generate car alarm messages when shot, even when no alarm goes off
  381.             (combination with car equalize plugin?)
  382.             - see below: the single hook might also fix this.. -- if not, hook for sound
  383.             - do a hookoutput on prop_car_alarm's and use that to track the actual alarm
  384.                 going off (might help in the case 2 alarms go off exactly at the same time?)
  385.     - fix:  double prints on car alarms (sometimes? epi + m60)
  386.  
  387.     - fix:  sometimes instaclear reports double for single clear (0.16s / 0.19s) epi saw this, was for hunter
  388.     - fix:  deadstops and m2s don't always register .. no idea why..
  389.     - fix:  sometimes a (first?) round doesn't work for skeet detection.. no hurt/full skeets are reported or counted
  390.  
  391.     - make forwards fire for every potential action,
  392.         - include the relevant values, so other plugins can decide for themselves what to consider it
  393.    
  394.     - test chargers getting dislodged with boomer pops?
  395.    
  396.     - add commonhop check
  397.     - add deathcharge assist check
  398.         - smoker
  399.         - jockey
  400.        
  401.     - add deathcharge coordinates for some areas
  402.         - DT4 next to saferoom
  403.         - DA1 near the lower roof, on sidewalk next to fence (no hurttrigger there)
  404.         - DA2 next to crane roof to the right of window
  405.             DA2 charge down into start area, after everyone's jumped the fence
  406.            
  407.     - count rock hits even if they do no damage [epi request]    
  408.     - sir
  409.         - make separate teamskeet forward, with (for now, up to) 4 skeeters + the damage each did
  410.     - xan
  411.         - add detection/display of unsuccesful witch crowns (witch death + info)
  412.        
  413.     detect...
  414.         - ? add jockey deadstops (and change forward to reflect type)
  415.         - ? speedcrown detection?
  416.         - ? spit-on-cap detection
  417.    
  418.     ---
  419.     done:
  420.         - applied sanity bounds to calculated damage for hunter dps
  421.         - removed tank's name from rock skeet print
  422.         - 300+ speed hops are considered hops even if no increase
  423. */
  424.  
  425. public Plugin:myinfo =
  426. {
  427.     name = "Skill Detection (skeets, crowns, levels)",
  428.     author = "Tabun",
  429.     description = "Detects and reports skeets, crowns, levels, highpounces, etc.",
  430.     version = PLUGIN_VERSION,
  431.     url = "https://github.com/Tabbernaut/L4D2-Plugins"
  432. }
  433.  
  434. public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
  435. {
  436.     RegPluginLibrary("skill_detect");
  437.    
  438.     g_hForwardSkeet =           CreateGlobalForward("OnSkeet", ET_Ignore, Param_Cell, Param_Cell );
  439.     g_hForwardSkeetHurt =       CreateGlobalForward("OnSkeetHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell );
  440.     g_hForwardSkeetMelee =      CreateGlobalForward("OnSkeetMelee", ET_Ignore, Param_Cell, Param_Cell );
  441.     g_hForwardSkeetMeleeHurt =  CreateGlobalForward("OnSkeetMeleeHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell );
  442.     g_hForwardSkeetSniper =     CreateGlobalForward("OnSkeetSniper", ET_Ignore, Param_Cell, Param_Cell );
  443.     g_hForwardSkeetSniperHurt = CreateGlobalForward("OnSkeetSniperHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell );
  444.     g_hForwardSkeetGL =         CreateGlobalForward("OnSkeetGL", ET_Ignore, Param_Cell, Param_Cell );
  445.     g_hForwardSIShove =         CreateGlobalForward("OnSpecialShoved", ET_Ignore, Param_Cell, Param_Cell, Param_Cell );
  446.     g_hForwardHunterDeadstop =  CreateGlobalForward("OnHunterDeadstop", ET_Ignore, Param_Cell, Param_Cell );
  447.     g_hForwardBoomerPop =       CreateGlobalForward("OnBoomerPop", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float );
  448.     g_hForwardLevel =           CreateGlobalForward("OnChargerLevel", ET_Ignore, Param_Cell, Param_Cell );
  449.     g_hForwardLevelHurt =       CreateGlobalForward("OnChargerLevelHurt", ET_Ignore, Param_Cell, Param_Cell, Param_Cell );
  450.     g_hForwardCrown =           CreateGlobalForward("OnWitchCrown", ET_Ignore, Param_Cell, Param_Cell );
  451.     g_hForwardDrawCrown =       CreateGlobalForward("OnWitchDrawCrown", ET_Ignore, Param_Cell, Param_Cell, Param_Cell );
  452.     g_hForwardTongueCut =       CreateGlobalForward("OnTongueCut", ET_Ignore, Param_Cell, Param_Cell );
  453.     g_hForwardSmokerSelfClear = CreateGlobalForward("OnSmokerSelfClear", ET_Ignore, Param_Cell, Param_Cell, Param_Cell );
  454.     g_hForwardRockSkeeted =     CreateGlobalForward("OnTankRockSkeeted", ET_Ignore, Param_Cell, Param_Cell );
  455.     g_hForwardRockEaten =       CreateGlobalForward("OnTankRockEaten", ET_Ignore, Param_Cell, Param_Cell );
  456.     g_hForwardHunterDP =        CreateGlobalForward("OnHunterHighPounce", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Float, Param_Cell, Param_Cell );
  457.     g_hForwardJockeyDP =        CreateGlobalForward("OnJockeyHighPounce", ET_Ignore, Param_Cell, Param_Cell, Param_Float, Param_Cell );
  458.     g_hForwardDeathCharge =     CreateGlobalForward("OnDeathCharge", ET_Ignore, Param_Cell, Param_Cell, Param_Float, Param_Float, Param_Cell );
  459.     g_hForwardClear =           CreateGlobalForward("OnSpecialClear", ET_Ignore, Param_Cell, Param_Cell, Param_Cell, Param_Cell, Param_Float, Param_Float, Param_Cell );
  460.     g_hForwardVomitLanded =     CreateGlobalForward("OnBoomerVomitLanded", ET_Ignore, Param_Cell, Param_Cell );
  461.     g_hForwardBHopStreak =      CreateGlobalForward("OnBunnyHopStreak", ET_Ignore, Param_Cell, Param_Cell, Param_Float );
  462.     g_hForwardAlarmTriggered =  CreateGlobalForward("OnCarAlarmTriggered", ET_Ignore, Param_Cell, Param_Cell, Param_Cell );
  463.     g_bLateLoad = late;
  464.    
  465.     return APLRes_Success;
  466. }
  467.  
  468. public OnPluginStart()
  469. {
  470.     // hooks
  471.     HookEvent("round_start",                Event_RoundStart,               EventHookMode_PostNoCopy);
  472.     HookEvent("scavenge_round_start",       Event_RoundStart,               EventHookMode_PostNoCopy);
  473.     HookEvent("round_end",                  Event_RoundEnd,                 EventHookMode_PostNoCopy);
  474.    
  475.     HookEvent("player_spawn",               Event_PlayerSpawn,              EventHookMode_Post);
  476.     HookEvent("player_hurt",                Event_PlayerHurt,               EventHookMode_Pre);
  477.     HookEvent("player_death",               Event_PlayerDeath,              EventHookMode_Pre);
  478.     HookEvent("ability_use",                Event_AbilityUse,               EventHookMode_Post);
  479.     HookEvent("lunge_pounce",               Event_LungePounce,              EventHookMode_Post);
  480.     HookEvent("player_shoved",              Event_PlayerShoved,             EventHookMode_Post);
  481.     HookEvent("player_jump",                Event_PlayerJumped,             EventHookMode_Post);
  482.     HookEvent("player_jump_apex",           Event_PlayerJumpApex,           EventHookMode_Post);
  483.    
  484.     HookEvent("player_now_it",              Event_PlayerBoomed,             EventHookMode_Post);
  485.     HookEvent("boomer_exploded",            Event_BoomerExploded,           EventHookMode_Post);
  486.    
  487.     //HookEvent("infected_hurt",              Event_InfectedHurt,             EventHookMode_Post);
  488.     HookEvent("witch_spawn",                Event_WitchSpawned,             EventHookMode_Post);
  489.     HookEvent("witch_killed",               Event_WitchKilled,              EventHookMode_Post);
  490.     HookEvent("witch_harasser_set",         Event_WitchHarasserSet,         EventHookMode_Post);
  491.    
  492.     HookEvent("tongue_grab",                Event_TongueGrab,               EventHookMode_Post);
  493.     HookEvent("tongue_pull_stopped",        Event_TonguePullStopped,        EventHookMode_Post);
  494.     HookEvent("choke_start",                Event_ChokeStart,               EventHookMode_Post);
  495.     HookEvent("choke_stopped",              Event_ChokeStop,                EventHookMode_Post);
  496.     HookEvent("jockey_ride",                Event_JockeyRide,               EventHookMode_Post);
  497.     HookEvent("charger_carry_start",        Event_ChargeCarryStart,         EventHookMode_Post);
  498.     HookEvent("charger_carry_end",          Event_ChargeCarryEnd,           EventHookMode_Post);
  499.     HookEvent("charger_impact",             Event_ChargeImpact,             EventHookMode_Post);
  500.     HookEvent("charger_pummel_start",       Event_ChargePummelStart,        EventHookMode_Post);
  501.    
  502.     HookEvent("player_incapacitated_start", Event_IncapStart,               EventHookMode_Post);
  503.     HookEvent("triggered_car_alarm",        Event_CarAlarmGoesOff,          EventHookMode_Post);
  504.    
  505.    
  506.     // version cvar
  507.     CreateConVar( "sm_skill_detect_version", PLUGIN_VERSION, "Skill detect plugin version.", FCVAR_NOTIFY|FCVAR_REPLICATED|FCVAR_DONTRECORD );
  508.    
  509.     // cvars: config
  510.    
  511.     g_hCvarReport = CreateConVar(           "sm_skill_report_enable" ,      "0", "Whether to report in chat (see sm_skill_report_flags).", _, true, 0.0, true, 1.0 );
  512.     g_hCvarReportFlags = CreateConVar(      "sm_skill_report_flags" ,       REP_DEFAULT, "What to report skeets in chat (bitflags: 1,2:skeets/hurt; 4,8:level/chip; 16,32:crown/draw; 64,128:cut/selfclear, ... ).", _, true, 0.0 );
  513.    
  514.     g_hCvarAllowMelee = CreateConVar(       "sm_skill_skeet_allowmelee",    "1", "Whether to count/forward melee skeets.", _, true, 0.0, true, 1.0 );
  515.     g_hCvarAllowSniper = CreateConVar(      "sm_skill_skeet_allowsniper",   "1", "Whether to count/forward sniper/magnum headshots as skeets.", _, true, 0.0, true, 1.0 );
  516.     g_hCvarAllowGLSkeet = CreateConVar(     "sm_skill_skeet_allowgl",       "1", "Whether to count/forward direct GL hits as skeets.", _, true, 0.0, true, 1.0 );
  517.     g_hCvarDrawCrownThresh = CreateConVar(  "sm_skill_drawcrown_damage",  "500", "How much damage a survivor must at least do in the final shot for it to count as a drawcrown.", _, true, 0.0, false );
  518.     g_hCvarSelfClearThresh = CreateConVar(  "sm_skill_selfclear_damage",  "200", "How much damage a survivor must at least do to a smoker for him to count as self-clearing.", _, true, 0.0, false );
  519.     g_hCvarHunterDPThresh = CreateConVar(   "sm_skill_hunterdp_height",   "400", "Minimum height of hunter pounce for it to count as a DP.", _, true, 0.0, false );
  520.     g_hCvarJockeyDPThresh = CreateConVar(   "sm_skill_jockeydp_height",   "300", "How much height distance a jockey must make for his 'DP' to count as a reportable highpounce.", _, true, 0.0, false );
  521.     g_hCvarHideFakeDamage = CreateConVar(   "sm_skill_hidefakedamage",      "0", "If set, any damage done that exceeds the health of a victim is hidden in reports.", _, true, 0.0, true, 1.0 );
  522.     g_hCvarDeathChargeHeight = CreateConVar("sm_skill_deathcharge_height","400", "How much height distance a charger must take its victim for a deathcharge to be reported.", _, true, 0.0, false );
  523.     g_hCvarInstaTime = CreateConVar(        "sm_skill_instaclear_time",     "0.75", "A clear within this time (in seconds) counts as an insta-clear.", _, true, 0.0, false );
  524.     g_hCvarBHopMinStreak = CreateConVar(    "sm_skill_bhopstreak",          "3", "The lowest bunnyhop streak that will be reported.", _, true, 0.0, false );
  525.     g_hCvarBHopMinInitSpeed = CreateConVar( "sm_skill_bhopinitspeed",     "150", "The minimal speed of the first jump of a bunnyhopstreak (0 to allow 'hops' from standstill).", _, true, 0.0, false );
  526.     g_hCvarBHopContSpeed = CreateConVar(    "sm_skill_bhopkeepspeed",     "300", "The minimal speed at which hops are considered succesful even if not speed increase is made.", _, true, 0.0, false );
  527.    
  528.     // cvars: built in
  529.     g_hCvarPounceInterrupt = FindConVar("z_pounce_damage_interrupt");
  530.     HookConVarChange(g_hCvarPounceInterrupt, CvarChange_PounceInterrupt);
  531.     g_iPounceInterrupt = GetConVarInt(g_hCvarPounceInterrupt);
  532.    
  533.     g_hCvarChargerHealth = FindConVar("z_charger_health");
  534.     g_hCvarWitchHealth = FindConVar("z_witch_health");
  535.    
  536.     g_hCvarMaxPounceDistance = FindConVar("z_pounce_damage_range_max");
  537.     g_hCvarMinPounceDistance = FindConVar("z_pounce_damage_range_min");
  538.     g_hCvarMaxPounceDamage = FindConVar("z_hunter_max_pounce_bonus_damage");
  539.     if ( g_hCvarMaxPounceDistance == INVALID_HANDLE ) { g_hCvarMaxPounceDistance = CreateConVar( "z_pounce_damage_range_max",  "1000.0", "Not available on this server, added by l4d2_skill_detect.", _, true, 0.0, false ); }
  540.     if ( g_hCvarMinPounceDistance == INVALID_HANDLE ) { g_hCvarMinPounceDistance = CreateConVar( "z_pounce_damage_range_min",  "300.0", "Not available on this server, added by l4d2_skill_detect.", _, true, 0.0, false ); }
  541.     if ( g_hCvarMaxPounceDamage == INVALID_HANDLE ) { g_hCvarMaxPounceDamage = CreateConVar( "z_hunter_max_pounce_bonus_damage",  "49", "Not available on this server, added by l4d2_skill_detect.", _, true, 0.0, false ); }
  542.    
  543.    
  544.     // tries
  545.     g_hTrieWeapons = CreateTrie();
  546.     SetTrieValue(g_hTrieWeapons, "hunting_rifle",               WPTYPE_SNIPER);
  547.     SetTrieValue(g_hTrieWeapons, "sniper_military",             WPTYPE_SNIPER);
  548.     SetTrieValue(g_hTrieWeapons, "sniper_awp",                  WPTYPE_SNIPER);
  549.     SetTrieValue(g_hTrieWeapons, "sniper_scout",                WPTYPE_SNIPER);
  550.     SetTrieValue(g_hTrieWeapons, "pistol_magnum",               WPTYPE_MAGNUM);
  551.     SetTrieValue(g_hTrieWeapons, "grenade_launcher_projectile", WPTYPE_GL);
  552.    
  553.     g_hTrieEntityCreated = CreateTrie();
  554.     SetTrieValue(g_hTrieEntityCreated, "tank_rock",             OEC_TANKROCK);
  555.     SetTrieValue(g_hTrieEntityCreated, "witch",                 OEC_WITCH);
  556.     SetTrieValue(g_hTrieEntityCreated, "trigger_hurt",          OEC_TRIGGER);
  557.     SetTrieValue(g_hTrieEntityCreated, "prop_car_alarm",        OEC_CARALARM);
  558.     SetTrieValue(g_hTrieEntityCreated, "prop_car_glass",        OEC_CARGLASS);
  559.    
  560.     g_hTrieAbility = CreateTrie();
  561.     SetTrieValue(g_hTrieAbility, "ability_lunge",               ABL_HUNTERLUNGE);
  562.     SetTrieValue(g_hTrieAbility, "ability_throw",               ABL_ROCKTHROW);
  563.    
  564.     g_hWitchTrie = CreateTrie();
  565.     g_hRockTrie = CreateTrie();
  566.     g_hCarTrie = CreateTrie();
  567.    
  568.     if ( g_bLateLoad )
  569.     {
  570.         for ( new client = 1; client <= MaxClients; client++ )
  571.         {
  572.             if ( IS_VALID_INGAME(client) )
  573.             {
  574.                 SDKHook( client, SDKHook_OnTakeDamage, OnTakeDamageByWitch );
  575.             }
  576.         }
  577.     }
  578. }
  579.  
  580. public CvarChange_PounceInterrupt( Handle:convar, const String:oldValue[], const String:newValue[] )
  581. {
  582.     g_iPounceInterrupt = GetConVarInt(convar);
  583. }
  584.  
  585. public OnClientPostAdminCheck(client)
  586. {
  587.     SDKHook(client, SDKHook_OnTakeDamage, OnTakeDamageByWitch);
  588. }
  589.  
  590. public OnClientDisconnect(client)
  591. {
  592.     SDKUnhook(client, SDKHook_OnTakeDamage, OnTakeDamageByWitch);
  593. }
  594.  
  595.  
  596.  
  597. /*
  598.     Tracking
  599.     --------
  600. */
  601.  
  602. public Action: Event_RoundStart( Handle:event, const String:name[], bool:dontBroadcast )
  603. {
  604.     g_iRocksBeingThrownCount = 0;
  605.    
  606.     for ( new i = 1; i <= MaxClients; i++ )
  607.     {
  608.         g_bIsHopping[i] = false;
  609.        
  610.         for ( new j = 1; j <= MaxClients; j++ )
  611.         {
  612.             g_fVictimLastShove[i][j] = 0.0;
  613.         }
  614.     }
  615. }
  616.  
  617. public Action: Event_RoundEnd( Handle:event, const String:name[], bool:dontBroadcast )
  618. {
  619.     // clean trie, new cars will be created
  620.     ClearTrie(g_hCarTrie);
  621. }
  622.  
  623. public Action: Event_PlayerHurt( Handle:event, const String:name[], bool:dontBroadcast )
  624. {
  625.     new victim = GetClientOfUserId(GetEventInt(event, "userid"));
  626.     new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
  627.     new zClass;
  628.    
  629.     new damage = GetEventInt(event, "dmg_health");
  630.     new damagetype = GetEventInt(event, "type");
  631.    
  632.     if ( IS_VALID_INFECTED(victim) )
  633.     {
  634.         zClass = GetEntProp(victim, Prop_Send, "m_zombieClass");
  635.         new health = GetEventInt(event, "health");
  636.         new hitgroup = GetEventInt(event, "hitgroup");
  637.        
  638.         if ( damage < 1 ) { return Plugin_Continue; }
  639.        
  640.         switch ( zClass )
  641.         {
  642.             case ZC_HUNTER:
  643.             {
  644.                 // if it's not a survivor doing the work, only get the remaining health
  645.                 if ( !IS_VALID_SURVIVOR(attacker) )
  646.                 {
  647.                     g_iHunterLastHealth[victim] = health;
  648.                     return Plugin_Continue;
  649.                 }
  650.                
  651.                 // if the damage done is greater than the health we know the hunter to have remaining, reduce the damage done
  652.                 if ( g_iHunterLastHealth[victim] > 0 && damage > g_iHunterLastHealth[victim] )
  653.                 {
  654.                     damage = g_iHunterLastHealth[victim];
  655.                     g_iHunterOverkill[victim] = g_iHunterLastHealth[victim] - damage;
  656.                     g_iHunterLastHealth[victim] = 0;
  657.                 }
  658.                
  659.                 /*  
  660.                     handle old shotgun blast: too long ago? not the same blast
  661.                 */
  662.                 if ( g_iHunterShotDmg[victim][attacker] > 0 && FloatSub(GetGameTime(), g_fHunterShotStart[victim][attacker]) > SHOTGUN_BLAST_TIME )
  663.                 {
  664.                     g_fHunterShotStart[victim][attacker] = 0.0;
  665.                 }
  666.                
  667.                 /*
  668.                     m_isAttemptingToPounce is set to 0 here if the hunter is actually skeeted
  669.                     so the g_fHunterTracePouncing[victim] value indicates when the hunter was last seen pouncing in traceattack
  670.                     (should be DIRECTLY before this event for every shot).
  671.                 */
  672.                 new bool: isPouncing = bool:(
  673.                         GetEntProp(victim, Prop_Send, "m_isAttemptingToPounce")     ||
  674.                         g_fHunterTracePouncing[victim] != 0.0 && FloatSub( GetGameTime(), g_fHunterTracePouncing[victim] ) < 0.001
  675.                     );
  676.                
  677.                 if ( isPouncing )
  678.                 {
  679.                     if ( damagetype & DMG_BUCKSHOT )
  680.                     {
  681.                         // first pellet hit?
  682.                         if ( g_fHunterShotStart[victim][attacker] == 0.0 )
  683.                         {
  684.                             // new shotgun blast
  685.                             g_fHunterShotStart[victim][attacker] = GetGameTime();
  686.                             g_fHunterLastShot[victim] = g_fHunterShotStart[victim][attacker];
  687.                         }
  688.                         g_iHunterShotDmg[victim][attacker] += damage;
  689.                         g_iHunterShotDmgTeam[victim] += damage;
  690.                        
  691.                         if ( health == 0 ) {
  692.                             g_bHunterKilledPouncing[victim] = true;
  693.                         }
  694.                     }
  695.                     else if ( damagetype & (DMG_BLAST | DMG_PLASMA) && health == 0 )
  696.                     {
  697.                         // direct GL hit?
  698.                         /*
  699.                             direct hit is DMG_BLAST | DMG_PLASMA
  700.                             indirect hit is DMG_AIRBOAT
  701.                         */
  702.                        
  703.                         decl String: weaponB[32];
  704.                         new strWeaponType: weaponTypeB;
  705.                         GetEventString(event, "weapon", weaponB, sizeof(weaponB));
  706.                        
  707.                         if ( GetTrieValue(g_hTrieWeapons, weaponB, weaponTypeB) && weaponTypeB == WPTYPE_GL )
  708.                         {
  709.                             if ( GetConVarBool(g_hCvarAllowGLSkeet) ) {
  710.                                 HandleSkeet( attacker, victim, false, false, true );
  711.                             }
  712.                         }
  713.                     }
  714.                     else if (   damagetype & DMG_BULLET &&
  715.                                 health == 0 &&
  716.                                 hitgroup == HITGROUP_HEAD
  717.                     ) {
  718.                         // headshot with bullet based weapon (only single shots) -- only snipers
  719.                         decl String: weaponA[32];
  720.                         new strWeaponType: weaponTypeA;
  721.                         GetEventString(event, "weapon", weaponA, sizeof(weaponA));
  722.                        
  723.                         if (    GetTrieValue(g_hTrieWeapons, weaponA, weaponTypeA) &&
  724.                                 (   weaponTypeA == WPTYPE_SNIPER ||
  725.                                     weaponTypeA == WPTYPE_MAGNUM )
  726.                         ) {
  727.                             if ( damage >= g_iPounceInterrupt )
  728.                             {
  729.                                 g_iHunterShotDmgTeam[victim] = 0;
  730.                                 if ( GetConVarBool(g_hCvarAllowSniper) ) {
  731.                                     HandleSkeet( attacker, victim, false, true );
  732.                                 }
  733.                                 ResetHunter(victim);
  734.                             }
  735.                             else
  736.                             {
  737.                                 // hurt skeet
  738.                                 if ( GetConVarBool(g_hCvarAllowSniper) ) {
  739.                                     HandleNonSkeet( attacker, victim, damage, ( g_iHunterOverkill[victim] + g_iHunterShotDmgTeam[victim] > g_iPounceInterrupt ), false, true );
  740.                                 }
  741.                                 ResetHunter(victim);
  742.                             }
  743.                         }
  744.                        
  745.                         // already handled hurt skeet above
  746.                         //g_bHunterKilledPouncing[victim] = true;
  747.                     }
  748.                     else if ( damagetype & DMG_SLASH || damagetype & DMG_CLUB )
  749.                     {
  750.                         // melee skeet
  751.                         if ( damage >= g_iPounceInterrupt )
  752.                         {
  753.                             g_iHunterShotDmgTeam[victim] = 0;
  754.                             if ( GetConVarBool(g_hCvarAllowMelee) ) {
  755.                                 HandleSkeet( attacker, victim, true );
  756.                             }
  757.                             ResetHunter(victim);
  758.                             //g_bHunterKilledPouncing[victim] = true;
  759.                         }
  760.                         else if ( health == 0 )
  761.                         {
  762.                             // hurt skeet (always overkill)
  763.                             if ( GetConVarBool(g_hCvarAllowMelee) ) {
  764.                                 HandleNonSkeet( attacker, victim, damage, true, true, false );
  765.                             }
  766.                             ResetHunter(victim);
  767.                         }
  768.                     }
  769.                 }
  770.                 else if ( health == 0 )
  771.                 {
  772.                     // make sure we don't mistake non-pouncing hunters as 'not skeeted'-warnable
  773.                     g_bHunterKilledPouncing[victim] = false;
  774.                 }
  775.                
  776.                 // store last health seen for next damage event
  777.                 g_iHunterLastHealth[victim] = health;
  778.             }
  779.            
  780.             case ZC_CHARGER:
  781.             {
  782.                 if ( IS_VALID_SURVIVOR(attacker) )
  783.                 {                
  784.                     // check for levels
  785.                     if ( health == 0 && ( damagetype & DMG_CLUB || damagetype & DMG_SLASH ) )
  786.                     {
  787.                         new iChargeHealth = GetConVarInt(g_hCvarChargerHealth);
  788.                         new abilityEnt = GetEntPropEnt( victim, Prop_Send, "m_customAbility" );
  789.                         if ( IsValidEntity(abilityEnt) && GetEntProp(abilityEnt, Prop_Send, "m_isCharging") )
  790.                         {
  791.                             // fix fake damage?
  792.                             if ( GetConVarBool(g_hCvarHideFakeDamage) )
  793.                             {
  794.                                 damage = iChargeHealth - g_iChargerHealth[victim];
  795.                             }
  796.                            
  797.                             // charger was killed, was it a full level?
  798.                             if ( damage > (iChargeHealth * 0.65) ) {
  799.                                 HandleLevel( attacker, victim );
  800.                             }
  801.                             else {
  802.                                 HandleLevelHurt( attacker, victim, damage );
  803.                             }
  804.                         }
  805.                     }
  806.                 }
  807.                
  808.                 // store health for next damage it takes
  809.                 if ( health > 0 )
  810.                 {
  811.                     g_iChargerHealth[victim] = health;
  812.                 }
  813.             }
  814.            
  815.             case ZC_SMOKER:
  816.             {
  817.                 if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; }
  818.                
  819.                 g_iSmokerVictimDamage[victim] += damage;
  820.             }
  821.            
  822.         }
  823.     }
  824.     else if ( IS_VALID_INFECTED(attacker) )
  825.     {
  826.         zClass = GetEntProp(attacker, Prop_Send, "m_zombieClass");
  827.        
  828.         switch ( zClass )
  829.         {
  830.             case ZC_HUNTER:
  831.             {
  832.                 // a hunter pounce landing is DMG_CRUSH
  833.                 if ( damagetype & DMG_CRUSH ) {
  834.                     g_iPounceDamage[attacker] = damage;
  835.                 }
  836.             }
  837.            
  838.             case ZC_TANK:
  839.             {
  840.                 new String: weapon[10];
  841.                 GetEventString(event, "weapon", weapon, sizeof(weapon));
  842.                
  843.                 if ( StrEqual(weapon, "tank_rock") )
  844.                 {
  845.                     // find rock entity through tank
  846.                     if ( g_iTankRock[attacker] )
  847.                     {
  848.                         // remember that the rock wasn't shot
  849.                         decl String:rock_key[10];
  850.                         FormatEx(rock_key, sizeof(rock_key), "%x", g_iTankRock[attacker]);
  851.                         new rock_array[3];
  852.                         rock_array[rckDamage] = -1;
  853.                         SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true);
  854.                     }
  855.                    
  856.                     if ( IS_VALID_SURVIVOR(victim) )
  857.                     {
  858.                         HandleRockEaten( attacker, victim );
  859.                     }
  860.                 }
  861.                
  862.                 return Plugin_Continue;
  863.             }
  864.         }
  865.     }
  866.    
  867.     // check for deathcharge flags
  868.     if ( IS_VALID_SURVIVOR(victim) )
  869.     {
  870.         // debug
  871.         if ( damagetype & DMG_DROWN || damagetype & DMG_FALL ) {
  872.             g_iVictimMapDmg[victim] += damage;
  873.         }
  874.        
  875.         if ( damagetype & DMG_DROWN && damage >= MIN_DC_TRIGGER_DMG )
  876.         {
  877.             g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_HURTLOTS;
  878.         }
  879.         else if ( damagetype & DMG_FALL && damage >= MIN_DC_FALL_DMG )
  880.         {
  881.             g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_HURTLOTS;
  882.         }
  883.     }
  884.    
  885.     return Plugin_Continue;
  886. }
  887.  
  888. public Action: Event_PlayerSpawn( Handle:event, const String:name[], bool:dontBroadcast )
  889. {
  890.     new client = GetClientOfUserId(GetEventInt(event, "userid"));
  891.     if ( !IS_VALID_INFECTED(client) ) { return Plugin_Continue; }
  892.    
  893.     new zClass = GetEntProp(client, Prop_Send, "m_zombieClass");
  894.    
  895.     g_fSpawnTime[client] = GetGameTime();
  896.     g_fPinTime[client][0] = 0.0;
  897.     g_fPinTime[client][1] = 0.0;
  898.    
  899.     switch ( zClass )
  900.     {
  901.         case ZC_BOOMER:
  902.         {
  903.             g_bBoomerHitSomebody[client] = false;
  904.             g_iBoomerGotShoved[client] = 0;
  905.         }
  906.         case ZC_SMOKER:
  907.         {
  908.             g_bSmokerClearCheck[client] = false;
  909.             g_iSmokerVictim[client] = 0;
  910.             g_iSmokerVictimDamage[client] = 0;
  911.         }
  912.         case ZC_HUNTER:
  913.         {
  914.             SDKHook(client, SDKHook_TraceAttack, TraceAttack_Hunter);
  915.    
  916.             g_fPouncePosition[client][0] = 0.0;
  917.             g_fPouncePosition[client][1] = 0.0;
  918.             g_fPouncePosition[client][2] = 0.0;
  919.         }
  920.         case ZC_JOCKEY:
  921.         {
  922.             SDKHook(client, SDKHook_TraceAttack, TraceAttack_Jockey);
  923.            
  924.             g_fPouncePosition[client][0] = 0.0;
  925.             g_fPouncePosition[client][1] = 0.0;
  926.             g_fPouncePosition[client][2] = 0.0;
  927.         }
  928.         case ZC_CHARGER:
  929.         {
  930.             SDKHook(client, SDKHook_TraceAttack, TraceAttack_Charger);
  931.            
  932.             g_iChargerHealth[client] = GetConVarInt(g_hCvarChargerHealth);
  933.         }
  934.     }
  935.    
  936.     return Plugin_Continue;
  937. }
  938.  
  939. // player about to get incapped
  940. public Action: Event_IncapStart( Handle:event, const String:name[], bool:dontBroadcast )
  941. {
  942.     // test for deathcharges
  943.    
  944.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  945.     //new attacker = GetClientOfUserId( GetEventInt(event, "attacker") );
  946.     new attackent = GetEventInt(event, "attackerentid");
  947.     new dmgtype = GetEventInt(event, "type");
  948.    
  949.     new String: classname[24];
  950.     new strOEC: classnameOEC;
  951.     if ( IsValidEntity(attackent) ) {
  952.         GetEdictClassname(attackent, classname, sizeof(classname));
  953.         if ( GetTrieValue(g_hTrieEntityCreated, classname, classnameOEC)) {
  954.             g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_TRIGGER;
  955.         }
  956.     }
  957.    
  958.     new Float: flow = GetSurvivorDistance(client);
  959.    
  960.     //PrintDebug( 3, "Incap Pre on [%N]: attk: %i / %i (%s) - dmgtype: %i - flow: %.1f", client, attacker, attackent, classname, dmgtype, flow );
  961.    
  962.     // drown is damage type
  963.     if ( dmgtype & DMG_DROWN )
  964.     {
  965.         g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_DROWN;
  966.     }
  967.     if ( flow < WEIRD_FLOW_THRESH )
  968.     {
  969.         g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_WEIRDFLOW;
  970.     }
  971. }
  972.  
  973. // trace attacks on hunters
  974. public Action: TraceAttack_Hunter (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup)
  975. {
  976.     // track pinning
  977.     g_iSpecialVictim[victim] = GetEntPropEnt(victim, Prop_Send, "m_pounceVictim");
  978.    
  979.     if ( !IS_VALID_SURVIVOR(attacker) || !IsValidEdict(inflictor) ) { return; }
  980.    
  981.     // track flight
  982.     if ( GetEntProp(victim, Prop_Send, "m_isAttemptingToPounce") )
  983.     {
  984.         g_fHunterTracePouncing[victim] = GetGameTime();
  985.     }
  986.     else
  987.     {
  988.         g_fHunterTracePouncing[victim] = 0.0;
  989.     }  
  990. }
  991. public Action: TraceAttack_Charger (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup)
  992. {
  993.     // track pinning
  994.     new victimA = GetEntPropEnt(victim, Prop_Send, "m_carryVictim");
  995.     if ( victimA != -1 ) {
  996.         g_iSpecialVictim[victim] = victimA;
  997.     } else {
  998.         g_iSpecialVictim[victim] = GetEntPropEnt(victim, Prop_Send, "m_pummelVictim");
  999.     }
  1000.    
  1001. }
  1002. public Action: TraceAttack_Jockey (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup)
  1003. {
  1004.     // track pinning
  1005.     g_iSpecialVictim[victim] = GetEntPropEnt(victim, Prop_Send, "m_jockeyVictim");
  1006. }
  1007.  
  1008. public Action: Event_PlayerDeath( Handle:hEvent, const String:name[], bool:dontBroadcast )
  1009. {
  1010.     new victim = GetClientOfUserId( GetEventInt(hEvent, "userid") );
  1011.     new attacker = GetClientOfUserId( GetEventInt(hEvent, "attacker") );
  1012.    
  1013.     if ( IS_VALID_INFECTED(victim) )
  1014.     {
  1015.         new zClass = GetEntProp(victim, Prop_Send, "m_zombieClass");
  1016.        
  1017.         switch ( zClass )
  1018.         {
  1019.             case ZC_HUNTER:
  1020.             {
  1021.                 if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; }
  1022.                
  1023.                 if ( g_iHunterShotDmgTeam[victim] > 0 && g_bHunterKilledPouncing[victim] )
  1024.                 {
  1025.                     // skeet?
  1026.                     if (    g_iHunterShotDmgTeam[victim] > g_iHunterShotDmg[victim][attacker] &&
  1027.                             g_iHunterShotDmgTeam[victim] >= g_iPounceInterrupt
  1028.                     ) {
  1029.                         // team skeet
  1030.                         HandleSkeet( -2, victim );
  1031.                     }
  1032.                     else if ( g_iHunterShotDmg[victim][attacker] >= g_iPounceInterrupt )
  1033.                     {
  1034.                         // single player skeet
  1035.                         HandleSkeet( attacker, victim );
  1036.                     }
  1037.                     else if ( g_iHunterOverkill[victim] > 0 )
  1038.                     {
  1039.                         // overkill? might've been a skeet, if it wasn't on a hurt hunter (only for shotguns)
  1040.                         HandleNonSkeet( attacker, victim, g_iHunterShotDmgTeam[victim], ( g_iHunterOverkill[victim] + g_iHunterShotDmgTeam[victim] > g_iPounceInterrupt ) );
  1041.                     }
  1042.                     else
  1043.                     {
  1044.                         // not a skeet at all
  1045.                         HandleNonSkeet( attacker, victim, g_iHunterShotDmg[victim][attacker] );
  1046.                     }
  1047.                 }
  1048.                 else {
  1049.                     // check whether it was a clear
  1050.                     if ( g_iSpecialVictim[victim] > 0 )
  1051.                     {
  1052.                         HandleClear( attacker, victim, g_iSpecialVictim[victim],
  1053.                                 ZC_HUNTER,
  1054.                                 FloatSub( GetGameTime(), g_fPinTime[victim][0]),
  1055.                                 -1.0
  1056.                             );
  1057.                     }
  1058.                 }
  1059.                
  1060.                 ResetHunter(victim);
  1061.             }
  1062.            
  1063.             case ZC_SMOKER:
  1064.             {
  1065.                 if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; }
  1066.                
  1067.                 if (    g_bSmokerClearCheck[victim] &&
  1068.                         g_iSmokerVictim[victim] == attacker &&
  1069.                         g_iSmokerVictimDamage[victim] >= GetConVarInt(g_hCvarSelfClearThresh)
  1070.                 ) {
  1071.                         HandleSmokerSelfClear( attacker, victim );
  1072.                 }
  1073.                 else
  1074.                 {
  1075.                     g_bSmokerClearCheck[victim] = false;
  1076.                     g_iSmokerVictim[victim] = 0;
  1077.                 }
  1078.             }
  1079.            
  1080.             case ZC_JOCKEY:
  1081.             {
  1082.                 // check whether it was a clear
  1083.                 if ( g_iSpecialVictim[victim] > 0 )
  1084.                 {
  1085.                     HandleClear( attacker, victim, g_iSpecialVictim[victim],
  1086.                             ZC_JOCKEY,
  1087.                             FloatSub( GetGameTime(), g_fPinTime[victim][0]),
  1088.                             -1.0
  1089.                         );
  1090.                 }
  1091.             }
  1092.            
  1093.             case ZC_CHARGER:
  1094.             {
  1095.                 // is it someone carrying a survivor (that might be DC'd)?
  1096.                 // switch charge victim to 'impact' check (reset checktime)
  1097.                 if ( IS_VALID_INGAME(g_iChargeVictim[victim]) ) {
  1098.                     g_fChargeTime[ g_iChargeVictim[victim] ] = GetGameTime();
  1099.                 }
  1100.                
  1101.                 // check whether it was a clear
  1102.                 if ( g_iSpecialVictim[victim] > 0 )
  1103.                 {
  1104.                     HandleClear( attacker, victim, g_iSpecialVictim[victim],
  1105.                             ZC_CHARGER,
  1106.                             (g_fPinTime[victim][1] > 0.0) ? FloatSub( GetGameTime(), g_fPinTime[victim][1]) : -1.0,
  1107.                             FloatSub( GetGameTime(), g_fPinTime[victim][0])
  1108.                         );
  1109.                 }
  1110.             }
  1111.         }
  1112.     }
  1113.     else if ( IS_VALID_SURVIVOR(victim) )
  1114.     {
  1115.         // check for deathcharges
  1116.         //new atkent = GetEventInt(hEvent, "attackerentid");
  1117.         new dmgtype = GetEventInt(hEvent, "type");
  1118.        
  1119.         //PrintDebug( 3, "Died [%N]: attk: %i / %i - dmgtype: %i", victim, attacker, atkent, dmgtype );
  1120.        
  1121.         if ( dmgtype & DMG_FALL)
  1122.         {
  1123.             g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_FALL;
  1124.         }
  1125.         else if ( IS_VALID_INFECTED(attacker) && attacker != g_iVictimCharger[victim] )
  1126.         {
  1127.             // if something other than the charger killed them, remember (not a DC)
  1128.             g_iVictimFlags[victim] = g_iVictimFlags[victim] | VICFLG_KILLEDBYOTHER;
  1129.         }
  1130.     }
  1131.    
  1132.     return Plugin_Continue;
  1133. }
  1134.  
  1135. public Action: Event_PlayerShoved( Handle:event, const String:name[], bool:dontBroadcast )
  1136. {
  1137.     new victim = GetClientOfUserId(GetEventInt(event, "userid"));
  1138.     new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
  1139.    
  1140.     //PrintDebug(1, "Shove from %i on %i", attacker, victim);
  1141.    
  1142.     if ( !IS_VALID_SURVIVOR(attacker) || !IS_VALID_INFECTED(victim) ) { return Plugin_Continue; }
  1143.    
  1144.     new zClass = GetEntProp(victim, Prop_Send, "m_zombieClass");
  1145.    
  1146.     //PrintDebug(1, " --> Shove from %N on %N (class: %i) -- (last shove time: %.2f / %.2f)", attacker, victim, zClass, g_fVictimLastShove[victim][attacker], FloatSub( GetGameTime(), g_fVictimLastShove[victim][attacker] ) );
  1147.    
  1148.     // track on boomers
  1149.     if ( zClass == ZC_BOOMER )
  1150.     {
  1151.         g_iBoomerGotShoved[victim]++;
  1152.     }
  1153.     else {
  1154.         // check for clears
  1155.         switch ( zClass )
  1156.         {
  1157.             case ZC_HUNTER: {
  1158.                 if ( GetEntPropEnt(victim, Prop_Send, "m_pounceVictim") > 0 )
  1159.                 {
  1160.                     HandleClear( attacker, victim, GetEntPropEnt(victim, Prop_Send, "m_pounceVictim"),
  1161.                             ZC_HUNTER,
  1162.                             FloatSub( GetGameTime(), g_fPinTime[victim][0]),
  1163.                             -1.0,
  1164.                             true
  1165.                         );
  1166.                 }
  1167.             }
  1168.             case ZC_JOCKEY: {
  1169.                 if ( GetEntPropEnt(victim, Prop_Send, "m_jockeyVictim") > 0 )
  1170.                 {
  1171.                     HandleClear( attacker, victim, GetEntPropEnt(victim, Prop_Send, "m_jockeyVictim"),
  1172.                             ZC_JOCKEY,
  1173.                             FloatSub( GetGameTime(), g_fPinTime[victim][0]),
  1174.                             -1.0,
  1175.                             true
  1176.                         );
  1177.                 }
  1178.             }
  1179.         }
  1180.     }
  1181.    
  1182.     if ( g_fVictimLastShove[victim][attacker] == 0.0 || FloatSub( GetGameTime(), g_fVictimLastShove[victim][attacker] ) >= SHOVE_TIME )
  1183.     {
  1184.         if ( GetEntProp(victim, Prop_Send, "m_isAttemptingToPounce") )
  1185.         {
  1186.             HandleDeadstop( attacker, victim );
  1187.         }
  1188.        
  1189.         HandleShove( attacker, victim, zClass );
  1190.        
  1191.         g_fVictimLastShove[victim][attacker] = GetGameTime();
  1192.     }
  1193.    
  1194.     // check for shove on smoker by pull victim
  1195.     if ( g_iSmokerVictim[victim] == attacker )
  1196.     {
  1197.         g_bSmokerShoved[victim] = true;
  1198.     }
  1199.    
  1200.     //PrintDebug(0, "shove by %i on %i", attacker, victim );
  1201.     return Plugin_Continue;
  1202. }
  1203.  
  1204. public Action: Event_LungePounce( Handle:event, const String:name[], bool:dontBroadcast )
  1205. {
  1206.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1207.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  1208.    
  1209.     g_fPinTime[client][0] = GetGameTime();
  1210.    
  1211.     // clear hunter-hit stats (not skeeted)
  1212.     ResetHunter(client);
  1213.    
  1214.     // check if it was a DP    
  1215.     // ignore if no real pounce start pos
  1216.     if (    g_fPouncePosition[client][0] == 0.0
  1217.         &&  g_fPouncePosition[client][1] == 0.0
  1218.         &&  g_fPouncePosition[client][2] == 0.0
  1219.     ) {
  1220.         return Plugin_Continue;
  1221.     }
  1222.        
  1223.     new Float: endPos[3];
  1224.     GetClientAbsOrigin( client, endPos );
  1225.     new Float: fHeight = g_fPouncePosition[client][2] - endPos[2];
  1226.    
  1227.     // from pounceannounce:
  1228.     // distance supplied isn't the actual 2d vector distance needed for damage calculation. See more about it at
  1229.     // http://forums.alliedmods.net/showthread.php?t=93207
  1230.    
  1231.     new Float: fMin = GetConVarFloat(g_hCvarMinPounceDistance);
  1232.     new Float: fMax = GetConVarFloat(g_hCvarMaxPounceDistance);
  1233.     new Float: fMaxDmg = GetConVarFloat(g_hCvarMaxPounceDamage);
  1234.    
  1235.     // calculate 2d distance between previous position and pounce position
  1236.     new distance = RoundToNearest( GetVectorDistance(g_fPouncePosition[client], endPos) );
  1237.    
  1238.     // get damage using hunter damage formula
  1239.     // check if this is accurate, seems to differ from actual damage done!
  1240.     new Float: fDamage = ( ( (float(distance) - fMin) / (fMax - fMin) ) * fMaxDmg ) + 1.0;
  1241.  
  1242.     // apply bounds
  1243.     if (fDamage < 0.0) {
  1244.         fDamage = 0.0;
  1245.     } else if (fDamage > fMaxDmg + 1.0) {
  1246.         fDamage = fMaxDmg + 1.0;
  1247.     }
  1248.    
  1249.     new Handle: pack = CreateDataPack();
  1250.     WritePackCell( pack, client );
  1251.     WritePackCell( pack, victim );
  1252.     WritePackFloat( pack, fDamage );
  1253.     WritePackFloat( pack, fHeight );
  1254.     CreateTimer( 0.05, Timer_HunterDP, pack );
  1255.    
  1256.     return Plugin_Continue;
  1257. }
  1258.  
  1259. public Action: Timer_HunterDP( Handle:timer, Handle:pack )
  1260. {
  1261.     ResetPack( pack );
  1262.     new client = ReadPackCell( pack );
  1263.     new victim = ReadPackCell( pack );
  1264.     new Float: fDamage = ReadPackFloat( pack );
  1265.     new Float: fHeight = ReadPackFloat( pack );
  1266.     CloseHandle( pack );
  1267.    
  1268.     HandleHunterDP( client, victim, g_iPounceDamage[client], fDamage, fHeight );
  1269. }
  1270.  
  1271. public Action: Event_PlayerJumped( Handle:event, const String:name[], bool:dontBroadcast )
  1272. {
  1273.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1274.    
  1275.     if ( IS_VALID_INFECTED(client) )
  1276.     {
  1277.         new zClass = GetEntProp(client, Prop_Send, "m_zombieClass");
  1278.         if ( zClass != ZC_JOCKEY ) { return Plugin_Continue; }
  1279.    
  1280.         // where did jockey jump from?
  1281.         GetClientAbsOrigin( client, g_fPouncePosition[client] );
  1282.     }
  1283.     else if ( IS_VALID_SURVIVOR(client) )
  1284.     {
  1285.         // could be the start or part of a hopping streak
  1286.        
  1287.         new Float: fPos[3], Float: fVel[3];
  1288.         GetClientAbsOrigin( client, fPos );
  1289.         GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVel );
  1290.         fVel[2] = 0.0; // safeguard
  1291.        
  1292.         new Float: fLengthNew, Float: fLengthOld;
  1293.         fLengthNew = GetVectorLength(fVel);
  1294.        
  1295.        
  1296.         g_bHopCheck[client] = false;
  1297.        
  1298.         if ( !g_bIsHopping[client] )
  1299.         {
  1300.             if ( fLengthNew >= GetConVarFloat(g_hCvarBHopMinInitSpeed) )
  1301.             {
  1302.                 // starting potential hop streak
  1303.                 g_fHopTopVelocity[client] = fLengthNew;
  1304.                 g_bIsHopping[client] = true;
  1305.                 g_iHops[client] = 0;
  1306.             }
  1307.         }
  1308.         else
  1309.         {
  1310.             // check for hopping streak
  1311.             fLengthOld = GetVectorLength(g_fLastHop[client]);
  1312.            
  1313.             // if they picked up speed, count it as a hop, otherwise, we're done hopping
  1314.             if ( fLengthNew - fLengthOld > HOP_ACCEL_THRESH || fLengthNew >= GetConVarFloat(g_hCvarBHopContSpeed) )
  1315.             {
  1316.                 g_iHops[client]++;
  1317.                
  1318.                 // this should always be the case...
  1319.                 if ( fLengthNew > g_fHopTopVelocity[client] )
  1320.                 {
  1321.                     g_fHopTopVelocity[client] = fLengthNew;
  1322.                 }
  1323.                
  1324.                 //PrintToChat( client, "bunnyhop %i: speed: %.1f / increase: %.1f", g_iHops[client], fLengthNew, fLengthNew - fLengthOld );
  1325.             }
  1326.             else
  1327.             {
  1328.                 g_bIsHopping[client] = false;
  1329.                
  1330.                 if ( g_iHops[client] )
  1331.                 {
  1332.                     HandleBHopStreak( client, g_iHops[client], g_fHopTopVelocity[client] );
  1333.                     g_iHops[client] = 0;
  1334.                 }
  1335.             }
  1336.         }
  1337.        
  1338.         g_fLastHop[client][0] = fVel[0];
  1339.         g_fLastHop[client][1] = fVel[1];
  1340.         g_fLastHop[client][2] = fVel[2];
  1341.        
  1342.         if ( g_iHops[client] != 0 )
  1343.         {
  1344.             // check when the player returns to the ground
  1345.             CreateTimer( HOP_CHECK_TIME, Timer_CheckHop, client, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE );
  1346.         }
  1347.     }
  1348.    
  1349.     return Plugin_Continue;
  1350. }
  1351.  
  1352. public Action: Timer_CheckHop (Handle:timer, any:client)
  1353. {
  1354.     // player back to ground = end of hop (streak)?
  1355.    
  1356.     if ( !IS_VALID_INGAME(client) || !IsPlayerAlive(client) )
  1357.     {
  1358.         // streak stopped by dying / teamswitch / disconnect?
  1359.         return Plugin_Stop;
  1360.     }
  1361.     else if ( GetEntityFlags(client) & FL_ONGROUND )
  1362.     {
  1363.         new Float: fVel[3];
  1364.         GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVel );
  1365.         fVel[2] = 0.0; // safeguard
  1366.        
  1367.         //PrintToChatAll("grounded %i: vel length: %.1f", client, GetVectorLength(fVel) );
  1368.        
  1369.         g_bHopCheck[client] = true;
  1370.        
  1371.         CreateTimer( HOPEND_CHECK_TIME, Timer_CheckHopStreak, client, TIMER_FLAG_NO_MAPCHANGE );
  1372.        
  1373.         return Plugin_Stop;
  1374.     }
  1375.    
  1376.     return Plugin_Continue;
  1377. }
  1378.  
  1379. public Action: Timer_CheckHopStreak (Handle:timer, any:client)
  1380. {
  1381.     if ( !IS_VALID_INGAME(client) || !IsPlayerAlive(client) ) { return Plugin_Continue; }
  1382.    
  1383.     // check if we have any sort of hop streak, and report
  1384.     if ( g_bHopCheck[client] && g_iHops[client] )
  1385.     {
  1386.         HandleBHopStreak( client, g_iHops[client], g_fHopTopVelocity[client] );
  1387.         g_bIsHopping[client] = false;
  1388.         g_iHops[client] = 0;
  1389.         g_fHopTopVelocity[client] = 0.0;
  1390.     }
  1391.    
  1392.     g_bHopCheck[client] = false;
  1393.    
  1394.     return Plugin_Continue;
  1395. }
  1396.  
  1397.  
  1398. public Action: Event_PlayerJumpApex( Handle:event, const String:name[], bool:dontBroadcast )
  1399. {
  1400.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1401.    
  1402.     if ( g_bIsHopping[client] )
  1403.     {
  1404.         new Float: fVel[3];
  1405.         GetEntPropVector(client, Prop_Data, "m_vecVelocity", fVel );
  1406.         fVel[2] = 0.0;
  1407.         new Float: fLength = GetVectorLength(fVel);
  1408.        
  1409.         if ( fLength > g_fHopTopVelocity[client] )
  1410.         {
  1411.             g_fHopTopVelocity[client] = fLength;
  1412.         }
  1413.     }
  1414. }
  1415.  
  1416.    
  1417. public Action: Event_JockeyRide( Handle:event, const String:name[], bool:dontBroadcast )
  1418. {
  1419.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1420.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  1421.    
  1422.     if ( !IS_VALID_INFECTED(client) || !IS_VALID_SURVIVOR(victim) ) { return Plugin_Continue; }
  1423.    
  1424.     g_fPinTime[client][0] = GetGameTime();
  1425.    
  1426.     // minimum distance travelled?
  1427.     // ignore if no real pounce start pos
  1428.     if ( g_fPouncePosition[client][0] == 0.0 && g_fPouncePosition[client][1] == 0.0 && g_fPouncePosition[client][2] == 0.0 ) { return Plugin_Continue; }
  1429.    
  1430.     new Float: endPos[3];
  1431.     GetClientAbsOrigin( client, endPos );
  1432.     new Float: fHeight = g_fPouncePosition[client][2] - endPos[2];
  1433.    
  1434.     //PrintToChatAll("jockey height: %.3f", fHeight);
  1435.    
  1436.     // (high) pounce
  1437.     HandleJockeyDP( client, victim, fHeight );
  1438.    
  1439.     return Plugin_Continue;
  1440. }
  1441.  
  1442. public Action: Event_AbilityUse( Handle:event, const String:name[], bool:dontBroadcast )
  1443. {
  1444.     // track hunters pouncing
  1445.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1446.     new String: abilityName[64];
  1447.     GetEventString( event, "ability", abilityName, sizeof(abilityName) );
  1448.    
  1449.     if ( !IS_VALID_INGAME(client) ) { return Plugin_Continue; }
  1450.    
  1451.     new strAbility: ability;
  1452.     if ( !GetTrieValue(g_hTrieAbility, abilityName, ability) ) { return Plugin_Continue; }
  1453.    
  1454.     switch ( ability )
  1455.     {
  1456.         case ABL_HUNTERLUNGE:
  1457.         {
  1458.             // hunter started a pounce
  1459.             ResetHunter(client);
  1460.             GetClientAbsOrigin( client, g_fPouncePosition[client] );
  1461.         }
  1462.    
  1463.         case ABL_ROCKTHROW:
  1464.         {
  1465.             // tank throws rock
  1466.             g_iRocksBeingThrown[g_iRocksBeingThrownCount] = client;
  1467.            
  1468.             // safeguard
  1469.             if ( g_iRocksBeingThrownCount < 9 ) { g_iRocksBeingThrownCount++; }
  1470.         }
  1471.     }
  1472.    
  1473.     return Plugin_Continue;
  1474. }
  1475.  
  1476. // charger carrying
  1477. public Action: Event_ChargeCarryStart( Handle:event, const String:name[], bool:dontBroadcast )
  1478. {
  1479.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1480.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  1481.     if ( !IS_VALID_INFECTED(client) ) { return; }
  1482.  
  1483.     PrintDebug(0, "Charge carry start: %i - %i -- time: %.2f", client, victim, GetGameTime() );
  1484.    
  1485.     g_fChargeTime[client] = GetGameTime();
  1486.     g_fPinTime[client][0] = g_fChargeTime[client];
  1487.     g_fPinTime[client][1] = 0.0;
  1488.    
  1489.     if ( !IS_VALID_SURVIVOR(victim) ) { return; }
  1490.    
  1491.     g_iChargeVictim[client] = victim;           // store who we're carrying (as long as this is set, it's not considered an impact charge flight)
  1492.     g_iVictimCharger[victim] = client;          // store who's charging whom
  1493.     g_iVictimFlags[victim] = VICFLG_CARRIED;    // reset flags for checking later - we know only this now
  1494.     g_fChargeTime[victim] = g_fChargeTime[client];
  1495.     g_iVictimMapDmg[victim] = 0;
  1496.    
  1497.     GetClientAbsOrigin( victim, g_fChargeVictimPos[victim] );
  1498.    
  1499.     //CreateTimer( CHARGE_CHECK_TIME, Timer_ChargeCheck, client, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE );
  1500.     CreateTimer( CHARGE_CHECK_TIME, Timer_ChargeCheck, victim, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE );
  1501. }
  1502.  
  1503. public Action: Event_ChargeImpact( Handle:event, const String:name[], bool:dontBroadcast )
  1504. {
  1505.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1506.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  1507.     if ( !IS_VALID_INFECTED(client) || !IS_VALID_SURVIVOR(victim) ) { return; }
  1508.    
  1509.     // remember how many people the charger bumped into, and who, and where they were
  1510.     GetClientAbsOrigin( victim, g_fChargeVictimPos[victim] );
  1511.    
  1512.     g_iVictimCharger[victim] = client;      // store who we've bumped up
  1513.     g_iVictimFlags[victim] = 0;             // reset flags for checking later
  1514.     g_fChargeTime[victim] = GetGameTime();  // store time per victim, for impacts
  1515.     g_iVictimMapDmg[victim] = 0;
  1516.    
  1517.     CreateTimer( CHARGE_CHECK_TIME, Timer_ChargeCheck, victim, TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE );
  1518. }
  1519.  
  1520. public Action: Event_ChargePummelStart( Handle:event, const String:name[], bool:dontBroadcast )
  1521. {
  1522.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1523.    
  1524.     if ( !IS_VALID_INFECTED(client) ) { return; }
  1525.    
  1526.     g_fPinTime[client][1] = GetGameTime();
  1527. }
  1528.  
  1529.  
  1530. public Action: Event_ChargeCarryEnd( Handle:event, const String:name[], bool:dontBroadcast )
  1531. {
  1532.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1533.     if ( client < 1 || client > MaxClients ) { return; }
  1534.    
  1535.     g_fPinTime[client][1] = GetGameTime();
  1536.    
  1537.     // delay so we can check whether charger died 'mid carry'
  1538.     CreateTimer( 0.1, Timer_ChargeCarryEnd, client, TIMER_FLAG_NO_MAPCHANGE );
  1539. }
  1540.  
  1541. public Action: Timer_ChargeCarryEnd( Handle:timer, any:client )
  1542. {
  1543.     // set charge time to 0 to avoid deathcharge timer continuing
  1544.     g_iChargeVictim[client] = 0;        // unset this so the repeated timer knows to stop for an ongroundcheck
  1545. }
  1546.  
  1547. public Action: Timer_ChargeCheck( Handle:timer, any:client )
  1548. {
  1549.     // if something went wrong with the survivor or it was too long ago, forget about it
  1550.     if ( !IS_VALID_SURVIVOR(client) || !g_iVictimCharger[client] || g_fChargeTime[client] == 0.0 || FloatSub( GetGameTime(), g_fChargeTime[client]) > MAX_CHARGE_TIME )
  1551.     {
  1552.         return Plugin_Stop;
  1553.     }
  1554.    
  1555.     // we're done checking if either the victim reached the ground, or died
  1556.     if ( !IsPlayerAlive(client) )
  1557.     {
  1558.         // player died (this was .. probably.. a death charge)
  1559.         g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_AIRDEATH;
  1560.        
  1561.         // check conditions now
  1562.         CreateTimer( 0.0, Timer_DeathChargeCheck, client, TIMER_FLAG_NO_MAPCHANGE );
  1563.        
  1564.         return Plugin_Stop;
  1565.     }
  1566.     else if ( GetEntityFlags(client) & FL_ONGROUND && g_iChargeVictim[ g_iVictimCharger[client] ] != client )
  1567.     {
  1568.         // survivor reached the ground and didn't die (yet)
  1569.         // the client-check condition checks whether the survivor is still being carried by the charger
  1570.         //      (in which case it doesn't matter that they're on the ground)
  1571.        
  1572.         // check conditions with small delay (to see if they still die soon)
  1573.         CreateTimer( CHARGE_END_CHECK, Timer_DeathChargeCheck, client, TIMER_FLAG_NO_MAPCHANGE );
  1574.        
  1575.         return Plugin_Stop;
  1576.     }
  1577.    
  1578.     return Plugin_Continue;
  1579. }
  1580.  
  1581. public Action: Timer_DeathChargeCheck( Handle:timer, any:client )
  1582. {
  1583.     if ( !IS_VALID_INGAME(client) ) { return; }
  1584.    
  1585.     // check conditions.. if flags match up, it's a DC
  1586.     PrintDebug( 3, "Checking charge victim: %i - %i - flags: %i (alive? %i)", g_iVictimCharger[client], client, g_iVictimFlags[client], IsPlayerAlive(client) );
  1587.    
  1588.     new flags = g_iVictimFlags[client];
  1589.    
  1590.     if ( !IsPlayerAlive(client) )
  1591.     {
  1592.         new Float: pos[3];
  1593.         GetClientAbsOrigin( client, pos );
  1594.         new Float: fHeight = g_fChargeVictimPos[client][2] - pos[2];
  1595.        
  1596.         /*
  1597.             it's a deathcharge when:
  1598.                 the survivor is dead AND
  1599.                     they drowned/fell AND took enough damage or died in mid-air
  1600.                     AND not killed by someone else
  1601.                     OR is in an unreachable spot AND dropped at least X height
  1602.                     OR took plenty of map damage
  1603.                
  1604.             old.. need?
  1605.                 fHeight > GetConVarFloat(g_hCvarDeathChargeHeight)
  1606.         */
  1607.         if (    (   ( flags & VICFLG_DROWN || flags & VICFLG_FALL ) &&
  1608.                     ( flags & VICFLG_HURTLOTS || flags & VICFLG_AIRDEATH ) ||
  1609.                     ( flags & VICFLG_WEIRDFLOW && fHeight >= MIN_FLOWDROPHEIGHT ) ||
  1610.                     g_iVictimMapDmg[client] >= MIN_DC_TRIGGER_DMG
  1611.                 ) &&
  1612.                 !( flags & VICFLG_KILLEDBYOTHER )
  1613.         ) {
  1614.             HandleDeathCharge( g_iVictimCharger[client], client, fHeight, GetVectorDistance(g_fChargeVictimPos[client], pos, false), bool:(flags & VICFLG_CARRIED) );
  1615.         }
  1616.     }
  1617.     else if (   ( flags & VICFLG_WEIRDFLOW || g_iVictimMapDmg[client] >= MIN_DC_RECHECK_DMG ) &&
  1618.                 !(flags & VICFLG_WEIRDFLOWDONE)
  1619.     ) {
  1620.         // could be incapped and dying more slowly
  1621.         // flag only gets set on preincap, so don't need to check for incap
  1622.         g_iVictimFlags[client] = g_iVictimFlags[client] | VICFLG_WEIRDFLOWDONE;
  1623.        
  1624.         CreateTimer( CHARGE_END_RECHECK, Timer_DeathChargeCheck, client, TIMER_FLAG_NO_MAPCHANGE );
  1625.     }
  1626. }
  1627.  
  1628. stock ResetHunter(client)
  1629. {
  1630.     g_iHunterShotDmgTeam[client] = 0;
  1631.    
  1632.     for ( new i=1; i <= MaxClients; i++ )
  1633.     {
  1634.         g_iHunterShotDmg[client][i] = 0;
  1635.         g_fHunterShotStart[client][i] = 0.0;
  1636.     }
  1637.     g_iHunterOverkill[client] = 0;
  1638. }
  1639.  
  1640.  
  1641. // entity creation
  1642. public OnEntityCreated ( entity, const String:classname[] )
  1643. {
  1644.     if ( entity < 1 || !IsValidEntity(entity) || !IsValidEdict(entity) ) { return; }
  1645.    
  1646.     // track infected / witches, so damage on them counts as hits
  1647.    
  1648.     new strOEC: classnameOEC;
  1649.     if (!GetTrieValue(g_hTrieEntityCreated, classname, classnameOEC)) { return; }
  1650.    
  1651.     switch ( classnameOEC )
  1652.     {
  1653.         case OEC_TANKROCK:
  1654.         {
  1655.             decl String:rock_key[10];
  1656.             FormatEx(rock_key, sizeof(rock_key), "%x", entity);
  1657.             new rock_array[3];
  1658.            
  1659.             // store which tank is throwing what rock
  1660.             new tank = ShiftTankThrower();
  1661.            
  1662.             if ( IS_VALID_INGAME(tank) )
  1663.             {
  1664.                 g_iTankRock[tank] = entity;
  1665.                 rock_array[rckTank] = tank;
  1666.             }
  1667.             SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true);
  1668.            
  1669.             SDKHook(entity, SDKHook_TraceAttack, TraceAttack_Rock);
  1670.             SDKHook(entity, SDKHook_Touch, OnTouch_Rock);
  1671.         }
  1672.        
  1673.        
  1674.         case OEC_CARALARM:
  1675.         {
  1676.             decl String:car_key[10];
  1677.             FormatEx(car_key, sizeof(car_key), "%x", entity);
  1678.            
  1679.             SDKHook(entity, SDKHook_OnTakeDamage, OnTakeDamage_Car);
  1680.             SDKHook(entity, SDKHook_Touch, OnTouch_Car);
  1681.            
  1682.             SDKHook(entity, SDKHook_Spawn, OnEntitySpawned_CarAlarm);
  1683.         }
  1684.        
  1685.         case OEC_CARGLASS:
  1686.         {
  1687.             SDKHook(entity, SDKHook_OnTakeDamage, OnTakeDamage_CarGlass);
  1688.             SDKHook(entity, SDKHook_Touch, OnTouch_CarGlass);
  1689.            
  1690.             //SetTrieValue(g_hCarTrie, car_key, );
  1691.             SDKHook(entity, SDKHook_Spawn, OnEntitySpawned_CarAlarmGlass);
  1692.         }
  1693.     }
  1694. }
  1695.  
  1696. public OnEntitySpawned_CarAlarm ( entity )
  1697. {
  1698.     if ( !IsValidEntity(entity) ) { return; }
  1699.    
  1700.     decl String:car_key[10];
  1701.     FormatEx(car_key, sizeof(car_key), "%x", entity);
  1702.    
  1703.     decl String:target[48];
  1704.     GetEntPropString(entity, Prop_Data, "m_iName", target, sizeof(target));
  1705.    
  1706.     SetTrieValue( g_hCarTrie, target, entity );
  1707.     SetTrieValue( g_hCarTrie, car_key, 0 );         // who shot the car?
  1708.    
  1709.     HookSingleEntityOutput( entity, "OnCarAlarmStart", Hook_CarAlarmStart );
  1710. }
  1711.  
  1712. public OnEntitySpawned_CarAlarmGlass ( entity )
  1713. {
  1714.     if ( !IsValidEntity(entity) ) { return; }
  1715.    
  1716.     // glass is parented to a car, link the two through the trie
  1717.     // find parent and save both
  1718.     decl String:car_key[10];
  1719.     FormatEx(car_key, sizeof(car_key), "%x", entity);
  1720.    
  1721.     decl String:parent[48];
  1722.     GetEntPropString(entity, Prop_Data, "m_iParent", parent, sizeof(parent));
  1723.     new parentEntity;
  1724.    
  1725.     // find targetname in trie
  1726.     if ( GetTrieValue(g_hCarTrie, parent, parentEntity ) )
  1727.     {
  1728.         // if valid entity, save the parent entity
  1729.         if ( IsValidEntity(parentEntity) )
  1730.         {
  1731.             SetTrieValue( g_hCarTrie, car_key, parentEntity );
  1732.            
  1733.             decl String:car_key_p[10];
  1734.             FormatEx(car_key_p, sizeof(car_key_p), "%x_A", parentEntity);
  1735.             new testEntity;
  1736.            
  1737.             if ( GetTrieValue(g_hCarTrie, car_key_p, testEntity) )
  1738.             {
  1739.                 // second glass
  1740.                 FormatEx(car_key_p, sizeof(car_key_p), "%x_B", parentEntity);
  1741.             }
  1742.            
  1743.             SetTrieValue( g_hCarTrie, car_key_p, entity );
  1744.         }
  1745.     }
  1746. }
  1747.  
  1748. // entity destruction
  1749. public OnEntityDestroyed ( entity )
  1750. {
  1751.     decl String:witch_key[10];
  1752.     FormatEx(witch_key, sizeof(witch_key), "%x", entity);
  1753.    
  1754.     decl rock_array[3];
  1755.     if ( GetTrieArray(g_hRockTrie, witch_key, rock_array, sizeof(rock_array)) )
  1756.     {
  1757.         // tank rock
  1758.         CreateTimer( ROCK_CHECK_TIME, Timer_CheckRockSkeet, entity );
  1759.         SDKUnhook(entity, SDKHook_TraceAttack, TraceAttack_Rock);
  1760.         return;
  1761.     }
  1762.  
  1763.     decl witch_array[MAXPLAYERS+DMGARRAYEXT];
  1764.     if ( GetTrieArray(g_hWitchTrie, witch_key, witch_array, sizeof(witch_array)) )
  1765.     {
  1766.         // witch
  1767.         //  delayed deletion, to avoid potential problems with crowns not detecting
  1768.         CreateTimer( WITCH_DELETE_TIME, Timer_WitchKeyDelete, entity );
  1769.         SDKUnhook(entity, SDKHook_OnTakeDamagePost, OnTakeDamagePost_Witch);
  1770.         return;
  1771.     }
  1772. }
  1773.  
  1774. public Action: Timer_WitchKeyDelete (Handle:timer, any:witch)
  1775. {
  1776.     decl String:witch_key[10];
  1777.     FormatEx(witch_key, sizeof(witch_key), "%x", witch);
  1778.     RemoveFromTrie(g_hWitchTrie, witch_key);
  1779. }
  1780.  
  1781.  
  1782. public Action: Timer_CheckRockSkeet (Handle:timer, any:rock)
  1783. {
  1784.     decl rock_array[3];
  1785.     decl String: rock_key[10];
  1786.     FormatEx(rock_key, sizeof(rock_key), "%x", rock);
  1787.     if (!GetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array)) ) { return Plugin_Continue; }
  1788.    
  1789.     RemoveFromTrie(g_hRockTrie, rock_key);
  1790.    
  1791.     // if rock didn't hit anyone / didn't touch anything, it was shot
  1792.     if ( rock_array[rckDamage] > 0 )
  1793.     {
  1794.         HandleRockSkeeted( rock_array[rckSkeeter], rock_array[rckTank] );
  1795.     }
  1796.    
  1797.     return Plugin_Continue;
  1798. }
  1799.  
  1800. // boomer got somebody
  1801. public Action: Event_PlayerBoomed (Handle:event, const String:name[], bool:dontBroadcast)
  1802. {
  1803.     new attacker = GetClientOfUserId( GetEventInt(event, "attacker") );
  1804.     new bool: byBoom = GetEventBool(event, "by_boomer");
  1805.    
  1806.     if ( byBoom && IS_VALID_INFECTED(attacker) )
  1807.     {
  1808.         g_bBoomerHitSomebody[attacker] = true;
  1809.        
  1810.         // check if it was vomit spray
  1811.         new bool: byExplosion = GetEventBool(event, "exploded");
  1812.         if ( !byExplosion )
  1813.         {
  1814.             // count amount of booms
  1815.             if ( !g_iBoomerVomitHits[attacker] ) {
  1816.                 // check for boom count later
  1817.                 CreateTimer( VOMIT_DURATION_TIME, Timer_BoomVomitCheck, attacker, TIMER_FLAG_NO_MAPCHANGE );
  1818.             }
  1819.             g_iBoomerVomitHits[attacker]++;
  1820.         }
  1821.     }
  1822. }
  1823. // check how many booms landed
  1824. public Action: Timer_BoomVomitCheck ( Handle:timer, any:client )
  1825. {
  1826.     HandleVomitLanded( client, g_iBoomerVomitHits[client] );
  1827.     g_iBoomerVomitHits[client] = 0;
  1828. }
  1829.  
  1830. // boomers that didn't bile anyone
  1831. public Action: Event_BoomerExploded (Handle:event, const String:name[], bool:dontBroadcast)
  1832. {
  1833.     new client = GetClientOfUserId( GetEventInt(event, "userid") );
  1834.     new bool: biled = GetEventBool(event, "splashedbile");
  1835.     if ( !biled && !g_bBoomerHitSomebody[client] )
  1836.     {
  1837.         new attacker = GetClientOfUserId( GetEventInt(event, "attacker") );
  1838.         if ( IS_VALID_SURVIVOR(attacker) )
  1839.         {
  1840.             HandlePop( attacker, client, g_iBoomerGotShoved[client], FloatSub(GetGameTime(), g_fSpawnTime[client]) );
  1841.         }
  1842.     }
  1843. }
  1844.  
  1845. // crown tracking
  1846. public Action: Event_WitchSpawned ( Handle:event, const String:name[], bool:dontBroadcast )
  1847. {
  1848.     new witch = GetEventInt(event, "witchid");
  1849.    
  1850.     SDKHook(witch, SDKHook_OnTakeDamagePost, OnTakeDamagePost_Witch);
  1851.    
  1852.     new witch_dmg_array[MAXPLAYERS+DMGARRAYEXT];
  1853.     decl String:witch_key[10];
  1854.     FormatEx(witch_key, sizeof(witch_key), "%x", witch);
  1855.     witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_HEALTH)] = GetConVarInt(g_hCvarWitchHealth);
  1856.     SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false);
  1857. }
  1858.  
  1859. public Action: Event_WitchKilled ( Handle:event, const String:name[], bool:dontBroadcast )
  1860. {
  1861.     new witch = GetEventInt(event, "witchid");
  1862.     new attacker = GetClientOfUserId( GetEventInt(event, "userid") );
  1863.     SDKUnhook(witch, SDKHook_OnTakeDamagePost, OnTakeDamagePost_Witch);
  1864.    
  1865.     if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; }
  1866.    
  1867.     new bool: bOneShot = GetEventBool(event, "oneshot");
  1868.    
  1869.     // is it a crown / drawcrown?
  1870.     new Handle: pack = CreateDataPack();
  1871.     WritePackCell( pack, attacker );
  1872.     WritePackCell( pack, witch );
  1873.     WritePackCell( pack, (bOneShot) ? 1 : 0 );
  1874.     CreateTimer( WITCH_CHECK_TIME, Timer_CheckWitchCrown, pack );
  1875.    
  1876.     return Plugin_Continue;
  1877. }
  1878. public Action: Event_WitchHarasserSet ( Handle:event, const String:name[], bool:dontBroadcast )
  1879. {
  1880.     new witch = GetEventInt(event, "witchid");
  1881.    
  1882.     decl String:witch_key[10];
  1883.     FormatEx(witch_key, sizeof(witch_key), "%x", witch);
  1884.     decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT];
  1885.    
  1886.     if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) )
  1887.     {
  1888.         for ( new i = 0; i <= MAXPLAYERS; i++ )
  1889.         {
  1890.             witch_dmg_array[i] = 0;
  1891.         }
  1892.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_HEALTH)] = GetConVarInt(g_hCvarWitchHealth);
  1893.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_STARTLED)] = 1;  // harasser set
  1894.         SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false);
  1895.     }
  1896.     else
  1897.     {
  1898.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_STARTLED)] = 1;  // harasser set
  1899.         SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true);
  1900.     }
  1901. }
  1902.  
  1903. public Action: OnTakeDamageByWitch ( victim, &attacker, &inflictor, &Float:damage, &damagetype )
  1904. {
  1905.     // if a survivor is hit by a witch, note it in the witch damage array (maxplayers+2 = 1)
  1906.     if ( IS_VALID_SURVIVOR(victim) && damage > 0.0 )
  1907.     {
  1908.        
  1909.         // not a crown if witch hit anyone for > 0 damage
  1910.         if ( IsWitch(attacker) )
  1911.         {
  1912.             decl String:witch_key[10];
  1913.             FormatEx(witch_key, sizeof(witch_key), "%x", attacker);
  1914.             decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT];
  1915.            
  1916.             if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) )
  1917.             {
  1918.                 for ( new i = 0; i <= MAXPLAYERS; i++ )
  1919.                 {
  1920.                     witch_dmg_array[i] = 0;
  1921.                 }
  1922.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_HEALTH)] = GetConVarInt(g_hCvarWitchHealth);
  1923.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_GOTSLASH)] = 1;  // failed
  1924.                 SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false);
  1925.             }
  1926.             else
  1927.             {
  1928.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_GOTSLASH)] = 1;  // failed
  1929.                 SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true);
  1930.             }
  1931.         }
  1932.     }
  1933. }
  1934.  
  1935. public OnTakeDamagePost_Witch ( victim, attacker, inflictor, Float:damage, damagetype )
  1936. {
  1937.     // only called for witches, so no check required
  1938.    
  1939.     decl String:witch_key[10];
  1940.     FormatEx(witch_key, sizeof(witch_key), "%x", victim);
  1941.     decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT];
  1942.    
  1943.     if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) )
  1944.     {
  1945.         for ( new i = 0; i <= MAXPLAYERS; i++ )
  1946.         {
  1947.             witch_dmg_array[i] = 0;
  1948.         }
  1949.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_HEALTH)] = GetConVarInt(g_hCvarWitchHealth);
  1950.         SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, false);
  1951.     }
  1952.    
  1953.     // store damage done to witch
  1954.     if ( IS_VALID_SURVIVOR(attacker) )
  1955.     {
  1956.         witch_dmg_array[attacker] += RoundToFloor(damage);
  1957.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_HEALTH)] -= RoundToFloor(damage);
  1958.        
  1959.         // remember last shot
  1960.         if ( g_fWitchShotStart[attacker] == 0.0 || FloatSub(GetGameTime(), g_fWitchShotStart[attacker]) > SHOTGUN_BLAST_TIME )
  1961.         {
  1962.             // reset last shot damage count and attacker
  1963.             g_fWitchShotStart[attacker] = GetGameTime();
  1964.             witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNER)] = attacker;
  1965.             witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] = 0;
  1966.             witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNTYPE)] = ( damagetype & DMG_BUCKSHOT ) ? 1 : 0; // only allow shotguns
  1967.         }
  1968.        
  1969.         // continued blast, add up
  1970.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] += RoundToFloor(damage);
  1971.        
  1972.         SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true);
  1973.     }
  1974.     else
  1975.     {
  1976.         // store all chip from other sources than survivor in [0]
  1977.         witch_dmg_array[0] += RoundToFloor(damage);
  1978.         //witch_dmg_array[MAXPLAYERS+1] -= RoundToFloor(damage);
  1979.         SetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT, true);
  1980.     }
  1981. }
  1982.  
  1983. public Action: Timer_CheckWitchCrown(Handle:timer, Handle:pack)
  1984. {
  1985.     ResetPack( pack );
  1986.     new attacker = ReadPackCell( pack );
  1987.     new witch = ReadPackCell( pack );
  1988.     new bool:bOneShot = bool:ReadPackCell( pack );
  1989.     CloseHandle( pack );
  1990.  
  1991.     CheckWitchCrown( witch, attacker, bOneShot );
  1992. }
  1993.  
  1994. stock CheckWitchCrown ( witch, attacker, bool: bOneShot = false )
  1995. {
  1996.     decl String:witch_key[10];
  1997.     FormatEx(witch_key, sizeof(witch_key), "%x", witch);
  1998.     decl witch_dmg_array[MAXPLAYERS+DMGARRAYEXT];
  1999.     if ( !GetTrieArray(g_hWitchTrie, witch_key, witch_dmg_array, MAXPLAYERS+DMGARRAYEXT) ) {
  2000.         PrintDebug(0, "Witch Crown Check: Error: Trie entry missing (entity: %i, oneshot: %i)", witch, bOneShot);
  2001.         return;
  2002.     }
  2003.    
  2004.     new chipDamage = 0;
  2005.     new iWitchHealth = GetConVarInt(g_hCvarWitchHealth);
  2006.    
  2007.     /*
  2008.         the attacker is the last one that did damage to witch
  2009.             if their damage is full damage on an unharrassed witch, it's a full crown
  2010.             if their damage is full or > drawcrown_threshhold, it's a drawcrown
  2011.     */
  2012.    
  2013.     // not a crown at all if anyone was hit, or if the killing damage wasn't a shotgun blast
  2014.    
  2015.     // safeguard: if it was a 'oneshot' witch kill, must've been a shotgun
  2016.     //      this is not enough: sometimes a shotgun crown happens that is not even reported as a oneshot...
  2017.     //      seems like the cause is that the witch post ontakedamage is not called in time?
  2018.     if ( bOneShot )
  2019.     {
  2020.         witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNTYPE)] = 1;
  2021.     }
  2022.    
  2023.     if ( witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_GOTSLASH)] || !witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNTYPE)] )
  2024.     {
  2025.         PrintDebug(0, "Witch Crown Check: Failed: bungled: %i / crowntype: %i (entity: %i)",
  2026.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_GOTSLASH)],
  2027.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNTYPE)],
  2028.                 witch
  2029.             );
  2030.         PrintDebug(1, "Witch Crown Check: Further details: attacker: %N, attacker dmg: %i, teamless dmg: %i",
  2031.                 attacker,
  2032.                 witch_dmg_array[attacker],
  2033.                 witch_dmg_array[0]
  2034.             );
  2035.         return;
  2036.     }
  2037.    
  2038.     PrintDebug(0, "Witch Crown Check: crown shot: %i, harrassed: %i (full health: %i / drawthresh: %i / oneshot %i)",
  2039.             witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)],
  2040.             witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_STARTLED)],
  2041.             iWitchHealth,
  2042.             GetConVarInt(g_hCvarDrawCrownThresh),
  2043.             bOneShot
  2044.         );
  2045.    
  2046.     // full crown? unharrassed
  2047.     if ( !witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_STARTLED)] && ( bOneShot || witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] >= iWitchHealth ) )
  2048.     {
  2049.         // make sure that we don't count any type of chip
  2050.         if ( GetConVarBool(g_hCvarHideFakeDamage) )
  2051.         {
  2052.             chipDamage = 0;
  2053.             for ( new i = 0; i <= MAXPLAYERS; i++ )
  2054.             {
  2055.                 if ( i == attacker ) { continue; }
  2056.                 chipDamage += witch_dmg_array[i];
  2057.             }
  2058.             witch_dmg_array[attacker] = iWitchHealth - chipDamage;
  2059.         }
  2060.         HandleCrown( attacker, witch_dmg_array[attacker] );
  2061.     }
  2062.     else if ( witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] >= GetConVarInt(g_hCvarDrawCrownThresh) )
  2063.     {
  2064.         // draw crown: harassed + over X damage done by one survivor -- in ONE shot
  2065.        
  2066.         for ( new i = 0; i <= MAXPLAYERS; i++ )
  2067.         {
  2068.             if ( i == attacker ) {
  2069.                 // count any damage done before final shot as chip
  2070.                 chipDamage += witch_dmg_array[i] - witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)];
  2071.             } else {
  2072.                 chipDamage += witch_dmg_array[i];
  2073.             }
  2074.         }
  2075.        
  2076.         // make sure that we don't count any type of chip
  2077.         if ( GetConVarBool(g_hCvarHideFakeDamage) )
  2078.         {
  2079.             // unlikely to happen, but if the chip was A LOT
  2080.             if ( chipDamage >= iWitchHealth ) {
  2081.                 chipDamage = iWitchHealth - 1;
  2082.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] = 1;
  2083.             }
  2084.             else {
  2085.                 witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] = iWitchHealth - chipDamage;
  2086.             }
  2087.             // re-check whether it qualifies as a drawcrown:
  2088.             if ( witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)] < GetConVarInt(g_hCvarDrawCrownThresh) ) { return; }
  2089.         }
  2090.        
  2091.         // plus, set final shot as 'damage', and the rest as chip
  2092.         HandleDrawCrown( attacker, witch_dmg_array[MAXPLAYERS+view_as<int>(WTCH_CROWNSHOT)], chipDamage );
  2093.     }
  2094.  
  2095.     // remove trie
  2096.  
  2097. }
  2098.  
  2099. // tank rock
  2100. public Action: TraceAttack_Rock (victim, &attacker, &inflictor, &Float:damage, &damagetype, &ammotype, hitbox, hitgroup)
  2101. {
  2102.     if ( IS_VALID_SURVIVOR(attacker) )
  2103.     {
  2104.         /*
  2105.             can't really use this for precise detection, though it does
  2106.             report the last shot -- the damage report is without distance falloff
  2107.         */
  2108.         decl String:rock_key[10];
  2109.         decl rock_array[3];
  2110.         FormatEx(rock_key, sizeof(rock_key), "%x", victim);
  2111.         GetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array));
  2112.         rock_array[rckDamage] += RoundToFloor(damage);
  2113.         rock_array[rckSkeeter] = attacker;
  2114.         SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true);
  2115.     }
  2116. }
  2117.  
  2118. public OnTouch_Rock ( entity )
  2119. {
  2120.     // remember that the rock wasn't shot
  2121.     decl String:rock_key[10];
  2122.     FormatEx(rock_key, sizeof(rock_key), "%x", entity);
  2123.     new rock_array[3];
  2124.     rock_array[rckDamage] = -1;
  2125.     SetTrieArray(g_hRockTrie, rock_key, rock_array, sizeof(rock_array), true);
  2126.    
  2127.     SDKUnhook(entity, SDKHook_Touch, OnTouch_Rock);
  2128. }
  2129.  
  2130. // smoker tongue cutting & self clears
  2131. public Action: Event_TonguePullStopped (Handle:event, const String:name[], bool:dontBroadcast)
  2132. {
  2133.     new attacker = GetClientOfUserId( GetEventInt(event, "userid") );
  2134.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  2135.     new smoker = GetClientOfUserId( GetEventInt(event, "smoker") );
  2136.     new reason = GetEventInt(event, "release_type");
  2137.    
  2138.     if ( !IS_VALID_SURVIVOR(attacker) || !IS_VALID_INFECTED(smoker) ) { return Plugin_Continue; }
  2139.    
  2140.     // clear check -  if the smoker itself was not shoved, handle the clear
  2141.     HandleClear( attacker, smoker, victim,
  2142.             ZC_SMOKER,
  2143.             (g_fPinTime[smoker][1] > 0.0) ? FloatSub( GetGameTime(), g_fPinTime[smoker][1]) : -1.0,
  2144.             FloatSub( GetGameTime(), g_fPinTime[smoker][0]),
  2145.             bool:( reason != CUT_SLASH && reason != CUT_KILL )
  2146.         );
  2147.    
  2148.     if ( attacker != victim ) { return Plugin_Continue; }
  2149.    
  2150.     if ( reason == CUT_KILL )
  2151.     {
  2152.         g_bSmokerClearCheck[smoker] = true;
  2153.     }
  2154.     else if ( g_bSmokerShoved[smoker] )
  2155.     {
  2156.         HandleSmokerSelfClear( attacker, smoker, true );
  2157.     }
  2158.     else if ( reason == CUT_SLASH ) // note: can't trust this to actually BE a slash..
  2159.     {
  2160.         // check weapon
  2161.         decl String:weapon[32];
  2162.         GetClientWeapon( attacker, weapon, 32 );
  2163.        
  2164.         // this doesn't count the chainsaw, but that's no-skill anyway
  2165.         if ( StrEqual(weapon, "weapon_melee", false) )
  2166.         {
  2167.             HandleTongueCut( attacker, smoker );
  2168.         }
  2169.     }
  2170.    
  2171.     return Plugin_Continue;
  2172. }
  2173.  
  2174. public Action: Event_TongueGrab (Handle:event, const String:name[], bool:dontBroadcast)
  2175. {
  2176.     new attacker = GetClientOfUserId( GetEventInt(event, "userid") );
  2177.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  2178.    
  2179.     if ( IS_VALID_INFECTED(attacker) && IS_VALID_SURVIVOR(victim) )
  2180.     {
  2181.         // new pull, clean damage
  2182.         g_bSmokerClearCheck[attacker] = false;
  2183.         g_bSmokerShoved[attacker] = false;
  2184.         g_iSmokerVictim[attacker] = victim;
  2185.         g_iSmokerVictimDamage[attacker] = 0;
  2186.         g_fPinTime[attacker][0] = GetGameTime();
  2187.         g_fPinTime[attacker][1] = 0.0;
  2188.     }
  2189.    
  2190.     return Plugin_Continue;
  2191. }
  2192.  
  2193. public Action: Event_ChokeStart (Handle:event, const String:name[], bool:dontBroadcast)
  2194. {
  2195.     new attacker = GetClientOfUserId( GetEventInt(event, "userid") );
  2196.    
  2197.     if ( g_fPinTime[attacker][0] == 0.0 ) { g_fPinTime[attacker][0] = GetGameTime(); }
  2198.     g_fPinTime[attacker][1] = GetGameTime();
  2199. }
  2200.  
  2201. public Action: Event_ChokeStop (Handle:event, const String:name[], bool:dontBroadcast)
  2202. {
  2203.     new attacker = GetClientOfUserId( GetEventInt(event, "userid") );
  2204.     new victim = GetClientOfUserId( GetEventInt(event, "victim") );
  2205.     new smoker = GetClientOfUserId( GetEventInt(event, "smoker") );
  2206.     new reason = GetEventInt(event, "release_type");
  2207.    
  2208.     if ( !IS_VALID_SURVIVOR(attacker) || !IS_VALID_INFECTED(smoker) ) { return; }
  2209.    
  2210.     // if the smoker itself was not shoved, handle the clear
  2211.     HandleClear( attacker, smoker, victim,
  2212.             ZC_SMOKER,
  2213.             (g_fPinTime[smoker][1] > 0.0) ? FloatSub( GetGameTime(), g_fPinTime[smoker][1]) : -1.0,
  2214.             FloatSub( GetGameTime(), g_fPinTime[smoker][0]),
  2215.             bool:( reason != CUT_SLASH && reason != CUT_KILL )
  2216.         );
  2217. }
  2218.  
  2219. // car alarm handling
  2220. public Hook_CarAlarmStart ( const String:output[], caller, activator, Float:delay )
  2221. {
  2222.     //decl String:car_key[10];
  2223.     //FormatEx(car_key, sizeof(car_key), "%x", entity);
  2224.    
  2225.     PrintDebug( 0, "calarm trigger: caller %i / activator %i / delay: %.2f", caller, activator, delay );
  2226. }
  2227. public Action: Event_CarAlarmGoesOff( Handle:event, const String:name[], bool:dontBroadcast )
  2228. {
  2229.     g_fLastCarAlarm = GetGameTime();
  2230. }
  2231.  
  2232. public Action: OnTakeDamage_Car ( victim, &attacker, &inflictor, &Float:damage, &damagetype )
  2233. {
  2234.     if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; }
  2235.    
  2236.     /*
  2237.         boomer popped on alarmed car =
  2238.             DMG_BLAST_SURFACE| DMG_BLAST
  2239.         and inflictor is the boomer
  2240.    
  2241.         melee slash/club =
  2242.             DMG_SLOWBURN|DMG_PREVENT_PHYSICS_FORCE + DMG_CLUB or DMG_SLASH
  2243.         shove is without DMG_SLOWBURN
  2244.     */
  2245.    
  2246.     CreateTimer( 0.01, Timer_CheckAlarm, victim, TIMER_FLAG_NO_MAPCHANGE );
  2247.    
  2248.     decl String:car_key[10];
  2249.     FormatEx(car_key, sizeof(car_key), "%x", victim);
  2250.     SetTrieValue(g_hCarTrie, car_key, attacker);
  2251.  
  2252.     if ( damagetype & DMG_BLAST )
  2253.     {
  2254.         if ( IS_VALID_INFECTED(inflictor) && GetEntProp(inflictor, Prop_Send, "m_zombieClass") == ZC_BOOMER ) {
  2255.             g_iLastCarAlarmReason[attacker] = CALARM_BOOMER;
  2256.             g_iLastCarAlarmBoomer = inflictor;
  2257.         } else {
  2258.             g_iLastCarAlarmReason[attacker] = CALARM_EXPLOSION;
  2259.         }
  2260.     }
  2261.     else if ( damage == 0.0 && ( damagetype & DMG_CLUB || damagetype & DMG_SLASH ) && !( damagetype & DMG_SLOWBURN) )
  2262.     {
  2263.         g_iLastCarAlarmReason[attacker] = CALARM_TOUCHED;
  2264.     }
  2265.     else
  2266.     {
  2267.         g_iLastCarAlarmReason[attacker] = CALARM_HIT;
  2268.     }
  2269.    
  2270.     return Plugin_Continue;
  2271. }
  2272.  
  2273. public OnTouch_Car ( entity, client )
  2274. {
  2275.     if ( !IS_VALID_SURVIVOR(client) ) { return; }
  2276.    
  2277.     CreateTimer( 0.01, Timer_CheckAlarm, entity, TIMER_FLAG_NO_MAPCHANGE );
  2278.    
  2279.     decl String:car_key[10];
  2280.     FormatEx(car_key, sizeof(car_key), "%x", entity);
  2281.     SetTrieValue(g_hCarTrie, car_key, client);
  2282.    
  2283.     g_iLastCarAlarmReason[client] = CALARM_TOUCHED;
  2284.    
  2285.     return;
  2286. }
  2287.  
  2288. public Action: OnTakeDamage_CarGlass ( victim, &attacker, &inflictor, &Float:damage, &damagetype )
  2289. {
  2290.     // check for either: boomer pop or survivor
  2291.     if ( !IS_VALID_SURVIVOR(attacker) ) { return Plugin_Continue; }
  2292.    
  2293.     decl String:car_key[10];
  2294.     FormatEx(car_key, sizeof(car_key), "%x", victim);
  2295.     new parentEntity;
  2296.    
  2297.     if ( GetTrieValue(g_hCarTrie, car_key, parentEntity) )
  2298.     {
  2299.         CreateTimer( 0.01, Timer_CheckAlarm, parentEntity, TIMER_FLAG_NO_MAPCHANGE );
  2300.        
  2301.         FormatEx(car_key, sizeof(car_key), "%x", parentEntity);
  2302.         SetTrieValue(g_hCarTrie, car_key, attacker);
  2303.        
  2304.         if ( damagetype & DMG_BLAST )
  2305.         {
  2306.             if ( IS_VALID_INFECTED(inflictor) && GetEntProp(inflictor, Prop_Send, "m_zombieClass") == ZC_BOOMER ) {
  2307.                 g_iLastCarAlarmReason[attacker] = CALARM_BOOMER;
  2308.                 g_iLastCarAlarmBoomer = inflictor;
  2309.             } else {
  2310.                 g_iLastCarAlarmReason[attacker] = CALARM_EXPLOSION;
  2311.             }
  2312.         }
  2313.         else if ( damage == 0.0 && ( damagetype & DMG_CLUB || damagetype & DMG_SLASH ) && !( damagetype & DMG_SLOWBURN) )
  2314.         {
  2315.             g_iLastCarAlarmReason[attacker] = CALARM_TOUCHED;
  2316.         }
  2317.         else
  2318.         {
  2319.             g_iLastCarAlarmReason[attacker] = CALARM_HIT;
  2320.         }
  2321.     }
  2322.    
  2323.     return Plugin_Continue;
  2324. }
  2325.  
  2326. public OnTouch_CarGlass ( entity, client )
  2327. {
  2328.     if ( !IS_VALID_SURVIVOR(client) ) { return; }
  2329.    
  2330.     decl String:car_key[10];
  2331.     FormatEx(car_key, sizeof(car_key), "%x", entity);
  2332.     new parentEntity;
  2333.    
  2334.     if ( GetTrieValue(g_hCarTrie, car_key, parentEntity) )
  2335.     {
  2336.         CreateTimer( 0.01, Timer_CheckAlarm, parentEntity, TIMER_FLAG_NO_MAPCHANGE );
  2337.        
  2338.         FormatEx(car_key, sizeof(car_key), "%x", parentEntity);
  2339.         SetTrieValue(g_hCarTrie, car_key, client);
  2340.        
  2341.         g_iLastCarAlarmReason[client] = CALARM_TOUCHED;
  2342.     }
  2343.    
  2344.     return;
  2345. }
  2346.  
  2347. public Action: Timer_CheckAlarm (Handle:timer, any:entity)
  2348. {
  2349.     //PrintToChatAll( "checking alarm: time: %.3f", GetGameTime() - g_fLastCarAlarm );
  2350.    
  2351.     if ( FloatSub(GetGameTime(), g_fLastCarAlarm) < CARALARM_MIN_TIME )
  2352.     {
  2353.         // got a match, drop stuff from trie and handle triggering
  2354.         decl String:car_key[10];
  2355.         new testEntity;
  2356.         new survivor = -1;
  2357.        
  2358.         // remove car glass
  2359.         FormatEx(car_key, sizeof(car_key), "%x_A", entity);
  2360.         if ( GetTrieValue(g_hCarTrie, car_key, testEntity) )
  2361.         {
  2362.             RemoveFromTrie(g_hCarTrie, car_key);
  2363.             SDKUnhook(testEntity, SDKHook_OnTakeDamage, OnTakeDamage_CarGlass);
  2364.             SDKUnhook(testEntity, SDKHook_Touch, OnTouch_CarGlass);
  2365.         }
  2366.         FormatEx(car_key, sizeof(car_key), "%x_B", entity);
  2367.         if ( GetTrieValue(g_hCarTrie, car_key, testEntity) )
  2368.         {
  2369.             RemoveFromTrie(g_hCarTrie, car_key);
  2370.             SDKUnhook(testEntity, SDKHook_OnTakeDamage, OnTakeDamage_CarGlass);
  2371.             SDKUnhook(testEntity, SDKHook_Touch, OnTouch_CarGlass);
  2372.         }
  2373.        
  2374.         // remove car
  2375.         FormatEx(car_key, sizeof(car_key), "%x", entity);
  2376.         if ( GetTrieValue(g_hCarTrie, car_key, survivor) )
  2377.         {
  2378.             RemoveFromTrie(g_hCarTrie, car_key);
  2379.             SDKUnhook(entity, SDKHook_OnTakeDamage, OnTakeDamage_Car);
  2380.             SDKUnhook(entity, SDKHook_Touch, OnTouch_Car);
  2381.         }
  2382.        
  2383.         // check for infected assistance
  2384.         new infected = 0;
  2385.         if ( IS_VALID_SURVIVOR(survivor) )
  2386.         {
  2387.             if ( g_iLastCarAlarmReason[survivor] == view_as<int>(CALARM_BOOMER) )
  2388.             {
  2389.                 infected = g_iLastCarAlarmBoomer;
  2390.             }
  2391.             else if ( IS_VALID_INFECTED(GetEntPropEnt(survivor, Prop_Send, "m_carryAttacker")) )
  2392.             {
  2393.                 infected = GetEntPropEnt(survivor, Prop_Send, "m_carryAttacker");
  2394.             }
  2395.             else if ( IS_VALID_INFECTED(GetEntPropEnt(survivor, Prop_Send, "m_jockeyAttacker")) )
  2396.             {
  2397.                 infected = GetEntPropEnt(survivor, Prop_Send, "m_jockeyAttacker");
  2398.             }
  2399.             else if ( IS_VALID_INFECTED(GetEntPropEnt(survivor, Prop_Send, "m_tongueOwner")) )
  2400.             {
  2401.                 infected = GetEntPropEnt(survivor, Prop_Send, "m_tongueOwner");
  2402.             }
  2403.         }
  2404.  
  2405.         HandleCarAlarmTriggered(
  2406.                 survivor,
  2407.                 infected,
  2408.                 (IS_VALID_INGAME(survivor)) ? g_iLastCarAlarmReason[survivor] : view_as<int>(CALARM_UNKNOWN)
  2409.             );
  2410.     }
  2411. }
  2412.  
  2413.  
  2414. /* throwactivate .. for more reliable rock-tracking?
  2415. public Action: L4D_OnCThrowActivate ( ability )
  2416. {
  2417.     // tank throws rock
  2418.     if ( !IsValidEntity(ability) ) { return Plugin_Continue; }
  2419.    
  2420.     // find tank player
  2421.     new tank = GetEntPropEnt(ability, Prop_Send, "m_owner");
  2422.     if ( !IS_VALID_INGAME(tank) ) { return Plugin_Continue; }
  2423.    
  2424.     ...
  2425. }
  2426. */
  2427.  
  2428. /*
  2429.     Reporting and forwards
  2430.     ----------------------
  2431. */
  2432. // boomer pop
  2433. stock HandlePop( attacker, victim, shoveCount, Float:timeAlive )
  2434. {
  2435.     // report?
  2436.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_POP )
  2437.     {
  2438.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2439.         {
  2440.             PrintToChatAll( "\x04%N\x01 popped \x05%N\x01.", attacker, victim );
  2441.         }
  2442.         else if ( IS_VALID_INGAME(attacker) )
  2443.         {
  2444.             PrintToChatAll( "\x04%N\x01 popped a boomer.", attacker );
  2445.         }
  2446.     }
  2447.    
  2448.     Call_StartForward(g_hForwardBoomerPop);
  2449.     Call_PushCell(attacker);
  2450.     Call_PushCell(victim);
  2451.     Call_PushCell(shoveCount);
  2452.     Call_PushFloat(timeAlive);
  2453.     Call_Finish();
  2454. }
  2455.  
  2456. // charger level
  2457. stock HandleLevel( attacker, victim )
  2458. {
  2459.     // report?
  2460.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_LEVEL )
  2461.     {
  2462.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2463.         {
  2464.             PrintToChatAll( "\x04%N\x01 leveled \x05%N\x01.", attacker, victim );
  2465.         }
  2466.         else if ( IS_VALID_INGAME(attacker) )
  2467.         {
  2468.             PrintToChatAll( "\x04%N\x01 leveled a charger.", attacker );
  2469.         }
  2470.         else {
  2471.             PrintToChatAll( "A charger was leveled." );
  2472.         }
  2473.     }
  2474.    
  2475.     // call forward
  2476.     Call_StartForward(g_hForwardLevel);
  2477.     Call_PushCell(attacker);
  2478.     Call_PushCell(victim);
  2479.     Call_Finish();
  2480. }
  2481. // charger level hurt
  2482. stock HandleLevelHurt( attacker, victim, damage )
  2483. {
  2484.     // report?
  2485.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_HURTLEVEL )
  2486.     {
  2487.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2488.         {
  2489.             PrintToChatAll( "\x04%N\x01 chip-leveled \x05%N\x01 (\x03%i\x01 damage).", attacker, victim, damage );
  2490.         }
  2491.         else if ( IS_VALID_INGAME(attacker) )
  2492.         {
  2493.             PrintToChatAll( "\x04%N\x01 chip-leveled a charger. (\x03%i\x01 damage)", attacker, damage );
  2494.         }
  2495.         else {
  2496.             PrintToChatAll( "A charger was chip-leveled (\x03%i\x01 damage).", damage );
  2497.         }
  2498.     }
  2499.    
  2500.     // call forward
  2501.     Call_StartForward(g_hForwardLevelHurt);
  2502.     Call_PushCell(attacker);
  2503.     Call_PushCell(victim);
  2504.     Call_PushCell(damage);
  2505.     Call_Finish();
  2506. }
  2507.  
  2508. // deadstops
  2509. stock HandleDeadstop( attacker, victim )
  2510. {
  2511.     // report?
  2512.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_DEADSTOP )
  2513.     {
  2514.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2515.         {
  2516.             PrintToChatAll( "\x04%N\x01 deadstopped \x05%N\x01.", attacker, victim );
  2517.         }
  2518.         else if ( IS_VALID_INGAME(attacker) )
  2519.         {
  2520.             PrintToChatAll( "\x04%N\x01 deadstopped a hunter.", attacker );
  2521.         }
  2522.     }
  2523.    
  2524.     Call_StartForward(g_hForwardHunterDeadstop);
  2525.     Call_PushCell(attacker);
  2526.     Call_PushCell(victim);
  2527.     Call_Finish();
  2528. }
  2529. stock HandleShove( attacker, victim, zombieClass )
  2530. {
  2531.     // report?
  2532.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_SHOVE )
  2533.     {
  2534.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2535.         {
  2536.             PrintToChatAll( "\x04%N\x01 shoved \x05%N\x01.", attacker, victim );
  2537.         }
  2538.         else if ( IS_VALID_INGAME(attacker) )
  2539.         {
  2540.             PrintToChatAll( "\x04%N\x01 shoved an SI.", attacker );
  2541.         }
  2542.     }
  2543.    
  2544.     Call_StartForward(g_hForwardSIShove);
  2545.     Call_PushCell(attacker);
  2546.     Call_PushCell(victim);
  2547.     Call_PushCell(zombieClass);
  2548.     Call_Finish();
  2549. }
  2550.  
  2551. // real skeet
  2552. stock HandleSkeet( attacker, victim, bool:bMelee = false, bool:bSniper = false, bool:bGL = false )
  2553. {
  2554.     // report?
  2555.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_SKEET )
  2556.     {
  2557.         if ( attacker == -2 )
  2558.         {
  2559.             // team skeet sets to -2
  2560.             if ( IS_VALID_INGAME(victim) && !IsFakeClient(victim) ) {
  2561.                 PrintToChatAll( "\x05%N\x01 was team-skeeted.", victim );
  2562.             } else {
  2563.                 PrintToChatAll( "\x01A hunter was team-skeeted." );
  2564.             }
  2565.         }
  2566.         else if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2567.         {
  2568.             PrintToChatAll( "\x04%N\x01 %sskeeted \x05%N\x01.",
  2569.                     attacker,
  2570.                     (bMelee) ? "melee-": ((bSniper) ? "headshot-" : ((bGL) ? "grenade-" : "") ),
  2571.                     victim
  2572.                 );
  2573.         }
  2574.         else if ( IS_VALID_INGAME(attacker) )
  2575.         {
  2576.             PrintToChatAll( "\x04%N\x01 %sskeeted a hunter.",
  2577.                     attacker,
  2578.                     (bMelee) ? "melee-": ((bSniper) ? "headshot-" : ((bGL) ? "grenade-" : "") )
  2579.                 );
  2580.         }
  2581.     }
  2582.    
  2583.     // call forward
  2584.     if ( bSniper )
  2585.     {
  2586.         Call_StartForward(g_hForwardSkeetSniper);
  2587.         Call_PushCell(attacker);
  2588.         Call_PushCell(victim);
  2589.         Call_Finish();
  2590.     }
  2591.     else if ( bGL )
  2592.     {
  2593.         Call_StartForward(g_hForwardSkeetGL);
  2594.         Call_PushCell(attacker);
  2595.         Call_PushCell(victim);
  2596.         Call_Finish();
  2597.     }
  2598.     else if ( bMelee )
  2599.     {
  2600.         Call_StartForward(g_hForwardSkeetMelee);
  2601.         Call_PushCell(attacker);
  2602.         Call_PushCell(victim);
  2603.         Call_Finish();
  2604.     }
  2605.     else
  2606.     {
  2607.         Call_StartForward(g_hForwardSkeet);
  2608.         Call_PushCell(attacker);
  2609.         Call_PushCell(victim);
  2610.         Call_Finish();
  2611.     }
  2612. }
  2613.  
  2614. // hurt skeet / non-skeet
  2615. //  NOTE: bSniper not set yet, do this
  2616. stock HandleNonSkeet( attacker, victim, damage, bool:bOverKill = false, bool:bMelee = false, bool:bSniper = false )
  2617. {
  2618.     // report?
  2619.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_HURTSKEET )
  2620.     {
  2621.         if ( IS_VALID_INGAME(victim) )
  2622.         {
  2623.             PrintToChatAll( "\x05%N\x01 was \x04not\x01 skeeted (\x03%i\x01 damage).%s", victim, damage, (bOverKill) ? "(Would've skeeted if hunter were unchipped!)" : "" );
  2624.         }
  2625.         else
  2626.         {
  2627.             PrintToChatAll( "\x01Hunter was \x04not\x01 skeeted (\x03%i\x01 damage).%s", damage, (bOverKill) ? "(Would've skeeted if hunter were unchipped!)" : "" );
  2628.         }
  2629.     }
  2630.    
  2631.     // call forward
  2632.     if ( bSniper )
  2633.     {
  2634.         Call_StartForward(g_hForwardSkeetSniperHurt);
  2635.         Call_PushCell(attacker);
  2636.         Call_PushCell(victim);
  2637.         Call_PushCell(damage);
  2638.         Call_PushCell(bOverKill);
  2639.         Call_Finish();
  2640.     }
  2641.     else if ( bMelee )
  2642.     {
  2643.         Call_StartForward(g_hForwardSkeetMeleeHurt);
  2644.         Call_PushCell(attacker);
  2645.         Call_PushCell(victim);
  2646.         Call_PushCell(damage);
  2647.         Call_PushCell(bOverKill);
  2648.         Call_Finish();
  2649.     }
  2650.     else
  2651.     {
  2652.         Call_StartForward(g_hForwardSkeetHurt);
  2653.         Call_PushCell(attacker);
  2654.         Call_PushCell(victim);
  2655.         Call_PushCell(damage);
  2656.         Call_PushCell(bOverKill);
  2657.         Call_Finish();
  2658.     }
  2659. }
  2660.  
  2661.  
  2662. // crown
  2663. HandleCrown( attacker, damage )
  2664. {
  2665.     // report?
  2666.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_CROWN )
  2667.     {
  2668.         if ( IS_VALID_INGAME(attacker) )
  2669.         {
  2670.             PrintToChatAll( "\x04%N\x01 crowned a witch (\x03%i\x01 damage).", attacker, damage );
  2671.         }
  2672.         else {
  2673.             PrintToChatAll( "A witch was crowned." );
  2674.         }
  2675.     }
  2676.    
  2677.     // call forward
  2678.     Call_StartForward(g_hForwardCrown);
  2679.     Call_PushCell(attacker);
  2680.     Call_PushCell(damage);
  2681.     Call_Finish();
  2682. }
  2683. // drawcrown
  2684. HandleDrawCrown( attacker, damage, chipdamage )
  2685. {
  2686.     // report?
  2687.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_DRAWCROWN )
  2688.     {
  2689.         if ( IS_VALID_INGAME(attacker) )
  2690.         {
  2691.             PrintToChatAll( "\x04%N\x01 draw-crowned a witch (\x03%i\x01 damage, \x05%i\x01 chip).", attacker, damage, chipdamage );
  2692.         }
  2693.         else {
  2694.             PrintToChatAll( "A witch was draw-crowned (\x03%i\x01 damage, \x05%i\x01 chip).", damage, chipdamage );
  2695.         }
  2696.     }
  2697.    
  2698.     // call forward
  2699.     Call_StartForward(g_hForwardDrawCrown);
  2700.     Call_PushCell(attacker);
  2701.     Call_PushCell(damage);
  2702.     Call_PushCell(chipdamage);
  2703.     Call_Finish();
  2704. }
  2705.  
  2706. // smoker clears
  2707. HandleTongueCut( attacker, victim )
  2708. {
  2709.     // report?
  2710.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_TONGUECUT )
  2711.     {
  2712.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2713.         {
  2714.             PrintToChatAll( "\x04%N\x01 cut \x05%N\x01's tongue.", attacker, victim );
  2715.         }
  2716.         else if ( IS_VALID_INGAME(attacker) )
  2717.         {
  2718.             PrintToChatAll( "\x04%N\x01 cut smoker tongue.", attacker );
  2719.         }
  2720.     }
  2721.    
  2722.     // call forward
  2723.     Call_StartForward(g_hForwardTongueCut);
  2724.     Call_PushCell(attacker);
  2725.     Call_PushCell(victim);
  2726.     Call_Finish();
  2727. }
  2728.  
  2729. HandleSmokerSelfClear( attacker, victim, bool:withShove = false )
  2730. {
  2731.     // report?
  2732.     if (    GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_SELFCLEAR &&
  2733.             (!withShove || GetConVarInt(g_hCvarReport) & REP_SELFCLEARSHOVE )
  2734.     ) {
  2735.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2736.         {
  2737.             PrintToChatAll( "\x04%N\x01 self-cleared from \x05%N\x01's tongue%s.", attacker, victim, (withShove) ? " by shoving" : "" );
  2738.         }
  2739.         else if ( IS_VALID_INGAME(attacker) )
  2740.         {
  2741.             PrintToChatAll( "\x04%N\x01 self-cleared from a smoker tongue%s.", attacker, (withShove) ? " by shoving" : "" );
  2742.         }
  2743.     }
  2744.    
  2745.     // call forward
  2746.     Call_StartForward(g_hForwardSmokerSelfClear);
  2747.     Call_PushCell(attacker);
  2748.     Call_PushCell(victim);
  2749.     Call_PushCell(withShove);
  2750.     Call_Finish();
  2751. }
  2752.  
  2753. // rocks
  2754. HandleRockEaten( attacker, victim )
  2755. {
  2756.     Call_StartForward(g_hForwardRockEaten);
  2757.     Call_PushCell(attacker);
  2758.     Call_PushCell(victim);
  2759.     Call_Finish();
  2760. }
  2761. HandleRockSkeeted( attacker, victim )
  2762. {
  2763.     // report?
  2764.     if ( GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_ROCKSKEET )
  2765.     {
  2766.         /*
  2767.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2768.         {
  2769.             PrintToChatAll( "\x04%N\x01 skeeted \x05%N\x01's rock.", attacker, victim );
  2770.         }
  2771.         else if ( IS_VALID_INGAME(attacker) )
  2772.         {
  2773.         }
  2774.         */
  2775.         PrintToChatAll( "\x04%N\x01 skeeted a tank rock.", attacker );
  2776.     }
  2777.    
  2778.     Call_StartForward(g_hForwardRockSkeeted);
  2779.     Call_PushCell(attacker);
  2780.     Call_PushCell(victim);
  2781.     Call_Finish();
  2782. }
  2783.  
  2784. // highpounces
  2785. stock HandleHunterDP( attacker, victim, actualDamage, Float:calculatedDamage, Float:height, bool:playerIncapped = false )
  2786. {
  2787.     // report?
  2788.     if (    GetConVarBool(g_hCvarReport)
  2789.         &&  GetConVarInt(g_hCvarReportFlags) & REP_HUNTERDP
  2790.         &&  height >= GetConVarFloat(g_hCvarHunterDPThresh)
  2791.         &&  !playerIncapped
  2792.     ) {
  2793.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(attacker) )
  2794.         {
  2795.             PrintToChatAll( "\x04%N\x01 high-pounced \x05%N\x01 (\x03%i\x01 damage, height: \x05%i\x01).", attacker,  victim, RoundFloat(calculatedDamage), RoundFloat(height) );
  2796.         }
  2797.         else if ( IS_VALID_INGAME(victim) )
  2798.         {
  2799.             PrintToChatAll( "A hunter high-pounced \x05%N\x01 (\x03%i\x01 damage, height: \x05%i\x01).", victim, RoundFloat(calculatedDamage), RoundFloat(height) );
  2800.         }
  2801.     }
  2802.    
  2803.     Call_StartForward(g_hForwardHunterDP);
  2804.     Call_PushCell(attacker);
  2805.     Call_PushCell(victim);
  2806.     Call_PushCell(actualDamage);
  2807.     Call_PushFloat(calculatedDamage);
  2808.     Call_PushFloat(height);
  2809.     Call_PushCell( (height >= GetConVarFloat(g_hCvarHunterDPThresh)) ? 1 : 0 );
  2810.     Call_PushCell( (playerIncapped) ? 1 : 0 );
  2811.     Call_Finish();
  2812. }
  2813. stock HandleJockeyDP( attacker, victim, Float:height )
  2814. {
  2815.     // report?
  2816.     if (    GetConVarBool(g_hCvarReport)
  2817.         &&  GetConVarInt(g_hCvarReportFlags) & REP_JOCKEYDP
  2818.         &&  height >= GetConVarFloat(g_hCvarJockeyDPThresh)
  2819.     ) {
  2820.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(attacker) )
  2821.         {
  2822.             PrintToChatAll( "\x04%N\x01 jockey high-pounced \x05%N\x01 (height: \x05%i\x01).", attacker,  victim, RoundFloat(height) );
  2823.         }
  2824.         else if ( IS_VALID_INGAME(victim) )
  2825.         {
  2826.             PrintToChatAll( "A jockey high-pounced \x05%N\x01 (height: \x05%i\x01).", victim, RoundFloat(height) );
  2827.         }
  2828.     }
  2829.    
  2830.     Call_StartForward(g_hForwardJockeyDP);
  2831.     Call_PushCell(attacker);
  2832.     Call_PushCell(victim);
  2833.     Call_PushFloat(height);
  2834.     Call_PushCell( (height >= GetConVarFloat(g_hCvarJockeyDPThresh)) ? 1 : 0 );
  2835.     Call_Finish();
  2836. }
  2837.  
  2838. // deathcharges
  2839. stock HandleDeathCharge( attacker, victim, Float:height, Float:distance, bool:bCarried = true )
  2840. {
  2841.     // report?
  2842.     if (    GetConVarBool(g_hCvarReport) &&
  2843.             GetConVarInt(g_hCvarReportFlags) & REP_DEATHCHARGE &&
  2844.             height >= GetConVarFloat(g_hCvarDeathChargeHeight)
  2845.     ) {
  2846.         if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(attacker) )
  2847.         {
  2848.             PrintToChatAll( "\x04%N\x01 death-charged \x05%N\x01 %s(height: \x05%i\x01).",
  2849.                     attacker,
  2850.                     victim,
  2851.                     (bCarried) ? "" : "by bowling ",
  2852.                     RoundFloat(height)
  2853.                 );
  2854.         }
  2855.         else if ( IS_VALID_INGAME(victim) )
  2856.         {
  2857.             PrintToChatAll( "A charger death-charged \x05%N\x01 %s(height: \x05%i\x01).",
  2858.                     victim,
  2859.                     (bCarried) ? "" : "by bowling ",
  2860.                     RoundFloat(height)
  2861.                 );
  2862.         }
  2863.     }
  2864.    
  2865.     Call_StartForward(g_hForwardDeathCharge);
  2866.     Call_PushCell(attacker);
  2867.     Call_PushCell(victim);
  2868.     Call_PushFloat(height);
  2869.     Call_PushFloat(distance);
  2870.     Call_PushCell( (bCarried) ? 1 : 0 );
  2871.     Call_Finish();
  2872. }
  2873.  
  2874. // SI clears    (cleartimeA = pummel/pounce/ride/choke, cleartimeB = tongue drag, charger carry)
  2875. stock HandleClear( attacker, victim, pinVictim, zombieClass, Float:clearTimeA, Float:clearTimeB, bool:bWithShove = false )
  2876. {
  2877.     // sanity check:
  2878.     if ( clearTimeA < 0 && clearTimeA != -1.0 ) { clearTimeA = 0.0; }
  2879.     if ( clearTimeB < 0 && clearTimeB != -1.0 ) { clearTimeB = 0.0; }
  2880.    
  2881.     PrintDebug(0, "Clear: %i freed %i from %i: time: %.2f / %.2f -- class: %s (with shove? %i)", attacker, pinVictim, victim, clearTimeA, clearTimeB, g_csSIClassName[zombieClass], bWithShove );
  2882.    
  2883.     if ( attacker != pinVictim && GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_INSTACLEAR )
  2884.     {
  2885.         new Float: fMinTime = GetConVarFloat(g_hCvarInstaTime);
  2886.         new Float: fClearTime = clearTimeA;
  2887.         if ( zombieClass == ZC_CHARGER || zombieClass == ZC_SMOKER ) { fClearTime = clearTimeB; }
  2888.        
  2889.        
  2890.         if ( fClearTime != -1.0 && fClearTime <= fMinTime )
  2891.         {
  2892.             if ( IS_VALID_INGAME(attacker) && IS_VALID_INGAME(victim) && !IsFakeClient(victim) )
  2893.             {
  2894.                 if ( IS_VALID_INGAME(pinVictim) )
  2895.                 {
  2896.                     PrintToChatAll( "\x04%N\x01 insta-cleared \x05%N\x01 from \x04%N\x01 (%s) (%.2f seconds).",
  2897.                             attacker, pinVictim, victim,
  2898.                             g_csSIClassName[zombieClass],
  2899.                             fClearTime
  2900.                         );
  2901.                 } else {
  2902.                     PrintToChatAll( "\x04%N\x01 insta-cleared a teammate from \x04%N\x01 (%s) (%.2f seconds).",
  2903.                             attacker, victim,
  2904.                             g_csSIClassName[zombieClass],
  2905.                             fClearTime
  2906.                         );
  2907.                 }
  2908.             }
  2909.             else if ( IS_VALID_INGAME(attacker) )
  2910.             {
  2911.                 if ( IS_VALID_INGAME(pinVictim) )
  2912.                 {
  2913.                     PrintToChatAll( "\x04%N\x01 insta-cleared \x05%N\x01 from a %s (%.2f seconds).",
  2914.                             attacker, pinVictim,
  2915.                             g_csSIClassName[zombieClass],
  2916.                             fClearTime
  2917.                         );
  2918.                 } else {
  2919.                     PrintToChatAll( "\x04%N\x01 insta-cleared a teammate from a %s (%.2f seconds).",
  2920.                             attacker,
  2921.                             g_csSIClassName[zombieClass],
  2922.                             fClearTime
  2923.                         );
  2924.                 }
  2925.             }
  2926.         }
  2927.     }
  2928.    
  2929.     Call_StartForward(g_hForwardClear);
  2930.     Call_PushCell(attacker);
  2931.     Call_PushCell(victim);
  2932.     Call_PushCell(pinVictim);
  2933.     Call_PushCell(zombieClass);
  2934.     Call_PushFloat(clearTimeA);
  2935.     Call_PushFloat(clearTimeB);
  2936.     Call_PushCell( (bWithShove) ? 1 : 0 );
  2937.     Call_Finish();
  2938. }
  2939.  
  2940. // booms
  2941. stock HandleVomitLanded( attacker, boomCount )
  2942. {
  2943.     Call_StartForward(g_hForwardVomitLanded);
  2944.     Call_PushCell(attacker);
  2945.     Call_PushCell(boomCount);
  2946.     Call_Finish();
  2947. }
  2948.  
  2949. // bhaps
  2950. stock HandleBHopStreak( survivor, streak, Float: maxVelocity )
  2951. {
  2952.     if (    GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_BHOPSTREAK &&
  2953.             IS_VALID_INGAME(survivor) && !IsFakeClient(survivor) &&
  2954.             streak >= GetConVarInt(g_hCvarBHopMinStreak)
  2955.     ) {
  2956.         PrintToChatAll( "\x04%N\x01 got \x05%i\x01 bunnyhop%s in a row (top speed: \x05%.1f\x01).",
  2957.                 survivor,
  2958.                 streak,
  2959.                 ( streak > 1 ) ? "s" : "",
  2960.                 maxVelocity
  2961.             );
  2962.     }
  2963.    
  2964.     Call_StartForward(g_hForwardBHopStreak);
  2965.     Call_PushCell(survivor);
  2966.     Call_PushCell(streak);
  2967.     Call_PushFloat(maxVelocity);
  2968.     Call_Finish();
  2969. }
  2970. // car alarms
  2971. stock HandleCarAlarmTriggered( survivor, infected, reason )
  2972. {
  2973.     if (    GetConVarBool(g_hCvarReport) && GetConVarInt(g_hCvarReportFlags) & REP_CARALARM &&
  2974.             IS_VALID_INGAME(survivor) && !IsFakeClient(survivor)
  2975.     ) {
  2976.         if ( reason == view_as<int>(CALARM_HIT) ) {
  2977.             PrintToChatAll( "\x05%N\x01 triggered an alarm with a hit.", survivor );
  2978.         }
  2979.         else if ( reason == view_as<int>(CALARM_TOUCHED) )
  2980.         {
  2981.             // if a survivor touches an alarmed car, it might be due to a special infected...
  2982.             if ( IS_VALID_INFECTED(infected) )
  2983.             {
  2984.                 if ( !IsFakeClient(infected) )
  2985.                 {
  2986.                     PrintToChatAll( "\x04%N\x01 made \x05%N\x01 trigger an alarm.", infected, survivor );
  2987.                 }
  2988.                 else {
  2989.                     switch ( GetEntProp(infected, Prop_Send, "m_zombieClass") )
  2990.                     {
  2991.                         case ZC_SMOKER: { PrintToChatAll( "\x01A hunter made \x05%N\x01 trigger an alarm.", survivor ); }
  2992.                         case ZC_JOCKEY: { PrintToChatAll( "\x01A jockey made \x05%N\x01 trigger an alarm.", survivor ); }
  2993.                         case ZC_CHARGER: { PrintToChatAll( "\x01A charger made \x05%N\x01 trigger an alarm.", survivor ); }
  2994.                         default: { PrintToChatAll( "\x01A bot infected made \x05%N\x01 trigger an alarm.", survivor ); }
  2995.                     }
  2996.                 }
  2997.             }
  2998.             else
  2999.             {
  3000.                 PrintToChatAll( "\x05%N\x01 touched an alarmed car.", survivor );
  3001.             }
  3002.         }
  3003.         else if ( reason == view_as<int>(CALARM_EXPLOSION) ) {
  3004.             PrintToChatAll( "\x05%N\x01 triggered an alarm with an explosion.", survivor );
  3005.         }
  3006.         else if ( reason == view_as<int>(CALARM_BOOMER) )
  3007.         {
  3008.             if ( IS_VALID_INFECTED(infected) && !IsFakeClient(infected) )
  3009.             {
  3010.                 PrintToChatAll( "\x05%N\x01 triggered an alarm by killing a boomer \x04%N\x01.", survivor, infected );
  3011.             }
  3012.             else
  3013.             {
  3014.                 PrintToChatAll( "\x05%N\x01 triggered an alarm by shooting a boomer.", survivor );
  3015.             }
  3016.         }
  3017.         else {
  3018.             PrintToChatAll( "\x05%N\x01 triggered an alarm.", survivor );
  3019.         }
  3020.     }
  3021.    
  3022.     Call_StartForward(g_hForwardAlarmTriggered);
  3023.     Call_PushCell(survivor);
  3024.     Call_PushCell(infected);
  3025.     Call_PushCell(reason);
  3026.     Call_Finish();
  3027. }
  3028.  
  3029.  
  3030. // support
  3031. // -------
  3032.  
  3033. stock GetSurvivorPermanentHealth(client)
  3034. {
  3035.     return GetEntProp(client, Prop_Send, "m_iHealth");
  3036. }
  3037.  
  3038. stock GetSurvivorTempHealth(client)
  3039. {
  3040.     new temphp = RoundToCeil(
  3041.             GetEntPropFloat(client, Prop_Send, "m_healthBuffer")
  3042.             - ( (GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime") )
  3043.             * GetConVarFloat( FindConVar("pain_pills_decay_rate")))
  3044.         ) - 1;
  3045.     return (temphp > 0 ? temphp : 0);
  3046. }
  3047.  
  3048. stock Float: GetSurvivorDistance(client)
  3049. {
  3050.     return L4D2Direct_GetFlowDistance(client);
  3051. }
  3052. stock ShiftTankThrower()
  3053. {
  3054.     new tank = -1;
  3055.    
  3056.     if ( !g_iRocksBeingThrownCount ) { return -1; }
  3057.    
  3058.     tank = g_iRocksBeingThrown[0];
  3059.    
  3060.     // shift the tank array downwards, if there are more than 1 throwers
  3061.     if ( g_iRocksBeingThrownCount > 1 )
  3062.     {
  3063.         for ( new x = 1; x <= g_iRocksBeingThrownCount; x++ )
  3064.         {
  3065.             g_iRocksBeingThrown[x-1] = g_iRocksBeingThrown[x];
  3066.         }
  3067.     }
  3068.    
  3069.     g_iRocksBeingThrownCount--;
  3070.    
  3071.     return tank;
  3072. }
  3073. /*  Height check..
  3074.     not required now
  3075.     maybe for some other 'skill'?
  3076. static Float: GetHeightAboveGround( Float:pos[3] )
  3077. {
  3078.     // execute Trace straight down
  3079.     new Handle:trace = TR_TraceRayFilterEx( pos, ANGLE_STRAIGHT_DOWN, MASK_SHOT, RayType_Infinite, ChargeTraceFilter );
  3080.    
  3081.     if (!TR_DidHit(trace))
  3082.     {
  3083.         LogError("Tracer Bug: Trace did not hit anything...");
  3084.     }
  3085.    
  3086.     decl Float:vEnd[3];
  3087.     TR_GetEndPosition(vEnd, trace); // retrieve our trace endpoint
  3088.     CloseHandle(trace);
  3089.    
  3090.     return GetVectorDistance(pos, vEnd, false);
  3091. }
  3092.  
  3093. public bool: ChargeTraceFilter (entity, contentsMask)
  3094. {
  3095.     if ( !entity || !IsValidEntity(entity) ) // dont let WORLD, or invalid entities be hit
  3096.     {
  3097.         return false;
  3098.     }
  3099.     return true;
  3100. }
  3101. */
  3102.  
  3103. stock PrintDebug(debuglevel, const String:Message[], any:... )
  3104. {
  3105.     decl String:DebugBuff[256];
  3106.     VFormat(DebugBuff, sizeof(DebugBuff), Message, 3);
  3107.     LogMessage(DebugBuff);
  3108. }
  3109. stock bool: IsWitch(entity)
  3110. {
  3111.     if ( !IsValidEntity(entity) ) { return false; }
  3112.    
  3113.     decl String: classname[24];
  3114.     new strOEC: classnameOEC;
  3115.     GetEdictClassname(entity, classname, sizeof(classname));
  3116.     if ( !GetTrieValue(g_hTrieEntityCreated, classname, classnameOEC) || classnameOEC != OEC_WITCH ) { return false; }
  3117.    
  3118.     return true;
  3119. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement