Skip to content

Commit af76377

Browse files
authored
[l4d2_smoker_drag_damage_interval] Update (#919)
* [l4d2_smoker_drag_damage_interval] Upload * [l4d2_smoker_drag_damage_interval] Change configs for new version * [l4d2_smoker_drag_damage_interval] Upload * [l4d2_smoker_drag_damage_interval_zone] Delete old version * [l4d2_smoker_drag_damage_interval] Upload smx * [l4d2_smoker_drag_damage_interval_zone] Remove old smx version * [l4d2_smoker_drag_damage_interval] Fix the incorrect code for the first damage. * [l4d2_smoker_drag_damage_interval] Edit comment
1 parent 25e8f04 commit af76377

8 files changed

Lines changed: 165 additions & 201 deletions

File tree

Binary file not shown.
Binary file not shown.
Lines changed: 161 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,91 @@
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

2064
public 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

2973
public 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

4491
void 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, %sdamage: %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

Comments
 (0)