/* * ============================================================================ * * Rotoblin * * File: rotoblin.survivorexploitfixes.sp * Type: Module * Description: Fixes some exploits for the suvivor ledge grabs * * Copyright (C) 2011 pwn <ajparise@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * ============================================================================ */// --------------------// Private// --------------------enum exploitState{ PLAYER_PULLED, // 1 or 0 indicating if the player is pulled PLAYER_PULLED_BY, // User ID of smoker who caused the pull PLAYER_HANGING, // 1 or 0 indicating if the player is hanging PLAYER_WAS_INCAPPED, // 1 or 0 indicating if the player was incapped by the pulled ledge hang HEALTH_SOLID, // Solid health of the player prior to the ledge (only if they were pulled) HEALTH_HANGING_OR_RELEASE, // Incap health of the player when they first went off the ledge OR after tongue has been release HEALTH_REVIVE_START, // Incap health of the player on revival HEALTH_SOLID_REVIVE, // Calculated solid health the player should have when revived Float:HEALTH_TEMP_REVIVE, // Calculated temp health the player should have when revive HANDLE_REVIVE_HEALTH, // 1 or 0 indicating if we should handle solid health on revive HANDLE_REVIVE_TEMP_HEALTH, // 1 or 0 indicating if we should handle temporary health on revive Float:HEALTH_BUFFER, // Health buffer value when the player grabbed the ledge Float:HEALTH_BUFFER_TIME, // Health buffer time value when the player grabbed the ledge Handle:TIMER_HEALTH_TRACKER, // Handle of pulled and hanging timer (calculates health) Handle:TIMER_HEALTH_UPDATER // Handle of health update timer (restores health and bonus)}static playerState[MAXPLAYERS+1][exploitState];static Float:SMOKER_CLAW_DAMAGE = 3.0;static g_iDebugChannel = 0;static const String: DEBUG_CHANNEL_NAME[] = "SurvivorExploitFixes";// **********************************************// Forwards// **********************************************/** * Plugin is starting. * * @noreturn */public _SurvExploitFixes_OnPluginStart(){ HookPublicEvent(EVENT_ONPLUGINENABLE, _SEF_OnPluginEnable); HookPublicEvent(EVENT_ONPLUGINDISABLE, _SEF_OnPluginDisable); g_iDebugChannel = DebugAddChannel(DEBUG_CHANNEL_NAME); DebugPrintToAllEx("Module is now setup");}/** * Plugin is now enabled. * * @noreturn */public _SEF_OnPluginEnable(){ // Smoker events HookEvent("choke_start", _SEF_ChokeStart_Event); HookEvent("tongue_grab", _SEF_TongueGrab_Event); HookEvent("tongue_release", _SEF_TongueRelease_Event); // Round events HookEvent("round_start", _SEF_RoundStart_Event); HookEvent("round_end", _SEF_RoundEnd_Event); // Player events HookEvent("heal_success", _SEF_HealSuccess_Event); HookEvent("player_death", _SEF_PlayerDeath_Event); HookEvent("player_ledge_grab", _SEF_PlayerLedgeGrab_Event); HookEvent("revive_begin", _SEF_ReviveBegin_Event); HookEvent("revive_success", _SEF_ReviveSuccess_Event); HookEvent("player_bot_replace", _SEF_BotReplacePlayer_Event); HookEvent("bot_player_replace", _SEF_PlayerReplaceBot_Event); DebugPrintToAllEx( "Module is now loaded"); }/** * Plugin is now disabled. * * @noreturn */public _SEF_OnPluginDisable(){ // Smoker events UnhookEvent("choke_start", _SEF_ChokeStart_Event); UnhookEvent("tongue_grab", _SEF_TongueGrab_Event); UnhookEvent("tongue_release", _SEF_TongueRelease_Event); // Round events UnhookEvent("round_start", _SEF_RoundStart_Event); UnhookEvent("round_end", _SEF_RoundEnd_Event); // Player events UnhookEvent("heal_success", _SEF_HealSuccess_Event); UnhookEvent("player_death", _SEF_PlayerDeath_Event); UnhookEvent("player_ledge_grab", _SEF_PlayerLedgeGrab_Event); UnhookEvent("revive_begin", _SEF_ReviveBegin_Event); UnhookEvent("revive_success", _SEF_ReviveSuccess_Event); UnhookEvent("player_bot_replace", _SEF_BotReplacePlayer_Event); UnhookEvent("bot_player_replace", _SEF_PlayerReplaceBot_Event); DebugPrintToAllEx( "Module is now unloaded");}// Copy player states between bot - player and player - bot switches public _SEF_BotReplacePlayer_Event(Handle:event, const String:name[], bool:dontBroadcast){ new player = GetClientOfUserId(GetEventInt(event, "player")); new bot = GetClientOfUserId(GetEventInt(event, "bot")); CopyPlayerState(player, bot); }public _SEF_PlayerReplaceBot_Event(Handle:event, const String:name[], bool:dontBroadcast){ new player = GetClientOfUserId(GetEventInt(event, "player")); new bot = GetClientOfUserId(GetEventInt(event, "bot")); CopyPlayerState(bot, player); }public _SEF_TongueGrab_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "victim"); new client = GetClientOfUserId(userid); new attacker = GetEventInt(event, "attacker"); // Start the monitor StartPulledHealthMonitor(client, attacker);}public _SEF_TongueRelease_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "victim"); new client = GetClientOfUserId(userid); // Mark the user as no longer being pulled playerState[client][PLAYER_PULLED] = 0; if (playerState[client][PLAYER_HANGING] == 1) { // Capture this to calculate additional damage done from hanging CaptureHangingHealth(client); } DebugPrintToAllEx( "Client %i: \"%N\" just released from pull", client, client);}public _SEF_ChokeStart_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "victim"); new client = GetClientOfUserId(userid); // Mark the user as no longer being pulled playerState[client][PLAYER_PULLED] = 0; DebugPrintToAllEx( "Client %i: \"%N\" is being choked", client, client);}public _SEF_PlayerLedgeGrab_Event(Handle:event, const String:name[], bool:dontBroadcast){ // Determine which survivor is on ledge new userid = GetEventInt(event, "userid"); new client = GetClientOfUserId(userid); // Mark the user as hanging playerState[client][PLAYER_HANGING] = 1; // Capture the current hanging health and temporary health CaptureHangingHealth(client); CaptureTemporaryHealth(client); }public _SEF_HealSuccess_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "subject"); new client = GetClientOfUserId(userid); ResetPlayerState(client);}public _SEF_ReviveBegin_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "subject"); new client = GetClientOfUserId(userid); // Capture the current incap health playerState[client][HEALTH_REVIVE_START] = GetSurvivorPermanentHealth(client); DebugPrintToAllEx( "Client %i: \"%N\" revive started at %i", client, client, playerState[client][HEALTH_REVIVE_START]);}public _SEF_ReviveSuccess_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "subject"); new client = GetClientOfUserId(userid); StartSetHealthTimer(client); DebugPrintToAllEx( "Client %i: \"%N\" revive success", client, client);}public _SEF_RoundStart_Event(Handle:event, const String:name[], bool:dontBroadcast){ DebugPrintToAllEx( "Survivor Exploit Fix Loaded");}public _SEF_PlayerDeath_Event(Handle:event, const String:name[], bool:dontBroadcast){ new userid = GetEventInt(event, "userid"); new client = GetClientOfUserId(userid); if(client && GetClientTeam(client) != TEAM_INFECTED) ResetPlayerState(client);}public _SEF_RoundEnd_Event(Handle:event, const String:name[], bool:dontBroadcast){ ResetAllPlayerStates();}/** * Starts the set health timer * * @param timer Handle to the timer object * @param client Client object * @noreturn */StartSetHealthTimer(client){ if (playerState[client][PLAYER_HANGING] == 1) { DebugPrintToAllEx("Starting revive timer for %i \"%N\"", client, client); playerState[client][Handle:TIMER_HEALTH_UPDATER] = CreateTimer(0.01, SetHealthTimer, client); }}/** * Called after revive success event if a player was hanging * * @param timer Handle to the timer object * @param client Client object * @noreturn */public Action:SetHealthTimer(Handle:timer, any:client){ AdjustPermanentReviveHealth(client); AdjustTemporaryReviveHealth(client); ResetPlayerState(client);}/** * Adjusts (if necessary) and Sets permanent health * Adjusts permanent health according to damage taken between tongue release and revive * Scenario 1 - Player is smoked off the ledge * 1a) Player was incapacitated * - Set health to pre-calculated health + 1 * - Issue hurtme command for 1 damage (this is necessary for incaps - otherwise the player will glitch until player is hurt) * 1b) Player was not incapacitated * - Adjust pre-calculated health to account for normal ledge damage * - Set health to pre-calculated health + 1 * - Issue hurtme command for 1 damage (this is necessary for incaps - otherwise the player will glitch until player is hurt) * @param client Client to set temporary health for * @noreturn */AdjustPermanentReviveHealth(client){ // Permanent health should be modified due to being pulled off a ledge if (playerState[client][HANDLE_REVIVE_HEALTH] == 1) { // Only modify revive health if player was not incapped if (playerState[client][PLAYER_WAS_INCAPPED] != 1) { // Modify calculated health based on amount of damage taken between tongue release and revive new ledgeReviveHealth = RoundToFloor(CalculateLedgeReviveHealth(client, float(playerState[client][HEALTH_SOLID_REVIVE]))); playerState[client][HEALTH_SOLID_REVIVE] = ledgeReviveHealth; } SetEntityHealth(client, playerState[client][HEALTH_SOLID_REVIVE] + 1); // Note we are adding 1 to enable HurtMe to fire without effecting health HurtMe(client); // Must send this - otherwise health may be restored improperly if perm health drops to 0 with temp health DebugPrintToAllEx( "Set survivor health: %i", playerState[client][HEALTH_SOLID_REVIVE]); } else { // Could handle temp health ledge hang issue // Players are not damaged until incap health drops below 300 // Could modify health accordingly if HB is in effect }}/** * Adjusts (if necessary) and Sets temporary health * Adjusts temporary health according to damage taken between tongue release and revive * Scenario 1 - Player fell off ledge with temporary health * 1a) Permanent health is <= 1 * - Set temp health with damage based on normal ledge damage rates with normal decay * 1b) Permanent health is > 1 * - Set original health bonus with normal decay * Scenario 2 - Player was incapacitated while on ledge * 2a) Player Didn't die from incap * - Add 1 to incap count * - Set temp health to 31 * - Issue hurt me command for 1 damage (this is necessary - otherwise the player will glitch until player is hurt) * 2b) Player died from incap (NOT HANDLED HERE) * - Is handled in PulledHealthMonitor * @param client Client to set temporary health for * @noreturn */AdjustTemporaryReviveHealth(client){ // Temporary health should be modified due to ledge hange (could be with or without a smoker) if (playerState[client][HANDLE_REVIVE_TEMP_HEALTH] == 1) { // Modifies temporary health based on time between tongue release and revive if (playerState[client][PLAYER_WAS_INCAPPED] == 0) { DebugPrintToAllEx( "Doing temp health without incap - %i", GetClientHealth(client)); // Additional damage to temp health (only if solid health <= 1) if (GetClientHealth(client) <= 1) { playerState[client][Float:HEALTH_TEMP_REVIVE] = CalculateLedgeReviveHealth(client, playerState[client][Float:HEALTH_TEMP_REVIVE]); SetSurvivorTempHealth(client, playerState[client][Float:HEALTH_TEMP_REVIVE], playerState[client][Float:HEALTH_BUFFER_TIME]); DebugPrintToAllEx( "Updated HB: %f at %f", playerState[client][Float:HEALTH_TEMP_REVIVE], playerState[client][Float:HEALTH_BUFFER_TIME]); } else // Don't damage temp health further, just restore for decay rate { SetSurvivorTempHealth(client, playerState[client][Float:HEALTH_BUFFER], playerState[client][Float:HEALTH_BUFFER_TIME]); DebugPrintToAllEx( "Reset HB: %f at %f", playerState[client][Float:HEALTH_BUFFER], playerState[client][Float:HEALTH_BUFFER_TIME]); } } else // Tick incap count and set revive health { new currentIncapCount = GetSurvivorIncapCount(client); currentIncapCount++; SetEntProp(client, Prop_Send, "m_currentReviveCount", currentIncapCount); SetSurvivorTempHealth(client, playerState[client][Float:HEALTH_TEMP_REVIVE], 0.0); HurtMe(client); DebugPrintToAllEx( "Incap count: %i | Incapped Health Bonus: %f", currentIncapCount, playerState[client][Float:HEALTH_TEMP_REVIVE]); } } }/** * Calculates damage done to health from hanging * Used to adjust revive health calculated from a ledge pull * * @param client Client to use for calculations * @return Modified health value based on hanging health and starting revive health */Float:CalculateLedgeReviveHealth(client, Float:health){ // Modify calculated health based on amount of damage taken between tongue release and revive new initialLedgeHealth = playerState[client][HEALTH_HANGING_OR_RELEASE]; // Health on tongue release new reviveLedgeHealth = playerState[client][HEALTH_REVIVE_START]; // Health on revive start // Calculate percentage of health that should remain new Float:dmgPercent = float(reviveLedgeHealth) / float(initialLedgeHealth); new Float:preciseHealth = health * dmgPercent; DebugPrintToAllEx( "ILH: %i, RLH: %i, CH: %f DP: %f, NH: %f", initialLedgeHealth, reviveLedgeHealth, health, dmgPercent, preciseHealth); return preciseHealth;}/** * Called on tongue drag event to monitor player's health (before ledge fall) * * @param client Client whose health should be monitored * @noreturn */StartPulledHealthMonitor(client, attackerId){ ResetPlayerState(client); playerState[client][PLAYER_PULLED_BY] = attackerId; playerState[client][PLAYER_PULLED] = 1; CapturePermanentHealth(client); playerState[client][Handle:TIMER_HEALTH_TRACKER] = CreateTimer(0.05, PulledHealthMonitor, client, TIMER_FLAG_NO_MAPCHANGE|TIMER_REPEAT); DebugPrintToAllEx( "Health monitor started for \"%N\"", client);}/** * Monitors health while a survivor is being pulled * Stops when a survivor is incapacitated, dies or is choked * * @param timer Handle to the timer object. * @param client Client index of pulled player * @noreturn */public Action:PulledHealthMonitor(Handle:timer, any:client){ new bool:isPulled = playerState[client][PLAYER_PULLED] == 1; new bool:isIncapped = playerState[client][PLAYER_WAS_INCAPPED] == 1; new bool:isHanging = playerState[client][PLAYER_HANGING] == 1; if (!isIncapped && isPulled) // Stop on incap or when pull is over { if (!isHanging) // Capture permanent health prior to ledge { CapturePermanentHealth(client); DebugPrintToAllEx( "Logging player health: %i", playerState[client][HEALTH_SOLID]); } else // Calculate health while being pulled on a ledge { // Handle health on revive playerState[client][HANDLE_REVIVE_HEALTH] = 1; new Float:currentHealth = CalculateSmokedHangHealth(client); if (currentHealth < 1.0) // Player should be incapacitated or dead { IncapacitateSurvivor(client); playerState[client][Handle:TIMER_HEALTH_TRACKER] = INVALID_HANDLE; return Plugin_Stop; } } return Plugin_Continue; } else { playerState[client][Handle:TIMER_HEALTH_TRACKER] = INVALID_HANDLE; return Plugin_Stop; }}/** * Calculates remaining health of survivor being pulled while hanging on a ledge * Used by the PulledHealthMonitor * * @param client Client index of pulled player * @return Total health remaining */Float:CalculateSmokedHangHealth(client){ // Determine total damage done by smoker so far new Float:startingHangingHealth = float(playerState[client][HEALTH_HANGING_OR_RELEASE]); new Float:currentHangingHealth = float(GetSurvivorPermanentHealth(client)); new Float:totalDamage = (startingHangingHealth - currentHangingHealth) / SMOKER_CLAW_DAMAGE; // Estimate current solid health using solid health remaining right before ledge grab new Float:solidHealth = float(playerState[client][HEALTH_SOLID]); new Float:newSolidHealth = solidHealth - totalDamage; // Estimate current temp health // Only damage temp health if damage exceeds solid health new Float:tempHealth = GetSurvivorTempHealth(client); new Float:tempHealthDmg = totalDamage - solidHealth; new Float:newTempHealth = tempHealth - (tempHealthDmg > 0.0 ? tempHealthDmg : 0.0); // Capture calculated health playerState[client][HEALTH_SOLID_REVIVE] = newSolidHealth < 0.0 ? 0 : RoundToFloor(newSolidHealth); playerState[client][Float:HEALTH_TEMP_REVIVE] = newTempHealth < 0.0 ? 0.0 : newTempHealth; DebugPrintToAllEx( "TD: %f | TPH: %i | TTH %f | TH: %f", totalDamage, playerState[client][HEALTH_SOLID_REVIVE], playerState[client][Float:HEALTH_TEMP_REVIVE], float(playerState[client][HEALTH_SOLID_REVIVE]) + playerState[client][Float:HEALTH_TEMP_REVIVE]); // Return total remaining health return float(playerState[client][HEALTH_SOLID_REVIVE]) + playerState[client][Float:HEALTH_TEMP_REVIVE];}/** * Determines what to do when a player gets incapacitated by a smoker while on a ledge * Scenario 1 - Incap count < 2 * - Set flags to enable plugin to adjust health on revive * - Send message to chat to let everyone know player was incapacitated * * Scenario 2 - Incap count >= 2 * - Set flags to release player from ledge and make them fall * - Adjust health buffer to 0.0 (fixes remaining buffer incase game thinks they have more) * - Kill player * - Send message to chat to let everyone know player has died * * @param client Client index of player */IncapacitateSurvivor(client){ // Get incap count and determine if action needs to be taken new currentIncapCount = GetSurvivorIncapCount(client); if (currentIncapCount < 2) { currentIncapCount++; playerState[client][HANDLE_REVIVE_HEALTH] = 1; playerState[client][HANDLE_REVIVE_TEMP_HEALTH] = 1; playerState[client][HEALTH_SOLID_REVIVE] = 0; playerState[client][Float:HEALTH_TEMP_REVIVE] = 31.0; playerState[client][PLAYER_WAS_INCAPPED] = 1; new attacker = playerState[client][PLAYER_PULLED_BY]; if (attacker) _SEF_SmokerIncappedPlayer_Event(client, attacker); else _SEF_SmokerIncappedPlayer_Event(client, attacker); } else { DebugPrintToAllEx( "Player %i \"%N\" should be dead", client, client); SetEntProp(client, Prop_Send, "m_isIncapacitated", 0); SetEntProp(client, Prop_Send, "m_isHangingFromLedge", 0); SetEntProp(client, Prop_Send, "m_isFallingFromLedge", 1); SetEntPropFloat(client, Prop_Send, "m_healthBuffer", 0.0); ForcePlayerSuicide(client); new attacker = playerState[client][PLAYER_PULLED_BY]; if (attacker) CPrintToChatAll("{green}%N {red}killed {green}%N" , attacker, client); else CPrintToChatAll("{green}%s {red}killed {green}%N", "Smoker", client); }}/* Temporary health functions */Float:GetSurvivorTempHealth(client){ new Float:temphp = GetEntPropFloat(client, Prop_Send, "m_healthBuffer") - ((GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime")) * GetConVarFloat(FindConVar("pain_pills_decay_rate"))) - 1.0; return Float:temphp > 0.0 ? temphp : 0.0;}/** * Sets a clients temporary health * * @param client Client index of player to snapshot * @param Float:health Amount of temporary health * @param Float:startTime Starting decay time (0.0 defaults to current game time) */SetSurvivorTempHealth(client, Float:health, Float:startTime){ if (startTime <= 0.0) startTime = GetGameTime(); SetEntPropFloat(client, Prop_Send, "m_healthBuffer", health); SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", startTime);}/** * Takes a snapshot of a clients temporary health * * @param client Client index of player to snapshot */CaptureTemporaryHealth(client){ // Capture health buffer playerState[client][Float:HEALTH_TEMP_REVIVE] = GetSurvivorTempHealth(client); playerState[client][Float:HEALTH_BUFFER] = GetEntPropFloat(client, Prop_Send, "m_healthBuffer"); playerState[client][Float:HEALTH_BUFFER_TIME] = GetEntPropFloat(client, Prop_Send, "m_healthBufferTime"); DebugPrintToAllEx("Stored temporary health: TH: %f HB: %f | HBT: %f", playerState[client][Float:HEALTH_TEMP_REVIVE], playerState[client][Float:HEALTH_BUFFER], playerState[client][Float:HEALTH_BUFFER_TIME]);}/** * Gets permanent health * * @param client Client index of player */GetSurvivorPermanentHealth(client){ return GetEntProp(client, Prop_Send, "m_iHealth");}/** * Sets permanent health * * @param client Client index of player *///SetSurvivorPermanentHealth(client, health)//{// health = health > 0 ? health : 0;// SetEntProp(client, Prop_Send, "m_iHealth", health);//}/** * Takes a snapshot of a clients permanent health * * @param client Client index of player to snapshot */CapturePermanentHealth(client){ playerState[client][HEALTH_SOLID] = GetSurvivorPermanentHealth(client); return playerState[client][HEALTH_SOLID];}/** * Takes a snapshot of a clients hanging healh * * @param client Client index of player to snapshot */CaptureHangingHealth(client){ playerState[client][HEALTH_HANGING_OR_RELEASE] = GetSurvivorPermanentHealth(client); DebugPrintToAllEx("Storing hanging health: %i", playerState[client][HEALTH_HANGING_OR_RELEASE]);}/** * Gets a survivors incap count * * @param client Client index of player */GetSurvivorIncapCount(client){ return GetEntProp(client, Prop_Send, "m_currentReviveCount");}/** * Resets player state tracked by the plugin * * @param client Client index of player to reset * @noreturn */ResetPlayerState(client){ playerState[client][PLAYER_PULLED] = 0; playerState[client][PLAYER_HANGING] = 0; playerState[client][PLAYER_WAS_INCAPPED] = 0; playerState[client][HEALTH_SOLID] = 0; playerState[client][HEALTH_HANGING_OR_RELEASE] = 0; playerState[client][HEALTH_REVIVE_START] = 0; playerState[client][HEALTH_SOLID_REVIVE] = -1; playerState[client][Float:HEALTH_TEMP_REVIVE] = -1.0; playerState[client][HANDLE_REVIVE_HEALTH] = 0; playerState[client][HANDLE_REVIVE_TEMP_HEALTH] = 1; // Set to 1 to always handle temp health on revive playerState[client][Float:HEALTH_BUFFER] = 0.0; playerState[client][Float:HEALTH_BUFFER_TIME] = 0.0; DebugPrintToAllEx("Reset player state for client %i \"%N\"", client, client);}/** * Resets all player states tracked by the plugin * * @noreturn */ResetAllPlayerStates(){ for(new i = 0; i < MAXPLAYERS+1; i++) { ResetPlayerState(i); }}/** * Copies player state from one player to another * * @param clientFrom Client to copy from * @param clientTo Client to copy to * @noreturn */CopyPlayerState(clientFrom, clientTo){ playerState[clientTo][PLAYER_PULLED] = playerState[clientFrom][PLAYER_PULLED]; playerState[clientTo][PLAYER_PULLED_BY] = playerState[clientFrom][PLAYER_PULLED_BY]; playerState[clientTo][PLAYER_HANGING] = playerState[clientFrom][PLAYER_HANGING]; playerState[clientTo][PLAYER_WAS_INCAPPED] = playerState[clientFrom][PLAYER_WAS_INCAPPED]; playerState[clientTo][HEALTH_SOLID] = playerState[clientFrom][HEALTH_SOLID]; playerState[clientTo][HEALTH_HANGING_OR_RELEASE] = playerState[clientFrom][HEALTH_HANGING_OR_RELEASE]; playerState[clientTo][HEALTH_REVIVE_START] = playerState[clientFrom][HEALTH_REVIVE_START]; playerState[clientTo][HEALTH_SOLID_REVIVE] = playerState[clientFrom][HEALTH_SOLID_REVIVE]; playerState[clientTo][Float:HEALTH_TEMP_REVIVE] = playerState[clientFrom][Float:HEALTH_TEMP_REVIVE]; playerState[clientTo][HANDLE_REVIVE_HEALTH] = playerState[clientFrom][HANDLE_REVIVE_HEALTH]; playerState[clientTo][HANDLE_REVIVE_TEMP_HEALTH] = playerState[clientFrom][HANDLE_REVIVE_TEMP_HEALTH]; playerState[clientTo][Float:HEALTH_BUFFER] = playerState[clientFrom][Float:HEALTH_BUFFER]; playerState[clientTo][Float:HEALTH_BUFFER_TIME] = playerState[clientFrom][Float:HEALTH_BUFFER_TIME]; DebugPrintToAllEx("Copied player state to client %i from client %i", clientFrom, clientTo); // Restart Timers if (playerState[clientFrom][PLAYER_PULLED] == 1 && playerState[clientFrom][Handle:TIMER_HEALTH_TRACKER] != INVALID_HANDLE) { CloseHandle(playerState[clientFrom][Handle:TIMER_HEALTH_TRACKER]); playerState[clientFrom][Handle:TIMER_HEALTH_TRACKER] = INVALID_HANDLE; StartPulledHealthMonitor(clientTo, playerState[clientTo][PLAYER_PULLED_BY]); } ResetPlayerState(clientFrom);}/** * Hurt client for 1 damage * Used to fix incaps on revive * * @param clientFrom Client to copy from * @param clientTo Client to copy to * @param client Client index of player * @noreturn */HurtMe(client){ new String:command[] = "hurtme"; new flags = GetCommandFlags(command); SetCommandFlags(command, flags & ~FCVAR_CHEAT); FakeClientCommand(client, "hurtme 1"); // restore z_spawn SetCommandFlags(command, flags);}/** * Wrapper for printing a debug message without having to define channel index * everytime. * * @param format Formatting rules. * @param ... Variable number of format parameters. * @noreturn */static DebugPrintToAllEx(const String:format[], any:...){ decl String:buffer[DEBUG_MESSAGE_LENGTH]; VFormat(buffer, sizeof(buffer), format, 2); DebugPrintToAll(g_iDebugChannel, buffer);} stock _SEF_SmokerIncappedPlayer_Event(client, atttacker){new Handle:event = CreateEvent("player_incapacitated", true);SetEventInt(event, "userid", GetClientUserId(client));SetEventInt(event, "attacker", GetClientUserId(attacker));SetEventInt(event, "attackerentid", 0);SetEventString(event, "weapon", "smoker_claw");FireEvent(event, false);}