Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions addons/sourcemod/gamedata/l4d2_smoker_drag_damage_interval.txt
Original file line number Diff line number Diff line change
@@ -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 */
}
}
}
}
Binary file not shown.
185 changes: 173 additions & 12 deletions addons/sourcemod/scripting/l4d2_smoker_drag_damage_interval.sp
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
/**
* Version 2.4
*
* Additions:
* - Added ConVar `tongue_damage_continuity` (Default 0: OFF)
*
* When enabled the internal damage timer carries over between dragging a survivor & choking a survivor.
* In Vanilla the timer gets reset to its full duration when transitioning between states.
*
* Gamedata:
* - We now use our own gamedata as we need some additional info.
*
* Version 2.3 by A1m`
*
* Changes:
Expand All @@ -22,14 +33,14 @@

#include <sourcemod>
#include <sdkhooks>
#include <dhooks>

#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

Expand All @@ -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"
};

Expand All @@ -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];
Expand All @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
23 changes: 9 additions & 14 deletions cfg/stripper/zonemod/maps/dkr_m2_carnival.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}