From 1a9fab284d408cf574481678c449106ecd78b7c1 Mon Sep 17 00:00:00 2001 From: honey <934223+Derpduck@users.noreply.github.com> Date: Fri, 29 May 2026 08:34:49 +0100 Subject: [PATCH 1/2] DKR 2.0 Fix map 2 ladder (#975) Re-do the ladder fix for DKR 2.0, compatible with both versions now --- cfg/stripper/zonemod/maps/dkr_m2_carnival.cfg | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/cfg/stripper/zonemod/maps/dkr_m2_carnival.cfg b/cfg/stripper/zonemod/maps/dkr_m2_carnival.cfg index a2cf27b20..5e10c50d6 100644 --- a/cfg/stripper/zonemod/maps/dkr_m2_carnival.cfg +++ b/cfg/stripper/zonemod/maps/dkr_m2_carnival.cfg @@ -33,20 +33,15 @@ add: ; "targetname" "eb_fix01" } - ; --- Fix broken infected ladder on telephone pole after the warehouse -filter: -{ - "hammerid" "1184297" -} -add: +modify: { - "classname" "func_simpleladder" - "origin" "-1.00049 7.33105 -90.5992" - "angles" "0 0 3" - "model" "*205" - "normal.x" "0" - "normal.y" "-0.99863" - "normal.z" "-0.052336" - "team" "2" + match: + { + "hammerid" "1184297" + } + insert: + { + "origin" "-5 -5 10" + } } \ No newline at end of file From 9ce4911cb893906b21ecaebd91fecb32fca77a94 Mon Sep 17 00:00:00 2001 From: SirPlease Date: Fri, 29 May 2026 11:04:21 +0200 Subject: [PATCH 2/2] Add functionality to l4d2_smoker_drag_damage_interval Add a ConVar that allows us to carry over the remaining time until doing damage when transitioning between dragging and choking a survivor as smoker. In Vanilla the timer would get fully reset. Also deals with a situation where the smoker sometimes gets an instant tick of damage (basically a 2x) Example: Survivor gets pulled (drag), they take 2 ticks of damage and are about to take another tick of damage but they are "fully reeled in" or "stuck" (choke), instead of applying the damage it resets the damage timer entirely, missing out on damage. --- .../l4d2_smoker_drag_damage_interval.txt | 56 ++++++ .../l4d2_smoker_drag_damage_interval.smx | Bin 5650 -> 7467 bytes .../l4d2_smoker_drag_damage_interval.sp | 185 ++++++++++++++++-- 3 files changed, 229 insertions(+), 12 deletions(-) create mode 100644 addons/sourcemod/gamedata/l4d2_smoker_drag_damage_interval.txt diff --git a/addons/sourcemod/gamedata/l4d2_smoker_drag_damage_interval.txt b/addons/sourcemod/gamedata/l4d2_smoker_drag_damage_interval.txt new file mode 100644 index 000000000..f5c2e9b1d --- /dev/null +++ b/addons/sourcemod/gamedata/l4d2_smoker_drag_damage_interval.txt @@ -0,0 +1,56 @@ +"Games" +{ + "left4dead2" + { + "Offsets" + { + /* + * CountdownTimer read by CTerrorPlayer::UpdateHangingFromTongue + * while the victim is being DRAGGED (m_isHangingFromTongue == 0). + */ + "CTerrorPlayer->m_tongueDragDamageTimer" + { + "linux" "13312" + "windows" "13332" + } + + /* CountdownTimer read by CTerrorPlayer::UpdateHangingFromTongue + * while the victim is HANGING/CHOKING (m_isHangingFromTongue == 1). + */ + "CTerrorPlayer->m_tongueChokeDamageTimer" + { + "linux" "13288" + "windows" "13308" + } + } + + "Functions" + { + "CTerrorPlayer::OnStartHangingFromTongue" + { + "signature" "CTerrorPlayer::OnStartHangingFromTongue" + "callconv" "thiscall" + "return" "void" + "this" "entity" + "arguments" + { + "method" + { + "type" "int" + } + } + } + } + + "Signatures" + { + "CTerrorPlayer::OnStartHangingFromTongue" + { + "library" "server" + "linux" "@_ZN13CTerrorPlayer24OnStartHangingFromTongueEi" + "windows" "\x55\x8B\xEC\x51\x8B\x45\x08\x53\x57\x8B\xF9" + /* 55 8B EC 51 8B 45 08 53 57 8B F9 */ + } + } + } +} 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 5c88180683f8e1a2cd2142209a98247e4c8b7773..d497ce18fda9ff098c4c5b89a050b4d960e69fc2 100644 GIT binary patch literal 7467 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 literal 5650 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 #include +#include #define DEBUG 0 -#define GAMEDATA "l4d2_si_ability" +#define GAMEDATA "l4d2_smoker_drag_damage_interval" // DMG_CHOKE = 1048576 = 0x100000 = (1 << 20) #define DMG_CHOKE (1 << 20) -#define IT_TIMESTAMP_INDEX 0 #define CT_DURATION_OFFSET 4 #define CT_TIMESTAMP_OFFSET 8 @@ -54,19 +65,31 @@ enum int g_iTongueHitCount[MAXPLAYERS + 1][eDamageInfo_Size], g_iTongueDragDamageTimerDurationOffset = -1, - g_iTongueDragDamageTimerTimeStampOffset = -1; + g_iTongueDragDamageTimerTimeStampOffset = -1, + g_iTongueChokeDamageTimerTimeStampOffset = -1, + g_iTongueReleaseTick[MAXPLAYERS + 1]; + +float + g_fDragTimestampSnapshot[MAXPLAYERS + 1]; + +bool + g_bSnapshotValid[MAXPLAYERS + 1]; ConVar g_hTongueDragDamageInterval = null, g_hTongueDragFirstDamageInterval = null, - g_hTongueDragFirstDamage = null; + g_hTongueDragFirstDamage = null, + g_hTongueDamageContinuity = null; + +DynamicDetour + g_hOnStartHangingFromTongue = null; 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 = "2.3", + description = "Implements a native-like cvar and functionality that should've been there out of the box", + version = "2.4", url = "https://github.com/SirPlease/L4D2-Competitive-Rework" }; @@ -75,6 +98,8 @@ public void OnPluginStart() InitGameData(); HookEvent("tongue_grab", Event_OnTongueGrab); + HookEvent("tongue_release", Event_OnTongueRelease); + HookEvent("choke_end", Event_OnChokeEnd); // Get the default value of cvar 'tongue_choke_damage_interval' char sCvarVal[32]; @@ -84,27 +109,43 @@ public void OnPluginStart() 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); + g_hTongueDamageContinuity = CreateConVar("tongue_damage_continuity", "0", "Preserve damage timer between drag <-> choke transitions. 0 = Vanilla behavior, 1 = carry over remaining time of the choke/drag to the next timer", _, true, 0.0, true, 1.0); LateLoad(); } void InitGameData() { - Handle hGamedata = LoadGameConfigFile(GAMEDATA); - - if (!hGamedata) { + GameData gd = new GameData(GAMEDATA); + if (!gd) { SetFailState("Gamedata '%s.txt' missing or corrupt.", GAMEDATA); } - int iTongueDragDamageTimer = GameConfGetOffset(hGamedata, "CTerrorPlayer->m_tongueDragDamageTimer"); + int iTongueDragDamageTimer = gd.GetOffset("CTerrorPlayer->m_tongueDragDamageTimer"); if (iTongueDragDamageTimer == -1) { SetFailState("Failed to get offset 'CTerrorPlayer->m_tongueDragDamageTimer'."); } - g_iTongueDragDamageTimerDurationOffset = iTongueDragDamageTimer + CT_DURATION_OFFSET; g_iTongueDragDamageTimerTimeStampOffset = iTongueDragDamageTimer + CT_TIMESTAMP_OFFSET; - delete hGamedata; + int iTongueChokeDamageTimer = gd.GetOffset("CTerrorPlayer->m_tongueChokeDamageTimer"); + if (iTongueChokeDamageTimer == -1) { + SetFailState("Failed to get offset 'CTerrorPlayer->m_tongueChokeDamageTimer'."); + } + g_iTongueChokeDamageTimerTimeStampOffset = iTongueChokeDamageTimer + CT_TIMESTAMP_OFFSET; + + g_hOnStartHangingFromTongue = DynamicDetour.FromConf(gd, "CTerrorPlayer::OnStartHangingFromTongue"); + if (!g_hOnStartHangingFromTongue) { + SetFailState("Failed to set up detour for 'CTerrorPlayer::OnStartHangingFromTongue'."); + } + if (!g_hOnStartHangingFromTongue.Enable(Hook_Pre, Detour_OnStartHangingFromTongue_Pre)) { + SetFailState("Failed to enable Pre detour on 'CTerrorPlayer::OnStartHangingFromTongue'."); + } + if (!g_hOnStartHangingFromTongue.Enable(Hook_Post, Detour_OnStartHangingFromTongue_Post)) { + SetFailState("Failed to enable Post detour on 'CTerrorPlayer::OnStartHangingFromTongue'."); + } + + delete gd; } void LateLoad() @@ -121,6 +162,16 @@ void LateLoad() public void OnClientPutInServer(int iClient) { SDKHook(iClient, SDKHook_OnTakeDamage, Hook_OnTakeDamage); + g_fDragTimestampSnapshot[iClient] = -1.0; + g_bSnapshotValid[iClient] = false; + g_iTongueReleaseTick[iClient] = -1; +} + +public void OnClientDisconnect(int iClient) +{ + g_fDragTimestampSnapshot[iClient] = -1.0; + g_bSnapshotValid[iClient] = false; + g_iTongueReleaseTick[iClient] = -1; } void Event_OnTongueGrab(Event hEvent, const char[] eName, bool bDontBroadcast) @@ -183,6 +234,116 @@ Action Hook_OnTakeDamage(int iVictim, int &iAttacker, int &iInflictor, float &fD return (bFirstDamage) ? Plugin_Changed : Plugin_Continue; } +MRESReturn Detour_OnStartHangingFromTongue_Pre(int client, DHookParam hParams) +{ + if (client < 1 || client > MaxClients) { + return MRES_Ignored; + } + + // Set it to false here as it's called non-stop during the pull, easy reset. + g_bSnapshotValid[client] = false; + + if (GetEntProp(client, Prop_Send, "m_isHangingFromTongue", 1) > 0) { + return MRES_Ignored; + } + + // We only land here when the client is about to officially StartHangingFromTongue. + g_fDragTimestampSnapshot[client] = GetEntDataFloat(client, g_iTongueDragDamageTimerTimeStampOffset); + g_bSnapshotValid[client] = true; + return MRES_Ignored; +} + +MRESReturn Detour_OnStartHangingFromTongue_Post(int client, DHookParam hParams) +{ + if (client < 1 || client > MaxClients) { + return MRES_Ignored; + } + + // Store current & reset + bool bValid = g_bSnapshotValid[client]; + float fSnapshot = g_fDragTimestampSnapshot[client]; + g_bSnapshotValid[client] = false; + g_fDragTimestampSnapshot[client] = -1.0; + + // Player is already hanging + if (!bValid) { + return MRES_Ignored; + } + + if (!g_hTongueDamageContinuity.BoolValue) { + return MRES_Ignored; + } + + // Shouldn't happen, but just in case. (Lets Vanilla behavior continue) + if (fSnapshot < 0.0) { + return MRES_Ignored; + } + + float fNow = GetGameTime(); + float fRemaining = fSnapshot - fNow; + if (fRemaining < 0.0) { + fRemaining = 0.0; + } + + /* + Game just set its choke_timer.m_timestamp to now + tongue_choke_damage_interval + Replace with now + remaining_drag_time so the damage timer continues instead of restarting. + We leave m_duration alone because UpdateHangingFromTongue restamps it to the cvar on the first choke tick. + */ +#if DEBUG + float fEngineChokeTs = GetEntDataFloat(client, g_iTongueChokeDamageTimerTimeStampOffset); + float fEngineWouldFireIn = fEngineChokeTs - fNow; + PrintToChatAll("[tongue_continuity] %N drag->choke: damage clock CONTINUED - next damage in %.2fs (vanilla would have reset to %.2fs)", \ + client, fRemaining, fEngineWouldFireIn); +#endif + + SetEntDataFloat(client, g_iTongueChokeDamageTimerTimeStampOffset, fNow + fRemaining, false); + + return MRES_Ignored; +} + +void Event_OnTongueRelease(Event hEvent, const char[] eName, bool bDontBroadcast) +{ + // Store the exact tick on which a player got released from a tongue. + int iVictim = GetClientOfUserId(hEvent.GetInt("victim")); + if (iVictim < 1 || iVictim > MaxClients) { + return; + } + + g_iTongueReleaseTick[iVictim] = GetGameTickCount(); +} + +void Event_OnChokeEnd(Event hEvent, const char[] eName, bool bDontBroadcast) +{ + if (!g_hTongueDamageContinuity.BoolValue) { + return; + } + + int iVictim = GetClientOfUserId(hEvent.GetInt("victim")); + if (iVictim < 1 || iVictim > MaxClients) { + return; + } + + // If tongue_release fired on the exact same tick, then it's guaranteed to be a survivor clear rather than transition. + if (g_iTongueReleaseTick[iVictim] == GetGameTickCount()) { + return; + } + + float fChokeTs = GetEntDataFloat(iVictim, g_iTongueChokeDamageTimerTimeStampOffset); + float fNow = GetGameTime(); + float fRemaining = fChokeTs - fNow; + if (fRemaining < 0.0) { + fRemaining = 0.0; + } + + SetEntDataFloat(iVictim, g_iTongueDragDamageTimerTimeStampOffset, fNow + fRemaining, false); + +#if DEBUG + PrintToChatAll("[tongue_continuity] %N choke->drag: damage clock CONTINUED - next damage in %.2fs (vanilla would have fired immediately)", \ + iVictim, fRemaining); +#endif +} + float GetFirstDamageInterval() { float fTongueFirstDamageInterval = g_hTongueDragFirstDamageInterval.FloatValue;