Skip to content

Commit

Permalink
Workaround for "restricted" spawns at valid positions (#712)
Browse files Browse the repository at this point in the history
* Saferoom GhostSpawn: Address restricted spawn away from safe areas

Video demonstrating the bug: https://www.bilibili.com/video/BV1P94y1k7uT

Fix:
- Replaced the patch with a safer workaround, which still has a small chance affecting ghost spawns (if being close to a checkpoint area).

* Saferoom GhostSpawn: Minor optimization
  • Loading branch information
jensewe authored Oct 8, 2023
1 parent a212c01 commit bf63cf3
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 68 deletions.
63 changes: 8 additions & 55 deletions addons/sourcemod/gamedata/l4d_fix_saferoom_ghostspawn.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,24 @@
{
"left4dead"
{
"MemPatches"
"Offsets"
{
"CTerrorPlayer::OnPreThinkGhostState__IsOverlapping_conditional_move"
"CDirector::m_bLastSurvivorLeftStartArea"
{
"signature" "CTerrorPlayer::OnPreThinkGhostState"
"linux"
{
"offset" "2F7h"
"verify" "\x89\x44\x24" // mov [esp+19Ch+var_150], eax
"patch" "\x90\x90\x90\x90"
}
"windows"
{
"offset" "15Fh"
"verify" "\x75" // jnz short loc_102AA293
// xor edi, edi
"patch" "\xEB"
}
}
}

"Signatures"
{
"CTerrorPlayer::OnPreThinkGhostState"
{
"library" "server"
"linux" "@_ZN13CTerrorPlayer20OnPreThinkGhostStateEv.part.837"
"windows" "\x55\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x8B\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x84"
/* 55 ? ? ? ? ? ? ? ? ? ? ? ? ? 8B ? ? ? ? ? ? ? ? E8 ? ? ? ? 84 */
"windows" "361"
"linux" "357"
}
}
}

"left4dead2"
{
"MemPatches"
{
"CTerrorPlayer::OnPreThinkGhostState__IsOverlapping_conditional_move"
{
"signature" "CTerrorPlayer::OnPreThinkGhostState"
"linux"
{
"offset" "2E7h"
"verify" "\x0F\x44" // cmovz esi, eax
"patch" "\x90\x90\x90"
}
"windows"
{
"offset" "525h"
"verify" "\x75" // jnz short loc_10332011
// mov dword ptr [ebp-68h], 0
// mov edi, [ebp-68h]
"patch" "\xEB"
}
}
}

"Signatures"
"Offsets"
{
"CTerrorPlayer::OnPreThinkGhostState"
"CDirector::m_bLastSurvivorLeftStartArea"
{
"library" "server"
"linux" "@_ZN13CTerrorPlayer20OnPreThinkGhostStateEv"
"windows" "\x53\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x55\x8B\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x2A\x8B\x2A\x8B\x2A\x2A\x2A\x2A\x2A\x2A\xE8\x2A\x2A\x2A\x2A\x84"
/* 53 ? ? ? ? ? ? ? ? ? ? ? 55 8B ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 8B ? 8B ? ? ? ? ? ? E8 ? ? ? ? 84 */
"windows" "353"
"linux" "353"
}
}
}
Expand Down
Binary file modified addons/sourcemod/plugins/fixes/l4d_fix_saferoom_ghostspawn.smx
Binary file not shown.
123 changes: 110 additions & 13 deletions addons/sourcemod/scripting/l4d_fix_saferoom_ghostspawn.sp
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,124 @@
#pragma newdecls required

#include <sourcemod>
#include <sourcescramble>
#include <left4dhooks>

#define PLUGIN_VERSION "1.0"
#define PLUGIN_VERSION "2.0.1"

public Plugin myinfo =
{
name = "[L4D & 2] Fix Saferoom Ghost Spawn",
author = "Forgetest",
description = "Fix a glitch that ghost can spawn in saferoom while it shouldn't.",
version = PLUGIN_VERSION,
url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins"
name = "[L4D & 2] Fix Saferoom Ghost Spawn",
author = "Forgetest",
description = "Fix a glitch that ghost can spawn in saferoom while it shouldn't.",
version = PLUGIN_VERSION,
url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins"
};

int g_iOffs_LastSurvivorLeftStartArea;
Address gpTheDirector;

public void OnPluginStart()
{
GameData gd = new GameData("l4d_fix_saferoom_ghostspawn");
if (!gd)
SetFailState("Missing gamedata \"l4d_fix_saferoom_ghostspawn\"");
GameData gd = new GameData("l4d_fix_saferoom_ghostspawn");
if (!gd)
SetFailState("Missing gamedata \"l4d_fix_saferoom_ghostspawn\"");

g_iOffs_LastSurvivorLeftStartArea = gd.GetOffset("CDirector::m_bLastSurvivorLeftStartArea");
if (g_iOffs_LastSurvivorLeftStartArea == -1)
SetFailState("Missing offset \"CDirector::m_bLastSurvivorLeftStartArea\"");

delete gd;

if (!MemoryPatch.CreateFromConf(gd, "CTerrorPlayer::OnPreThinkGhostState__IsOverlapping_conditional_move").Enable())
SetFailState("Failed to patch \"CTerrorPlayer::OnPreThinkGhostState__IsOverlapping_conditional_move\"");
LateLoad();
}

void LateLoad()
{
for (int i = 1; i <= MaxClients; ++i)
{
if (IsClientInGame(i) && GetClientTeam(i) == 3 && L4D_IsPlayerGhost(i))
L4D_OnEnterGhostState(i);
}
}

public void OnAllPluginsLoaded()
{
gpTheDirector = L4D_GetPointer(POINTER_DIRECTOR);
}

public void L4D_OnEnterGhostState(int client)
{
if (!IsClientInGame(client) || IsFakeClient(client))
return;

SDKHook(client, SDKHook_PreThinkPost, SDK_OnPreThink_Post);
}

void SDK_OnPreThink_Post(int client)
{
if (!IsClientInGame(client))
return;

if (!L4D_IsPlayerGhost(client))
{
SDKUnhook(client, SDKHook_PreThinkPost, SDK_OnPreThink_Post);
}
else
{
int spawnstate = L4D_GetPlayerGhostSpawnState(client);
if (spawnstate & L4D_SPAWNFLAG_RESTRICTEDAREA)
return;

Address area = L4D_GetLastKnownArea(client);
if (area == Address_Null)
return;

if (HasLastSurvivorLeftStartArea()) // therefore free spawn in saferoom
return;

// Some stupid maps like Blood Harvest finale and The Passing finale have CHECKPOINT inside a FINALE marked area.
int spawnattr = L4D_GetNavArea_SpawnAttributes(area);
if (~spawnattr & NAV_SPAWN_CHECKPOINT || spawnattr & NAV_SPAWN_FINALE)
return;

/**
* Game code looks like this:
*
* ```cpp
* CNavArea* area = GetLastKnownArea();
* if ( area && !area->IsOverlapping(GetAbsOrigin(), 100.0) )
* area = NULL;
* ```
*
* "area" will then be checked for in restricted area, except when it's NULL.
*/

float origin[3];
GetClientAbsOrigin(client, origin);
if (NavArea_IsOverlapping(area, origin)) // make sure it's the exact case
return;

static const float kflExtendedRange = 300.0; // adjustable, 300 units should be fair enough
if ((area = L4D_GetNearestNavArea(origin, kflExtendedRange, false, true, true, 2)) != Address_Null)
{
spawnattr = L4D_GetNavArea_SpawnAttributes(area);
if (spawnattr & NAV_SPAWN_CHECKPOINT && ~spawnattr & NAV_SPAWN_FINALE)
L4D_SetPlayerGhostSpawnState(client, spawnstate | L4D_SPAWNFLAG_RESTRICTEDAREA);
}
}
}

bool HasLastSurvivorLeftStartArea()
{
return LoadFromAddress(gpTheDirector + view_as<Address>(g_iOffs_LastSurvivorLeftStartArea), NumberType_Int8);
}

bool NavArea_IsOverlapping(Address area, const float pos[3], float tolerance = 100.0)
{
float center[3], size[3];
L4D_GetNavAreaCenter(area, center);
L4D_GetNavAreaSize(area, size);

delete gd;
return ( pos[0] + tolerance >= center[0] - size[0] * 0.5 && pos[0] - tolerance <= center[0] + size[0] * 0.5
&& pos[1] + tolerance >= center[1] - size[1] * 0.5 && pos[1] - tolerance <= center[1] + size[1] * 0.5 );
}

0 comments on commit bf63cf3

Please sign in to comment.