1+ /* *
2+ * Version 2.3 by A1m`
3+ *
4+ * Changes:
5+ * 1. Removed duplicate plugins:
6+ * - l4d2_smoker_drag_damage_interval_zone
7+ * - l4d2_smoker_drag_damage_interval.
8+ *
9+ * 2. Removed untrusted timer-based code:
10+ * - Replaced with safer, hook-based implementation using OnTakeDamage.
11+ * - Ensures more stable and reliable drag damage behavior without unnecessary timers.
12+ *
13+ * Notes:
14+ * The timing of damage is not perfect, as 'CTerrorPlayer::PostThink' is not called every frame,
15+ * but after a certain period of time depending on the number of players and the tick rate (see function 'CTerrorPlayer::ShouldPostThink').
16+ * For this reason, it is difficult to calculate using game netprops whether this was the first damage dealt or not,
17+ * the code becomes too complicated, so we introduce our own variables to determine this.
18+ **/
19+
120#pragma semicolon 1
221#pragma newdecls required
322
423#include <sourcemod>
24+ #include <sdkhooks>
25+
26+ #define DEBUG 0
27+ #define GAMEDATA " l4d2_si_ability"
28+
29+ // DMG_CHOKE = 1048576 = 0x100000 = (1 << 20)
30+ #define DMG_CHOKE (1 << 20 )
531
6- #define GAMEDATA " l4d2_si_ability"
32+ #define IT_TIMESTAMP_INDEX 0
33+ #define CT_DURATION_OFFSET 4
34+ #define CT_TIMESTAMP_OFFSET 8
735
8- #define DURATION_OFFSET 4
9- #define TIMESTAMP_OFFSET 8
36+ #define TEAM_SURVIVOR 2
37+ #define TEAM_INFECTED 3
1038
11- #define TEAM_SURVIVOR 2
39+ #define EPSILON 0.001
1240
13- int
14- m_tongueDragDamageTimerDuration ,
15- m_tongueDragDamageTimerTimeStamp ;
41+ #if DEBUG
42+ float
43+ g_fDebugDamageInterval = 0.0 ;
44+ #endif
1645
17- ConVar
18- tongue_drag_damage_interval ;
46+ enum
47+ {
48+ eUserId = 0 ,
49+ eHitCount ,
50+
51+ eDamageInfo_Size
52+ };
53+
54+ int
55+ g_iTongueHitCount [MAXPLAYERS + 1 ][eDamageInfo_Size ],
56+ g_iTongueDragDamageTimerDurationOffset = - 1 ,
57+ g_iTongueDragDamageTimerTimeStampOffset = - 1 ;
58+
59+ ConVar
60+ g_hTongueDragDamageInterval = null ,
61+ g_hTongueDragFirstDamageInterval = null ,
62+ g_hTongueDragFirstDamage = null ;
1963
2064public Plugin myinfo =
2165{
22- name = " L4D2 Smoker Drag Damage Interval " ,
23- author = " Visor, A1m`" ,
66+ name = " L4D2 smoker drag damage interval " ,
67+ author = " Visor, Sir, A1m`" ,
2468 description = " Implements a native-like cvar that should've been there out of the box" ,
25- version = " 0.7 " ,
69+ version = " 2.3 " ,
2670 url = " https://github.com/SirPlease/L4D2-Competitive-Rework"
2771};
2872
2973public void OnPluginStart ()
3074{
3175 InitGameData ();
32- HookEvent (" tongue_grab" , OnTongueGrab );
33-
34- char value [32 ];
35- ConVar tongue_choke_damage_interval = FindConVar (" tongue_choke_damage_interval" );
36- tongue_choke_damage_interval .GetString (value , sizeof (value ));
37-
38- tongue_drag_damage_interval = CreateConVar (" tongue_drag_damage_interval" , value , " How often the drag does damage." );
39-
40- ConVar tongue_choke_damage_amount = FindConVar (" tongue_choke_damage_amount" );
41- tongue_choke_damage_amount .AddChangeHook (tongue_choke_damage_amount_ValueChanged );
76+
77+ HookEvent (" tongue_grab" , Event_OnTongueGrab );
78+
79+ // Get the default value of cvar 'tongue_choke_damage_interval'
80+ char sCvarVal [32 ];
81+ ConVar hTongueChokeDamageInterval = FindConVar (" tongue_choke_damage_interval" );
82+ hTongueChokeDamageInterval .GetDefault (sCvarVal , sizeof (sCvarVal ));
83+
84+ 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 );
85+ 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 );
86+ 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 );
87+
88+ LateLoad ();
4289}
4390
4491void InitGameData ()
@@ -48,59 +95,119 @@ void InitGameData()
4895 if (! hGamedata ) {
4996 SetFailState (" Gamedata '%s .txt' missing or corrupt." , GAMEDATA );
5097 }
51-
52- int m_tongueDragDamageTimer = GameConfGetOffset (hGamedata , " CTerrorPlayer->m_tongueDragDamageTimer" );
53- if (m_tongueDragDamageTimer == - 1 ) {
98+
99+ int iTongueDragDamageTimer = GameConfGetOffset (hGamedata , " CTerrorPlayer->m_tongueDragDamageTimer" );
100+ if (iTongueDragDamageTimer == - 1 ) {
54101 SetFailState (" Failed to get offset 'CTerrorPlayer->m_tongueDragDamageTimer'." );
55102 }
56-
57- m_tongueDragDamageTimerDuration = m_tongueDragDamageTimer + DURATION_OFFSET ;
58- m_tongueDragDamageTimerTimeStamp = m_tongueDragDamageTimer + TIMESTAMP_OFFSET ;
59-
103+
104+ g_iTongueDragDamageTimerDurationOffset = iTongueDragDamageTimer + CT_DURATION_OFFSET ;
105+ g_iTongueDragDamageTimerTimeStampOffset = iTongueDragDamageTimer + CT_TIMESTAMP_OFFSET ;
106+
60107 delete hGamedata ;
61108}
62109
63- void tongue_choke_damage_amount_ValueChanged ( ConVar convar , const char [] oldValue , const char [] newValue )
110+ void LateLoad ( )
64111{
65- convar .SetInt (1 ); // hack-hack: game tries to change this cvar for some reason, can't be arsed so HARDCODETHATSHIT
112+ for (int i = 1 ; i <= MaxClients ; i ++ ) {
113+ if (! IsClientInGame (i )) {
114+ continue ;
115+ }
116+
117+ OnClientPutInServer (i );
118+ }
66119}
67120
68- void OnTongueGrab ( Event hEvent , const char [] eName , bool dontBroadcast )
121+ public void OnClientPutInServer ( int iClient )
69122{
70- int userid = hEvent .GetInt (" victim" );
71- int client = GetClientOfUserId (userid );
72-
73- SetDragDamageInterval (client );
74-
75- float fTimerUpdate = tongue_drag_damage_interval .FloatValue + 0.1 ;
76- CreateTimer (fTimerUpdate , FixDragInterval , userid , TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE );
123+ SDKHook (iClient , SDKHook_OnTakeDamage , Hook_OnTakeDamage );
77124}
78125
79- Action FixDragInterval ( Handle hTimer , any userid )
126+ void Event_OnTongueGrab ( Event hEvent , const char [] eName , bool bDontBroadcast )
80127{
81- int client = GetClientOfUserId (userid );
82- if (client > 0 && GetClientTeam (client ) == TEAM_SURVIVOR && IsSurvivorBeingDragged (client )) {
83- SetDragDamageInterval (client );
128+ // Replacing variable value 'CTerrorPlayer::m_tongueDragDamageTimer',
129+ // after calling a function 'CTerrorPlayer::OnGrabbedByTongue'.
130+ // Fix damage interval.
131+
132+ int iVictim = GetClientOfUserId (hEvent .GetInt (" victim" ));
133+ bool bIsHangingFromTongue = (GetEntProp (iVictim , Prop_Send , " m_isHangingFromTongue" , 1 ) > 0 );
134+
135+ if (! bIsHangingFromTongue ) { // Dragging?
136+ SetDragDamageTimer (iVictim , GetFirstDamageInterval ());
137+ }
138+
139+ g_iTongueHitCount [iVictim ][eUserId ] = hEvent .GetInt (" victim" );
140+ g_iTongueHitCount [iVictim ][eHitCount ] = 0 ;
141+
142+ #if DEBUG
143+ g_fDebugDamageInterval = GetGameTime ();
144+ #endif
145+ }
146+
147+ Action Hook_OnTakeDamage (int iVictim , int &iAttacker , int &iInflictor , float &fDamage , int &iDamageType )
148+ {
149+ // Replacing the function patch 'CTerrorPlayer::UpdateHangingFromTongue'.
150+ // This dmg function is called after variable 'CTerrorPlayer::m_tongueDragDamageTimer' is set, we can't get it here.
151+ if (! (iDamageType & DMG_CHOKE )) {
152+ return Plugin_Continue ;
153+ }
154+
155+ int iTongueOwner = GetEntPropEnt (iVictim , Prop_Send , " m_tongueOwner" );
156+ if (iTongueOwner < 1 || iTongueOwner > MaxClients || iTongueOwner != iAttacker ) {
157+ return Plugin_Continue ;
158+ }
159+
160+ // Stop dragging.
161+ if (GetEntProp (iVictim , Prop_Send , " m_isHangingFromTongue" , 1 ) > 0 ) {
84162 return Plugin_Continue ;
85163 }
86- return Plugin_Stop ;
164+
165+ // Fix damage interval.
166+ SetDragDamageTimer (iVictim , g_hTongueDragDamageInterval .FloatValue );
167+
168+ // First damage if cvar enabled.
169+ g_iTongueHitCount [iVictim ][eHitCount ]++ ;
170+ bool bFirstDamage = false ;
171+
172+ if (g_hTongueDragFirstDamage .FloatValue > 0.0 ) {
173+ if (g_iTongueHitCount [iVictim ][eHitCount ] == 1 && g_iTongueHitCount [iVictim ][eUserId ] == GetClientUserId (iVictim )) {
174+ fDamage = g_hTongueDragFirstDamage .FloatValue ;
175+ bFirstDamage = true ;
176+ }
177+ }
178+
179+ #if DEBUG
180+ DebugPrint (iVictim , fDamage , bFirstDamage );
181+ #endif
182+
183+ return (bFirstDamage ) ? Plugin_Changed : Plugin_Continue ;
87184}
88185
89- void SetDragDamageInterval ( int client )
186+ float GetFirstDamageInterval ( )
90187{
91- float fCvarValue = tongue_drag_damage_interval .FloatValue ;
92- float fTimeStamp = GetGameTime () + fCvarValue ;
93-
94- SetEntDataFloat (client , m_tongueDragDamageTimerDuration , fCvarValue ); //duration
95- SetEntDataFloat (client , m_tongueDragDamageTimerTimeStamp , fTimeStamp ); //timestamp
188+ float fTongueFirstDamageInterval = g_hTongueDragFirstDamageInterval .FloatValue ;
189+ if (fTongueFirstDamageInterval > 0.0 ) {
190+ return fTongueFirstDamageInterval ;
191+ }
192+
193+ return g_hTongueDragDamageInterval .FloatValue ;
96194}
97195
98- bool IsSurvivorBeingDragged (int client )
196+ void SetDragDamageTimer (int iClient , float fDuration )
99197{
100- return ((GetEntPropEnt (client , Prop_Send , " m_tongueOwner" ) > 0 ) && ! IsSurvivorBeingChoked (client ));
198+ // 'CTerrorPlayer::m_tongueDragDamageTimer', this is not netprop
199+ float fTimeStamp = GetGameTime () + fDuration ;
200+
201+ SetEntDataFloat (iClient , g_iTongueDragDamageTimerDurationOffset , fDuration , false ); // 'CountdownTimer::duration'
202+ SetEntDataFloat (iClient , g_iTongueDragDamageTimerTimeStampOffset , fTimeStamp , false ); // 'CountdownTimer::timestamp'
101203}
102204
103- bool IsSurvivorBeingChoked (int client )
205+ #if DEBUG
206+ void DebugPrint (int iVictim , float fDamage , bool bFirstDamage )
104207{
105- return (GetEntProp (client , Prop_Send , " m_isHangingFromTongue" ) > 0 );
208+ PrintToChatAll (" [DEBUG] Victim: %N , %s damage: %f , time: %f , game time: %f " , \
209+ iVictim , (bFirstDamage ) ? " first " : " " , fDamage , GetGameTime () - g_fDebugDamageInterval , GetGameTime ());
210+
211+ g_fDebugDamageInterval = GetGameTime ();
106212}
213+ #endif
0 commit comments