forked from gkistler/SourcemodStuff
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtankdamage.sp
More file actions
354 lines (299 loc) · 11.4 KB
/
tankdamage.sp
File metadata and controls
354 lines (299 loc) · 11.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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
/**
* =============================================================================
* Tank Damage Announce
* Track and display damage dealt to a tank by survivors, strongly based on Mr. Zero's Rotoblin tank management plugin.
*
* Known Issues:
* No support for multiple tanks.
*
* - Griffin
* =============================================================================
*/
#pragma semicolon 1
#include <sourcemod>
#define TEAM_SURVIVOR 2
#define TEAM_INFECTED 3
new g_iZombieClass_Tank = 5; // Zombie class of tank for finding tank on pass, L4D1=5 L4D2=8
new g_iOffset_Incapacitated = 0; // Used to check if tank is dying
new g_iTankClient = 0; // Which client is currently playing as tank
new g_iLastTankHealth = 0; // Used to award the killing blow the exact right amount of damage
new g_iSurvivorLimit = 4; // For survivor array in damage print
new g_iDamage[MAXPLAYERS + 1];
new bool: g_bEnabled = true;
new bool: g_bAnnounceTankDamage = false; // Whether or not tank damage should be announced
new bool: g_bIsTankInPlay = false; // Whether or not the tank is active
new Float: g_fMaxTankHealth = 6000.0;
new Handle: g_hCvarEnabled = INVALID_HANDLE;
new Handle: g_hCvarTankHealth = INVALID_HANDLE;
new Handle: g_hCvarTankBonusHealth = INVALID_HANDLE;
new Handle: g_hCvarSurvivorLimit = INVALID_HANDLE;
public Plugin:myinfo =
{
name = "Tank Damage Announce",
author = "Griffin",
description = "Announce damage dealt to tanks by survivors",
version = "0.6.3"
};
public OnPluginStart()
{
decl String:buffer[64];
GetGameFolderName(buffer, sizeof(buffer));
if (StrEqual(buffer, "left4dead2", false))
{
g_iZombieClass_Tank = 8;
}
g_bIsTankInPlay = false;
g_bAnnounceTankDamage = false;
g_iTankClient = 0;
ClearTankDamage();
HookEvent("tank_spawn", Event_TankSpawn);
HookEvent("player_death", Event_PlayerKilled);
HookEvent("round_start", Event_RoundStart);
HookEvent("round_end", Event_RoundEnd);
HookEvent("player_hurt", Event_PlayerHurt);
g_hCvarEnabled = CreateConVar("l4d_tankdamage_enabled", "1", "Announce damage done to tanks when enabled", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_hCvarSurvivorLimit = FindConVar("survivor_limit");
g_hCvarTankHealth = FindConVar("z_tank_health");
g_hCvarTankBonusHealth = FindConVar("versus_tank_bonus_health");
HookConVarChange(g_hCvarEnabled, Cvar_Enabled);
HookConVarChange(g_hCvarSurvivorLimit, Cvar_SurvivorLimit);
HookConVarChange(g_hCvarTankHealth, Cvar_TankHealth);
HookConVarChange(g_hCvarTankBonusHealth, Cvar_TankHealth);
g_bEnabled = GetConVarBool(g_hCvarEnabled);
CalculateTankHealth();
g_iOffset_Incapacitated = FindSendPropInfo("Tank", "m_isIncapacitated");
}
public OnMapStart()
{
// In cases where a tank spawns and map is changed manually, bypassing round end
ClearTankDamage();
}
public OnClientDisconnect_Post(client)
{
if (!g_bIsTankInPlay || client != g_iTankClient) return;
CreateTimer(0.5, Timer_CheckTank, client); // Use a delayed timer due to bugs where the tank passes to another player
}
public Cvar_Enabled(Handle:convar, const String:oldValue[], const String:newValue[])
{
g_bEnabled = StringToInt(newValue) > 0 ? true:false;
}
public Cvar_SurvivorLimit(Handle:convar, const String:oldValue[], const String:newValue[])
{
g_iSurvivorLimit = StringToInt(newValue);
}
public Cvar_TankHealth(Handle:convar, const String:oldValue[], const String:newValue[])
{
CalculateTankHealth();
}
CalculateTankHealth()
{
g_fMaxTankHealth = FloatMul(GetConVarFloat(g_hCvarTankHealth), GetConVarFloat(g_hCvarTankBonusHealth));
if (g_fMaxTankHealth <= 0.0) g_fMaxTankHealth = 1.0; // No dividing by 0!
}
public Event_PlayerHurt(Handle:event, const String:name[], bool:dontBroadcast)
{
if (!g_bIsTankInPlay) return; // No tank in play; no damage to record
new victim = GetClientOfUserId(GetEventInt(event, "userid"));
if (victim != GetTankClient() || // Victim isn't tank; no damage to record
IsTankDying() // Something buggy happens when tank is dying with regards to damage
) return;
new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
// We only care about damage dealt by survivors, though it can be funny to see
// claw/self inflicted hittable damage, so maybe in the future we'll do that
if (attacker == 0 || // Damage from world?
!IsClientInGame(attacker) || // Not sure if this happens
GetClientTeam(attacker) != TEAM_SURVIVOR
) return;
g_iDamage[attacker] += GetEventInt(event, "dmg_health");
g_iLastTankHealth = GetEventInt(event, "health");
}
public Event_PlayerKilled(Handle:event, const String:name[], bool:dontBroadcast)
{
if (!g_bIsTankInPlay) return; // No tank in play; no damage to record
new victim = GetClientOfUserId(GetEventInt(event, "userid"));
if (victim != g_iTankClient) return;
// Award the killing blow's damage to the attacker; we don't award
// damage from player_hurt after the tank has died/is dying
// If we don't do it this way, we get wonky/inaccurate damage values
new attacker = GetClientOfUserId(GetEventInt(event, "attacker"));
if (attacker && IsClientInGame(attacker)) g_iDamage[attacker] += g_iLastTankHealth;
// Damage announce could probably happen right here...
CreateTimer(0.5, Timer_CheckTank, victim); // Use a delayed timer due to bugs where the tank passes to another player
}
public Event_TankSpawn(Handle:event, const String:name[], bool:dontBroadcast)
{
new client = GetClientOfUserId(GetEventInt(event, "userid"));
g_iTankClient = client;
if (g_bIsTankInPlay) return; // Tank passed
// New tank, damage has not been announced
g_bAnnounceTankDamage = true;
g_bIsTankInPlay = true;
// Set health for damage print in case it doesn't get set by player_hurt (aka no one shoots the tank)
g_iLastTankHealth = GetClientHealth(client);
}
public Event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{
g_bIsTankInPlay = false;
g_iTankClient = 0;
ClearTankDamage(); // Probably redundant
}
// When survivors wipe or juke tank, announce damage
public Event_RoundEnd(Handle:event, const String:name[], bool:dontBroadcast)
{
// But only if a tank that hasn't been killed exists
if (g_bAnnounceTankDamage)
{
PrintRemainingHealth();
PrintTankDamage();
}
ClearTankDamage();
}
public Action:Timer_CheckTank(Handle:timer, any:oldtankclient)
{
if (g_iTankClient != oldtankclient) return; // Tank passed
new tankclient = FindTankClient();
if (tankclient && tankclient != oldtankclient)
{
g_iTankClient = tankclient;
return; // Found tank, done
}
if (g_bAnnounceTankDamage) PrintTankDamage(true /*show tank's name*/);
ClearTankDamage();
g_bIsTankInPlay = false; // No tank in play
}
bool:IsTankDying()
{
new tankclient = GetTankClient();
if (!tankclient) return false;
return bool:GetEntData(tankclient, g_iOffset_Incapacitated);
}
PrintRemainingHealth()
{
if (!g_bEnabled) return;
new tankclient = GetTankClient();
if (!tankclient) return;
if (IsFakeClient(tankclient))
{
PrintToChatAll("\x01[SM] \x03AI\x01 Tank had \x04%d \x01health remaining", g_iLastTankHealth);
}
else
{
PrintToChatAll("\x01[SM] Tank (\x03%N\x01) had \x04%d \x01health remaining", tankclient, g_iLastTankHealth);
}
}
PrintTankDamage(bool:show_tank_name=false)
{
if (!g_bEnabled) return;
// g_iTankClient never gets unset, so it contains the last person who was tank
if (show_tank_name && IsClientInGame(g_iTankClient))
{
if (IsFakeClient(g_iTankClient))
{
PrintToChatAll("\x01[SM] Damage dealt to \x03AI\x01 tank:");
}
else
{
PrintToChatAll("\x01[SM] Damage dealt to tank (\x03%N\x01):", g_iTankClient);
}
}
else
{
PrintToChatAll("\x01[SM] Damage dealt to tank:", g_iTankClient);
}
new client;
new percent_total; // Accumulated total of calculated percents, for fudging out numbers at the end
new damage_total; // Accumulated total damage dealt by survivors, to see if we need to fudge upwards to 100%
new survivor_count = 0;
decl survivor_clients[g_iSurvivorLimit]; // Array to store survivor client indices in, for the display iteration
decl percent_damage, damage;
for (client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) || GetClientTeam(client) != TEAM_SURVIVOR) continue;
survivor_clients[survivor_count] = client;
survivor_count++;
damage = g_iDamage[client];
damage_total += damage;
percent_damage = GetDamageAsPercent(damage);
percent_total += percent_damage;
}
SortCustom1D(survivor_clients, survivor_count, SortByDamageDesc);
new percent_adjustment;
// Percents add up to less than 100% AND > 99.5% damage was dealt to tank
if ((percent_total < 100 &&
float(damage_total) > (g_fMaxTankHealth - (g_fMaxTankHealth / 200.0)))
)
{
percent_adjustment = 100 - percent_total;
}
new last_percent = 100; // Used to store the last percent in iteration to make sure an adjusted percent doesn't exceed the previous percent
decl adjusted_percent_damage;
for (new i; i < survivor_count; i++)
{
client = survivor_clients[i];
damage = g_iDamage[client];
percent_damage = GetDamageAsPercent(damage);
// Attempt to adjust the top damager's percent, defer adjustment to next player if it's an exact percent
// e.g. 3000 damage on 6k health tank shouldn't be adjusted
if (percent_adjustment != 0 && // Is there percent to adjust
damage > 0 && // Is damage dealt > 0%
!IsExactPercent(damage) // Percent representation is not exact, e.g. 3000 damage on 6k tank = 50%
)
{
adjusted_percent_damage = percent_damage + percent_adjustment;
if (adjusted_percent_damage <= last_percent) // Make sure adjusted percent is not higher than previous percent, order must be maintained
{
percent_damage = adjusted_percent_damage;
percent_adjustment = 0;
}
}
PrintToChatAll("\x03%N\x01: %4d \x01[\x04%d%%\x01]", client, damage, percent_damage);
}
}
ClearTankDamage()
{
g_iLastTankHealth = 0;
for (new i = 1; i <= MaxClients; i++) { g_iDamage[i] = 0; }
g_bAnnounceTankDamage = false;
}
GetTankClient()
{
if (!g_bIsTankInPlay) return 0;
new tankclient = g_iTankClient;
if (!IsClientInGame(tankclient)) // If tank somehow is no longer in the game (kicked, hence events didn't fire)
{
tankclient = FindTankClient(); // find the tank client
if (!tankclient) return 0;
g_iTankClient = tankclient;
}
return tankclient;
}
FindTankClient()
{
for (new client = 1; client <= MaxClients; client++)
{
if (!IsClientInGame(client) ||
GetClientTeam(client) != TEAM_INFECTED ||
!IsPlayerAlive(client) ||
GetEntProp(client, Prop_Send, "m_zombieClass") != g_iZombieClass_Tank)
continue;
return client; // Found tank, return
}
return 0;
}
GetDamageAsPercent(damage)
{
return RoundToFloor(FloatMul(FloatDiv(float(damage), g_fMaxTankHealth), 100.0));
}
bool:IsExactPercent(damage)
{
return (FloatAbs(float(GetDamageAsPercent(damage)) - FloatMul(FloatDiv(float(damage), g_fMaxTankHealth), 100.0)) < 0.001) ? true:false;
}
public SortByDamageDesc(elem1, elem2, const array[], Handle:hndl)
{
// By damage, then by client index, descending
if (g_iDamage[elem1] > g_iDamage[elem2]) return -1;
else if (g_iDamage[elem2] > g_iDamage[elem1]) return 1;
else if (elem1 > elem2) return -1;
else if (elem2 > elem1) return 1;
return 0;
}