forked from SirPlease/L4D2-Competitive-Rework
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathl4d2_smoker_drag_damage_interval.sp
More file actions
213 lines (168 loc) · 6.4 KB
/
Copy pathl4d2_smoker_drag_damage_interval.sp
File metadata and controls
213 lines (168 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/**
* 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 <sourcemod>
#include <sdkhooks>
#define DEBUG 0
#define GAMEDATA "l4d2_si_ability"
// 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
#define TEAM_SURVIVOR 2
#define TEAM_INFECTED 3
#define EPSILON 0.001
#if DEBUG
float
g_fDebugDamageInterval = 0.0;
#endif
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, Sir, A1m`",
description = "Implements a native-like cvar that should've been there out of the box",
version = "2.3",
url = "https://github.com/SirPlease/L4D2-Competitive-Rework"
};
public void OnPluginStart()
{
InitGameData();
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()
{
Handle hGamedata = LoadGameConfigFile(GAMEDATA);
if (!hGamedata) {
SetFailState("Gamedata '%s.txt' missing or corrupt.", GAMEDATA);
}
int iTongueDragDamageTimer = GameConfGetOffset(hGamedata, "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;
}
void LateLoad()
{
for (int i = 1; i <= MaxClients; i++) {
if (!IsClientInGame(i)) {
continue;
}
OnClientPutInServer(i);
}
}
public void OnClientPutInServer(int iClient)
{
SDKHook(iClient, SDKHook_OnTakeDamage, Hook_OnTakeDamage);
}
void Event_OnTongueGrab(Event hEvent, const char[] eName, bool bDontBroadcast)
{
// 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;
}
// 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;
}
float GetFirstDamageInterval()
{
float fTongueFirstDamageInterval = g_hTongueDragFirstDamageInterval.FloatValue;
if (fTongueFirstDamageInterval > 0.0) {
return fTongueFirstDamageInterval;
}
return g_hTongueDragDamageInterval.FloatValue;
}
void SetDragDamageTimer(int iClient, float fDuration)
{
// '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'
}
#if DEBUG
void DebugPrint(int iVictim, float fDamage, bool bFirstDamage)
{
PrintToChatAll("[DEBUG] Victim: %N, %sdamage: %f, time: %f, game time: %f", \
iVictim, (bFirstDamage) ? "first " : "", fDamage, GetGameTime() - g_fDebugDamageInterval, GetGameTime());
g_fDebugDamageInterval = GetGameTime();
}
#endif