diff --git a/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval.smx b/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval.smx index ad99fe0ec..3657964b9 100644 Binary files a/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval.smx and b/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval.smx differ diff --git a/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval_zone.smx b/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval_zone.smx deleted file mode 100644 index a4426cf07..000000000 Binary files a/addons/sourcemod/plugins/optional/l4d2_smoker_drag_damage_interval_zone.smx and /dev/null differ diff --git a/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval.sp b/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval.sp index af60931d4..3d29db1f6 100644 --- a/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval.sp +++ b/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval.sp @@ -1,44 +1,91 @@ +/** + * Version 2.3 by A1m` + * + * Changes: + * 1. Removed duplicate plugins: + * - l4d2_smoker_drag_damage_interval_zone + * - l4d2_smoker_drag_damage_interval. + * + * 2. Removed untrusted timer-based code: + * - Replaced with safer, hook-based implementation using OnTakeDamage. + * - Ensures more stable and reliable drag damage behavior without unnecessary timers. + * + * Notes: + * The timing of damage is not perfect, as 'CTerrorPlayer::PostThink' is not called every frame, + * but after a certain period of time depending on the number of players and the tick rate (see function 'CTerrorPlayer::ShouldPostThink'). + * For this reason, it is difficult to calculate using game netprops whether this was the first damage dealt or not, + * the code becomes too complicated, so we introduce our own variables to determine this. +**/ + #pragma semicolon 1 #pragma newdecls required #include +#include + +#define DEBUG 0 +#define GAMEDATA "l4d2_si_ability" + +// DMG_CHOKE = 1048576 = 0x100000 = (1 << 20) +#define DMG_CHOKE (1 << 20) -#define GAMEDATA "l4d2_si_ability" +#define IT_TIMESTAMP_INDEX 0 +#define CT_DURATION_OFFSET 4 +#define CT_TIMESTAMP_OFFSET 8 -#define DURATION_OFFSET 4 -#define TIMESTAMP_OFFSET 8 +#define TEAM_SURVIVOR 2 +#define TEAM_INFECTED 3 -#define TEAM_SURVIVOR 2 +#define EPSILON 0.001 -int - m_tongueDragDamageTimerDuration, - m_tongueDragDamageTimerTimeStamp; +#if DEBUG +float + g_fDebugDamageInterval = 0.0; +#endif -ConVar - tongue_drag_damage_interval; +enum +{ + eUserId = 0, + eHitCount, + + eDamageInfo_Size +}; + +int + g_iTongueHitCount[MAXPLAYERS + 1][eDamageInfo_Size], + g_iTongueDragDamageTimerDurationOffset = -1, + g_iTongueDragDamageTimerTimeStampOffset = -1; + +ConVar + g_hTongueDragDamageInterval = null, + g_hTongueDragFirstDamageInterval = null, + g_hTongueDragFirstDamage = null; public Plugin myinfo = { - name = "L4D2 Smoker Drag Damage Interval", - author = "Visor, A1m`", + name = "L4D2 smoker drag damage interval", + author = "Visor, Sir, A1m`", description = "Implements a native-like cvar that should've been there out of the box", - version = "0.7", + version = "2.3", url = "https://github.com/SirPlease/L4D2-Competitive-Rework" }; public void OnPluginStart() { InitGameData(); - HookEvent("tongue_grab", OnTongueGrab); - - char value[32]; - ConVar tongue_choke_damage_interval = FindConVar("tongue_choke_damage_interval"); - tongue_choke_damage_interval.GetString(value, sizeof(value)); - - tongue_drag_damage_interval = CreateConVar("tongue_drag_damage_interval", value, "How often the drag does damage."); - - ConVar tongue_choke_damage_amount = FindConVar("tongue_choke_damage_amount"); - tongue_choke_damage_amount.AddChangeHook(tongue_choke_damage_amount_ValueChanged); + + HookEvent("tongue_grab", Event_OnTongueGrab); + + // Get the default value of cvar 'tongue_choke_damage_interval' + char sCvarVal[32]; + ConVar hTongueChokeDamageInterval = FindConVar("tongue_choke_damage_interval"); + hTongueChokeDamageInterval.GetDefault(sCvarVal, sizeof(sCvarVal)); + + g_hTongueDragDamageInterval = CreateConVar("tongue_drag_damage_interval", sCvarVal, "How often the drag does damage. Allowed values: 0.01 - 15.0.", _, true, 0.01, true, 15.0); + g_hTongueDragFirstDamageInterval = CreateConVar("tongue_drag_first_damage_interval", "-1.0", "After how many seconds do we apply our first tick of damage? 0.0 - disable, max value - 15.0.", _, false, 0.0, true, 15.0); + g_hTongueDragFirstDamage = CreateConVar("tongue_drag_first_damage", "-1.0", "How much damage do we apply on the first tongue hit? 0.0 - disable", _, false, 0.0, true, 100.0); + + LateLoad(); } void InitGameData() @@ -48,59 +95,119 @@ void InitGameData() if (!hGamedata) { SetFailState("Gamedata '%s.txt' missing or corrupt.", GAMEDATA); } - - int m_tongueDragDamageTimer = GameConfGetOffset(hGamedata, "CTerrorPlayer->m_tongueDragDamageTimer"); - if (m_tongueDragDamageTimer == -1) { + + int iTongueDragDamageTimer = GameConfGetOffset(hGamedata, "CTerrorPlayer->m_tongueDragDamageTimer"); + if (iTongueDragDamageTimer == -1) { SetFailState("Failed to get offset 'CTerrorPlayer->m_tongueDragDamageTimer'."); } - - m_tongueDragDamageTimerDuration = m_tongueDragDamageTimer + DURATION_OFFSET; - m_tongueDragDamageTimerTimeStamp = m_tongueDragDamageTimer + TIMESTAMP_OFFSET; - + + g_iTongueDragDamageTimerDurationOffset = iTongueDragDamageTimer + CT_DURATION_OFFSET; + g_iTongueDragDamageTimerTimeStampOffset = iTongueDragDamageTimer + CT_TIMESTAMP_OFFSET; + delete hGamedata; } -void tongue_choke_damage_amount_ValueChanged(ConVar convar, const char[] oldValue, const char[] newValue) +void LateLoad() { - convar.SetInt(1); // hack-hack: game tries to change this cvar for some reason, can't be arsed so HARDCODETHATSHIT + for (int i = 1; i <= MaxClients; i++) { + if (!IsClientInGame(i)) { + continue; + } + + OnClientPutInServer(i); + } } -void OnTongueGrab(Event hEvent, const char[] eName, bool dontBroadcast) +public void OnClientPutInServer(int iClient) { - int userid = hEvent.GetInt("victim"); - int client = GetClientOfUserId(userid); - - SetDragDamageInterval(client); - - float fTimerUpdate = tongue_drag_damage_interval.FloatValue + 0.1; - CreateTimer(fTimerUpdate, FixDragInterval, userid, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); + SDKHook(iClient, SDKHook_OnTakeDamage, Hook_OnTakeDamage); } -Action FixDragInterval(Handle hTimer, any userid) +void Event_OnTongueGrab(Event hEvent, const char[] eName, bool bDontBroadcast) { - int client = GetClientOfUserId(userid); - if (client > 0 && GetClientTeam(client) == TEAM_SURVIVOR && IsSurvivorBeingDragged(client)) { - SetDragDamageInterval(client); + // Replacing variable value 'CTerrorPlayer::m_tongueDragDamageTimer', + // ​​after calling a function 'CTerrorPlayer::OnGrabbedByTongue'. + // Fix damage interval. + + int iVictim = GetClientOfUserId(hEvent.GetInt("victim")); + bool bIsHangingFromTongue = (GetEntProp(iVictim, Prop_Send, "m_isHangingFromTongue", 1) > 0); + + if (!bIsHangingFromTongue) { // Dragging? + SetDragDamageTimer(iVictim, GetFirstDamageInterval()); + } + + g_iTongueHitCount[iVictim][eUserId] = hEvent.GetInt("victim"); + g_iTongueHitCount[iVictim][eHitCount] = 0; + +#if DEBUG + g_fDebugDamageInterval = GetGameTime(); +#endif +} + +Action Hook_OnTakeDamage(int iVictim, int &iAttacker, int &iInflictor, float &fDamage, int &iDamageType) +{ + // Replacing the function patch 'CTerrorPlayer::UpdateHangingFromTongue'. + // This dmg function is called after variable 'CTerrorPlayer::m_tongueDragDamageTimer' is set, we can't get it here. + if (!(iDamageType & DMG_CHOKE)) { + return Plugin_Continue; + } + + int iTongueOwner = GetEntPropEnt(iVictim, Prop_Send, "m_tongueOwner"); + if (iTongueOwner < 1 || iTongueOwner > MaxClients || iTongueOwner != iAttacker) { + return Plugin_Continue; + } + + // Stop dragging. + if (GetEntProp(iVictim, Prop_Send, "m_isHangingFromTongue", 1) > 0) { return Plugin_Continue; } - return Plugin_Stop; + + // Fix damage interval. + SetDragDamageTimer(iVictim, g_hTongueDragDamageInterval.FloatValue); + + // First damage if cvar enabled. + g_iTongueHitCount[iVictim][eHitCount]++; + bool bFirstDamage = false; + + if (g_hTongueDragFirstDamage.FloatValue > 0.0) { + if (g_iTongueHitCount[iVictim][eHitCount] == 1 && g_iTongueHitCount[iVictim][eUserId] == GetClientUserId(iVictim)) { + fDamage = g_hTongueDragFirstDamage.FloatValue; + bFirstDamage = true; + } + } + +#if DEBUG + DebugPrint(iVictim, fDamage, bFirstDamage); +#endif + + return (bFirstDamage) ? Plugin_Changed : Plugin_Continue; } -void SetDragDamageInterval(int client) +float GetFirstDamageInterval() { - float fCvarValue = tongue_drag_damage_interval.FloatValue; - float fTimeStamp = GetGameTime() + fCvarValue; - - SetEntDataFloat(client, m_tongueDragDamageTimerDuration, fCvarValue); //duration - SetEntDataFloat(client, m_tongueDragDamageTimerTimeStamp, fTimeStamp); //timestamp + float fTongueFirstDamageInterval = g_hTongueDragFirstDamageInterval.FloatValue; + if (fTongueFirstDamageInterval > 0.0) { + return fTongueFirstDamageInterval; + } + + return g_hTongueDragDamageInterval.FloatValue; } -bool IsSurvivorBeingDragged(int client) +void SetDragDamageTimer(int iClient, float fDuration) { - return ((GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) && !IsSurvivorBeingChoked(client)); + // 'CTerrorPlayer::m_tongueDragDamageTimer', this is not netprop + float fTimeStamp = GetGameTime() + fDuration; + + SetEntDataFloat(iClient, g_iTongueDragDamageTimerDurationOffset, fDuration, false); // 'CountdownTimer::duration' + SetEntDataFloat(iClient, g_iTongueDragDamageTimerTimeStampOffset, fTimeStamp, false); // 'CountdownTimer::timestamp' } -bool IsSurvivorBeingChoked(int client) +#if DEBUG +void DebugPrint(int iVictim, float fDamage, bool bFirstDamage) { - return (GetEntProp(client, Prop_Send, "m_isHangingFromTongue") > 0); + PrintToChatAll("[DEBUG] Victim: %N, %sdamage: %f, time: %f, game time: %f", \ + iVictim, (bFirstDamage) ? "first " : "", fDamage, GetGameTime() - g_fDebugDamageInterval, GetGameTime()); + + g_fDebugDamageInterval = GetGameTime(); } +#endif diff --git a/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval_zone.sp b/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval_zone.sp deleted file mode 100644 index e3122c2fc..000000000 --- a/addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval_zone.sp +++ /dev/null @@ -1,143 +0,0 @@ -#pragma semicolon 1 -#pragma newdecls required - -#include -#include -#include - -#define GAMEDATA "l4d2_si_ability" - -#define DURATION_OFFSET 4 -#define TIMESTAMP_OFFSET 8 - -#define TEAM_SURVIVOR 2 -#define TEAM_INFECTED 3 - -int - m_tongueDragDamageTimerDuration, - m_tongueDragDamageTimerTimeStamp; - -ConVar - tongue_drag_damage_interval, - tongue_drag_first_damage_interval, - tongue_drag_first_damage; - -public Plugin myinfo = -{ - name = "L4D2 Smoker Drag Damage Interval", - author = "Visor, Sir, A1m`", - description = "Implements a native-like cvar that should've been there out of the box", - version = "1.0", - url = "https://github.com/SirPlease/L4D2-Competitive-Rework" -}; - -public void OnPluginStart() -{ - InitGameData(); - - HookEvent("tongue_grab", OnTongueGrab); - - char value[32]; - ConVar tongue_choke_damage_interval = FindConVar("tongue_choke_damage_interval"); - tongue_choke_damage_interval.GetString(value, sizeof(value)); - - tongue_drag_damage_interval = CreateConVar("tongue_drag_damage_interval", value, "How often the drag does damage."); - tongue_drag_first_damage_interval = CreateConVar("tongue_drag_first_damage_interval", "-1.0", "After how many seconds do we apply our first tick of damage? | 0.0 to Disable."); - tongue_drag_first_damage = CreateConVar("tongue_drag_first_damage", "3.0", "How much damage do we apply on the first tongue hit? | Only applies when first_damage_interval is used"); - - ConVar tongue_choke_damage_amount = FindConVar("tongue_choke_damage_amount"); - tongue_choke_damage_amount.AddChangeHook(tongue_choke_damage_amount_ValueChanged); -} - -void InitGameData() -{ - Handle hGamedata = LoadGameConfigFile(GAMEDATA); - - if (!hGamedata) { - SetFailState("Gamedata '%s.txt' missing or corrupt.", GAMEDATA); - } - - int m_tongueDragDamageTimer = GameConfGetOffset(hGamedata, "CTerrorPlayer->m_tongueDragDamageTimer"); - if (m_tongueDragDamageTimer == -1) { - SetFailState("Failed to get offset 'CTerrorPlayer->m_tongueDragDamageTimer'."); - } - - m_tongueDragDamageTimerDuration = m_tongueDragDamageTimer + DURATION_OFFSET; - m_tongueDragDamageTimerTimeStamp = m_tongueDragDamageTimer + TIMESTAMP_OFFSET; - - delete hGamedata; -} - -void tongue_choke_damage_amount_ValueChanged(ConVar convar, const char[] oldValue, const char[] newValue) -{ - SetConVarInt(convar, 1); // hack-hack: game tries to change this cvar for some reason, can't be arsed so HARDCODETHATSHIT -} - -void OnTongueGrab(Event hEvent, const char[] name, bool dontBroadcast) -{ - int userid = hEvent.GetInt("victim"); - int client = GetClientOfUserId(userid); - float fFirst = tongue_drag_first_damage_interval.FloatValue; - - if (fFirst < 0.0) { - FixDragInterval(client, userid); - return; - } - - SetDragDamageInterval(client, tongue_drag_first_damage_interval); - CreateTimer(fFirst, FirstDamage, userid, TIMER_FLAG_NO_MAPCHANGE); -} - -void FixDragInterval(int client, int userid) -{ - SetDragDamageInterval(client, tongue_drag_damage_interval); - - float fTimerUpdate = tongue_drag_damage_interval.FloatValue + 0.1; - CreateTimer(fTimerUpdate, FixDragIntervalTimer, userid, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE); -} - -Action FirstDamage(Handle hTimer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client > 0 && GetClientTeam(client) == TEAM_SURVIVOR && IsSurvivorBeingDragged(client)) { - int iAttacker = GetEntPropEnt(client, Prop_Send, "m_tongueOwner"); - if (IsClientInGame(iAttacker) && GetClientTeam(iAttacker) == TEAM_INFECTED) { - float fDamage = tongue_drag_first_damage.FloatValue - 1.0; - SDKHooks_TakeDamage(client, iAttacker, iAttacker, fDamage); - } - - FixDragInterval(client, userid); - return Plugin_Continue; - } - - return Plugin_Continue; -} - -Action FixDragIntervalTimer(Handle hTimer, any userid) -{ - int client = GetClientOfUserId(userid); - if (client > 0 && GetClientTeam(client) == TEAM_SURVIVOR && IsSurvivorBeingDragged(client)) { - SetDragDamageInterval(client, tongue_drag_damage_interval); - return Plugin_Continue; - } - return Plugin_Stop; -} - -void SetDragDamageInterval(int client, ConVar hConvar) -{ - float fCvarValue = hConvar.FloatValue; - float fTimeStamp = GetGameTime() + fCvarValue; - - SetEntDataFloat(client, m_tongueDragDamageTimerDuration, fCvarValue); //duration - SetEntDataFloat(client, m_tongueDragDamageTimerTimeStamp, fTimeStamp); //timestamp -} - -bool IsSurvivorBeingDragged(int client) -{ - return ((GetEntPropEnt(client, Prop_Send, "m_tongueOwner") > 0) && !IsSurvivorBeingChoked(client)); -} - -bool IsSurvivorBeingChoked(int client) -{ - return (GetEntProp(client, Prop_Send, "m_isHangingFromTongue") > 0); -} diff --git a/cfg/cfgogl/apex/shared_plugins.cfg b/cfg/cfgogl/apex/shared_plugins.cfg index a1ab438c5..2d95d7f0c 100644 --- a/cfg/cfgogl/apex/shared_plugins.cfg +++ b/cfg/cfgogl/apex/shared_plugins.cfg @@ -73,7 +73,7 @@ sm plugins load optional/l4d_texture_manager_block.smx //---------------------- // EQ3 / Acemod / ZoneMod / Apex //---------------------- -sm plugins load optional/l4d2_smoker_drag_damage_interval_zone.smx +sm plugins load optional/l4d2_smoker_drag_damage_interval.smx sm plugins load optional/l4d_tankpunchstuckfix.smx sm plugins load optional/despawn_health.smx sm plugins load optional/checkpoint-rage-control.smx diff --git a/cfg/cfgogl/neomod/shared_plugins.cfg b/cfg/cfgogl/neomod/shared_plugins.cfg index 789ba7ac6..c7835c9ba 100644 --- a/cfg/cfgogl/neomod/shared_plugins.cfg +++ b/cfg/cfgogl/neomod/shared_plugins.cfg @@ -77,7 +77,7 @@ sm plugins load optional/l4d_texture_manager_block.smx //---------------------- // EQ3 / Acemod / ZoneMod //---------------------- -sm plugins load optional/l4d2_smoker_drag_damage_interval_zone.smx +sm plugins load optional/l4d2_smoker_drag_damage_interval.smx sm plugins load optional/l4d_tankpunchstuckfix.smx sm plugins load optional/despawn_health.smx sm plugins load optional/checkpoint-rage-control.smx diff --git a/cfg/cfgogl/nextmod/shared_plugins.cfg b/cfg/cfgogl/nextmod/shared_plugins.cfg index c2f9e3e52..a79eba6c6 100644 --- a/cfg/cfgogl/nextmod/shared_plugins.cfg +++ b/cfg/cfgogl/nextmod/shared_plugins.cfg @@ -54,7 +54,7 @@ sm plugins load optional/l4d2_spitblock.smx sm plugins load optional/l4d2_uniform_spit.smx sm plugins load optional/l4d_tank_painfade.smx sm plugins load optional/l4d_texture_manager_block.smx -sm plugins load optional/l4d2_smoker_drag_damage_interval_zone.smx +sm plugins load optional/l4d2_smoker_drag_damage_interval.smx sm plugins load optional/l4d_tankpunchstuckfix.smx sm plugins load optional/despawn_health.smx sm plugins load optional/checkpoint-rage-control.smx diff --git a/cfg/cfgogl/pmelite/confogl_plugins.cfg b/cfg/cfgogl/pmelite/confogl_plugins.cfg index 42ca010ee..735c62f7c 100644 --- a/cfg/cfgogl/pmelite/confogl_plugins.cfg +++ b/cfg/cfgogl/pmelite/confogl_plugins.cfg @@ -67,7 +67,7 @@ sm plugins load optional/l4d2_saferoom_detect.smx sm plugins load optional/l4d2_saferoom_item_remove.smx sm plugins load optional/l4d2_scoremod.smx sm plugins load optional/l4d2_setscores.smx -sm plugins load optional/l4d2_smoker_drag_damage_interval_zone.smx +sm plugins load optional/l4d2_smoker_drag_damage_interval.smx sm plugins load optional/l4d2_si_ffblock.smx sm plugins load optional/l4d2_si_staggers.smx sm plugins load optional/l4d2_skill_detect.smx