From f9f177ad094c15fbb117eac732a4f3432b703a12 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:21:20 +0800 Subject: [PATCH 1/2] Update l4d_consistent_escaperoute --- .../gamedata/l4d_consistent_escaperoute.txt | 170 +++++--- .../scripting/l4d_consistent_escaperoute.sp | 364 +++++++----------- 2 files changed, 240 insertions(+), 294 deletions(-) diff --git a/addons/sourcemod/gamedata/l4d_consistent_escaperoute.txt b/addons/sourcemod/gamedata/l4d_consistent_escaperoute.txt index d1d4d218b..123e28f90 100644 --- a/addons/sourcemod/gamedata/l4d_consistent_escaperoute.txt +++ b/addons/sourcemod/gamedata/l4d_consistent_escaperoute.txt @@ -1,7 +1,70 @@ "Games" { + "#default" + { + "Functions" + { + "l4d_consistent_escaperoute::TerrorNavMesh::ComputeFlowDistances" + { + "signature" "TerrorNavMesh::ComputeFlowDistances" + "callconv" "thiscall" + "return" "int" + "this" "ignore" + } + + // GetPlayerSpawnPosition(SurvivorCharacterType,Vector *,QAngle *,TerrorNavArea **) + "l4d_consistent_escaperoute::GetPlayerSpawnPosition" + { + "signature" "GetPlayerSpawnPosition" + "callconv" "cdecl" + "return" "bool" + "arguments" + { + "SurvivorCharacterType" + { + "type" "int" + } + "Vector *" + { + "type" "vectorptr" + } + "QAngle *" + { + "type" "vectorptr" + } + "TerrorNavArea **" + { + "type" "int" + } + } + } + } + } + "left4dead" { + "MemPatches" + { + "skip_spawn_position_idx" + { + "signature" "GetPlayerSpawnPosition" + "linux" + { + "offset" "356h" + "verify" "\x8B\x8B\x2A\x2A\x2A\x2A\x8D\x51\x01\x39\xC2\x0F\x8D\x2A\x2A\x2A\x2A\x89\x93\x2A\x2A\x2A\x2A\x01\xD2\x01\xD2" + "patch" "\x31\xD2\xEB\x17" // xor edx, edx + // jmp next + } + "windows" + { + "offset" "1E5h" + "verify" "\xA1\x2A\x2A\x2A\x2A\x83\xC0\x01\x3B\xC5\xA3\x2A\x2A\x2A\x2A\x7C\x07\x33\xC0\xA3\x2A\x2A\x2A\x2A" + "patch" "\x31\xC0\xEB\x14" // xor eax, eax + // jmp next + } + } + } + "Offsets" { "CEscapeRoute::m_nMainPathAreaCount" @@ -10,51 +73,57 @@ "windows" "7016" } - "TerrorNavArea::m_flowToGoal" - { - "linux" "348" - "windows" "340" - } - - "TerrorNavMesh::m_flMapMaxFlowDistance" + "CNavArea::m_id" { - "windows" "1444" - "linux" "1444" + "linux" "136" + "windows" "136" } } "Signatures" { - // Search string "ERROR: BuildEscapeRoute failed. The start or end area was NULL\n" - // "CEscapeRoute::ResetPath", "CEscapeRoute::AddArea" and "CEscapeRoute::FinishPath" can be found there. - "CEscapeRoute::ResetPath" - { - "library" "server" - "linux" "@_ZN12CEscapeRoute9ResetPathEv" - "windows" "\xD9\xEE\x33\xC0\xD9\x99" - /* D9 EE 33 C0 D9 99 */ - } - - "CEscapeRoute::AddArea" + "TerrorNavMesh::ComputeFlowDistances" { "library" "server" - "linux" "@_ZN12CEscapeRoute7AddAreaEP13TerrorNavArea" - "windows" "\x55\x8B\xEC\x83\xE4\xF0\x83\xEC\x28\x56\x8B\xF1\x8B\x86\x2A\x2A\x2A\x2A\x3D" - /* 55 8B EC 83 E4 F0 83 EC 28 56 8B F1 8B 86 ? ? ? ? 3D */ + "linux" "@_ZN13TerrorNavMesh20ComputeFlowDistancesEv" + "windows" "\x83\xEC\x3C\x53\x55\x56\x57\x6A\x04\x33\xFF" + /* 83 EC 3C 53 55 56 57 6A 04 33 FF */ } - - "CEscapeRoute::FinishPath" + + "GetPlayerSpawnPosition" { "library" "server" - "linux" "@_ZN12CEscapeRoute10FinishPathEv" - "windows" "\x8B\x81\x2A\x2A\x2A\x2A\x3D\x00\x02\x00\x00\x7C\x2A\x68" - /* 8B 81 ? ? ? ? 3D 00 02 00 00 7C ? 68 */ + "linux" "@_Z22GetPlayerSpawnPosition21SurvivorCharacterTypeP6VectorP6QAnglePP13TerrorNavArea" + "windows" "\x83\xEC\x34\x8B\x0D\x2A\x2A\x2A\x2A\x53" + /* 83 EC 34 8B 0D ? ? ? ? 53 */ } } } "left4dead2" { + "MemPatches" + { + "skip_spawn_position_idx" + { + "signature" "GetPlayerSpawnPosition" + "linux" + { + "offset" "41Ch" + "verify" "\x8B\x0D\x2A\x2A\x2A\x2A\x8D\x51\x01\x39\xC2\x0F\x8D\x2A\x2A\x2A\x2A\x89\x15\x2A\x2A\x2A\x2A\xC1\xE2\x02" + "patch" "\x31\xD2\xEB\x16" // xor edx, edx + // jmp next + } + "windows" + { + "offset" "2A8h" + "verify" "\xA1\x2A\x2A\x2A\x2A\x40\xA3\x2A\x2A\x2A\x2A\x3B\xC3\x7C\x07\x33\xC0\xA3\x2A\x2A\x2A\x2A" + "patch" "\x31\xC0\xEB\x12" // xor eax, eax + // jmp next + } + } + } + "Offsets" { "CEscapeRoute::m_nMainPathAreaCount" @@ -62,47 +131,30 @@ "linux" "7232" "windows" "7212" } - - "TerrorNavArea::m_flowToGoal" + + "CNavArea::m_id" { - "linux" "340" - "windows" "332" - } - - "TerrorNavMesh::m_flMapMaxFlowDistance" - { - "windows" "1528" - "linux" "1524" - // Compared against inside of the current_flow_distance ccommand. + "linux" "140" + "windows" "140" } } - + "Signatures" { - // Search string "ERROR: BuildEscapeRoute failed. No path from start to end. zeroFlowArea = #%d, endArea = #%d.\n" - // "CEscapeRoute::ResetPath", "CEscapeRoute::AddArea" and "CEscapeRoute::FinishPath" can be found there. - "CEscapeRoute::ResetPath" - { - "library" "server" - "linux" "@_ZN12CEscapeRoute9ResetPathEv" - "windows" "\x0F\x57\xC0\x33\xC0\x89\x81\x2A\x2A\x2A\x2A\x89\x81\x2A\x2A\x2A\x2A\xF3\x0F" - /* 0F 57 C0 33 C0 89 81 ? ? ? ? 89 81 ? ? ? ? F3 0F */ - } - - "CEscapeRoute::AddArea" + "TerrorNavMesh::ComputeFlowDistances" { "library" "server" - "linux" "@_ZN12CEscapeRoute7AddAreaEP13TerrorNavArea" - "windows" "\x53\x8B\xDC\x83\xEC\x08\x83\xE4\xF0\x83\xC4\x04\x55\x8B\x6B\x04\x89\x6C\x24\x04\x8B\xEC\x83\xEC\x28\x56\x8B\xF1\x8B\x86\x2A\x2A\x2A\x2A\x57\x3D\x00\x02\x00\x00\x7C" - /* 53 8B DC 83 EC 08 83 E4 F0 83 C4 04 55 8B 6B 04 89 6C 24 04 8B EC 83 EC 28 56 8B F1 8B 86 ? ? ? ? 57 3D 00 02 00 00 7C */ + "linux" "@_ZN13TerrorNavMesh20ComputeFlowDistancesEv" + "windows" "\x55\x8B\xEC\x81\xEC\x88\x00\x00\x00\xA1\x2A\x2A\x2A\x2A\x53" + /* 55 8B EC 81 EC 88 00 00 00 A1 ? ? ? ? 53 */ } - - "CEscapeRoute::FinishPath" + + "GetPlayerSpawnPosition" { "library" "server" - "linux" "@_ZN12CEscapeRoute10FinishPathEv" - "windows" "\x8B\x81\x2A\x2A\x2A\x2A\x3D\x00\x02\x00\x00\x7C\x2A\x68" - /* 8B 81 ? ? ? ? 3D 00 02 00 00 7C ? 68 */ + "linux" "@_Z22GetPlayerSpawnPosition21SurvivorCharacterTypeP6VectorP6QAnglePP13TerrorNavArea" + "windows" "\x55\x8B\xEC\x83\xEC\x4C\x53\x33\xDB" + /* 55 8B EC 83 EC 4C 53 33 DB */ } } } diff --git a/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp b/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp index eb1dd3437..106d88b5b 100644 --- a/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp +++ b/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp @@ -1,11 +1,18 @@ #pragma semicolon 1 #pragma newdecls required +#define DEBUG 1 +#define PLUGIN_VERSION "2.0" +#define PLUGIN_TAG "l4d_consistent_escaperoute" + #include -#include -#include +#include +#include +#include <@Forgetest/gamedatawrapper> -#define PLUGIN_VERSION "1.1.3" +#if DEBUG +#include +#endif public Plugin myinfo = { @@ -16,296 +23,183 @@ public Plugin myinfo = url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins" }; -enum struct SDKCallParamsWrapper { - SDKType type; - SDKPassMethod pass; - int decflags; - int encflags; -} - -methodmap GameDataWrapper < GameData { - public GameDataWrapper(const char[] file) { - GameData gd = new GameData(file); - if (!gd) SetFailState("Missing gamedata \"%s\"", file); - return view_as(gd); - } - property GameData Super { - public get() { return view_as(this); } - } - public int GetOffset(const char[] key) { - int offset = this.Super.GetOffset(key); - if (offset == -1) SetFailState("Missing offset \"%s\"", key); - return offset; - } - public Address GetAddress(const char[] key) { - Address ptr = this.Super.GetAddress(key); - if (ptr == Address_Null) SetFailState("Missing address \"%s\"", key); - return ptr; - } - public Handle CreateSDKCallOrFail( - SDKCallType type, - SDKFuncConfSource src, - const char[] name, - const SDKCallParamsWrapper[] params = {}, - int numParams = 0, - bool hasReturnValue = false, - const SDKCallParamsWrapper ret = {}) { - static const char k_sSDKFuncConfSource[SDKFuncConfSource][] = { "offset", "signature", "address" }; - Handle result; - StartPrepSDKCall(type); - if (!PrepSDKCall_SetFromConf(this, src, name)) - SetFailState("Missing %s \"%s\"", k_sSDKFuncConfSource[src], name); - for (int i = 0; i < numParams; ++i) - PrepSDKCall_AddParameter(params[i].type, params[i].pass, params[i].decflags, params[i].encflags); - if (hasReturnValue) - PrepSDKCall_SetReturnInfo(ret.type, ret.pass, ret.decflags, ret.encflags); - if (!(result = EndPrepSDKCall())) - SetFailState("Failed to prep sdkcall \"%s\"", name); - return result; - } -} - -int g_iOffs_m_nMainPathAreaCount, g_iOffs_m_flowToGoal, g_iOffs_m_flMapMaxFlowDistance; -Handle g_hSDKCall_ResetPath, g_hSDKCall_AddArea, g_hSDKCall_FinishPath; +methodmap Address {} -methodmap CEscapeRoute +#if DEBUG +int g_iOffs_m_nMainPathAreaCount; +methodmap CEscapeRoute < Address { public CEscapeRoute(int entity) { - return view_as(entity); - } - - public void ResetPath() { - SDKCall(g_hSDKCall_ResetPath, this); - } - - public void AddArea(TerrorNavArea area) { - SDKCall(g_hSDKCall_AddArea, this, area); - } - - public void FinishPath() { - SDKCall(g_hSDKCall_FinishPath, this); + return view_as(GetEntityAddress(entity)); } public TerrorNavArea GetMainPathArea(int index) { - return LoadFromAddress(GetEntityAddress(view_as(this)) + return LoadFromAddress(this + view_as
(g_iOffs_m_nMainPathAreaCount + 8) + view_as
(index * 4), NumberType_Int32); } property int m_nMainPathAreaCount { - public get() { return LoadFromAddress(GetEntityAddress(view_as(this)) + public get() { return LoadFromAddress(this + view_as
(g_iOffs_m_nMainPathAreaCount), NumberType_Int32); } } } +bool g_bEscapeRouteChecked = false; +ArrayList g_SavedEscapeRouteAreas; -methodmap CUtlVector +int g_offs_m_id; +methodmap TerrorNavArea < Address { - public int Size() { - return LoadFromAddress(view_as
(this) + view_as
(12), NumberType_Int32); - } - - property Address m_pElements { - public get() { return LoadFromAddress(view_as
(this), NumberType_Int32); } + public int GetID() { + return LoadFromAddress(this + view_as
(g_offs_m_id), NumberType_Int32); } } +#endif + +MemoryPatch g_patch_SkipSpawnPosIdx; -methodmap NavAreaVector < CUtlVector +public void OnPluginStart() { - public NavAreaVector(Address addr) { - return view_as(addr); - } + GameDataWrapper gd = new GameDataWrapper("l4d_consistent_escaperoute"); + +#if DEBUG + g_iOffs_m_nMainPathAreaCount = gd.GetOffset("CEscapeRoute::m_nMainPathAreaCount"); + g_offs_m_id = gd.GetOffset("CNavArea::m_id"); +#endif + + g_patch_SkipSpawnPosIdx = gd.CreatePatchOrFail("skip_spawn_position_idx", false); + + delete gd.CreateDetourOrFail("l4d_consistent_escaperoute::GetPlayerSpawnPosition", DTR_GetPlayerSpawnPosition, DTR_GetPlayerSpawnPosition_Post); + delete gd.CreateDetourOrFail("l4d_consistent_escaperoute::TerrorNavMesh::ComputeFlowDistances", DTR_ComputeFlowDistances, DTR_ComputeFlowDistances_Post); + delete gd; - public TerrorNavArea At(int index) { - return LoadFromAddress(this.m_pElements + view_as
(index * 4), NumberType_Int32); - } +#if DEBUG + g_SavedEscapeRouteAreas = new ArrayList(); + + HookEvent("round_start", Event_round_start); +#endif } -methodmap TerrorNavArea +public void OnMapEnd() { - public TerrorNavArea(Address addr) { - return view_as(addr); - } - - public int GetID() { - return L4D_GetNavAreaID(view_as
(this)); - } - - public void RemoveSpawnAttributes(int flag) { - int spawnAttributes = L4D_GetNavArea_SpawnAttributes(view_as
(this)); - L4D_SetNavArea_SpawnAttributes(view_as
(this), spawnAttributes & ~flag); - } - - property float m_flowToGoal { - public get() { return LoadFromAddress(view_as
(this) + view_as
(g_iOffs_m_flowToGoal), NumberType_Int32); } - public set(float flow) { StoreToAddress(view_as
(this) + view_as
(g_iOffs_m_flowToGoal), flow, NumberType_Int32); } - } - - property float m_flowFromStart { - public get() { return LoadFromAddress(view_as
(this) + view_as
(g_iOffs_m_flowToGoal + 4), NumberType_Int32); } - public set(float flow) { StoreToAddress(view_as
(this) + view_as
(g_iOffs_m_flowToGoal + 4), flow, NumberType_Int32); } - } +#if DEBUG + g_SavedEscapeRouteAreas.Clear(); +#endif } -methodmap TerrorNavMesh +static bool g_bInComputeFlowDistances = false; +MRESReturn DTR_ComputeFlowDistances(DHookReturn hReturn) { - public TerrorNavArea GetNavAreaByID(int id) { - return view_as(L4D_GetNavAreaByID(id)); - } - - property float m_flMapMaxFlowDistance { - public get() { return LoadFromAddress(L4D_GetPointer(POINTER_NAVMESH) + view_as
(g_iOffs_m_flMapMaxFlowDistance), NumberType_Int32); } - public set(float flow) { StoreToAddress(L4D_GetPointer(POINTER_NAVMESH) + view_as
(g_iOffs_m_flMapMaxFlowDistance), flow, NumberType_Int32); } - } -} + g_bInComputeFlowDistances = true; -NavAreaVector TheNavAreas; -TerrorNavMesh TheNavMesh; -CEscapeRoute TheEscapeRoute; + return MRES_Ignored; +} -enum struct TerrorNavInfo +MRESReturn DTR_GetPlayerSpawnPosition(DHookReturn hReturn, DHookParam hParams) { - int m_id; - float m_flowToGoal; - float m_flowFromStart; - - void Save(TerrorNavArea area) { - this.m_id = area.GetID(); - this.m_flowToGoal = area.m_flowToGoal; - this.m_flowFromStart = area.m_flowFromStart; - } - - void Restore() { - TerrorNavArea area = TheNavMesh.GetNavAreaByID(this.m_id); - area.m_flowToGoal = this.m_flowToGoal; - area.m_flowFromStart = this.m_flowFromStart; - } + if (!g_bInComputeFlowDistances) + return MRES_Ignored; + +// if ( iSurvPosCount ) +// { +// static int idx = -1; +// if ( ++idx >= iSurvPosCount ) +// idx = 0; +// +// iPosIndexToUse = idx; +// pSurvPosEnt = vecSurvPositions.At(iPosIndexToUse); +// } +// +// NOTE: The patch here transforms the original code to make "iPosIndexToUse" always zero +// which forces the first survivor postion entity to be where the start area is. + + g_patch_SkipSpawnPosIdx.Enable(); + + return MRES_Ignored; } -ArrayList g_aEscapeRouteAreas; -ArrayList g_aAreaFlows; +MRESReturn DTR_GetPlayerSpawnPosition_Post(DHookReturn hReturn, DHookParam hParams) +{ + if (!g_bInComputeFlowDistances) + return MRES_Ignored; -float g_flMapMaxFlowDistance; + g_patch_SkipSpawnPosIdx.Disable(); -public void OnPluginStart() + return MRES_Ignored; +} + +MRESReturn DTR_ComputeFlowDistances_Post(DHookReturn hReturn) { - GameDataWrapper gd = new GameDataWrapper("l4d_consistent_escaperoute"); - - g_iOffs_m_nMainPathAreaCount = gd.GetOffset("CEscapeRoute::m_nMainPathAreaCount"); - g_iOffs_m_flowToGoal = gd.GetOffset("TerrorNavArea::m_flowToGoal"); - g_iOffs_m_flMapMaxFlowDistance = gd.GetOffset("TerrorNavMesh::m_flMapMaxFlowDistance"); - - g_hSDKCall_ResetPath = gd.CreateSDKCallOrFail(SDKCall_Entity, SDKConf_Signature, "CEscapeRoute::ResetPath"); - g_hSDKCall_FinishPath = gd.CreateSDKCallOrFail(SDKCall_Entity, SDKConf_Signature, "CEscapeRoute::FinishPath"); + g_bInComputeFlowDistances = false; - SDKCallParamsWrapper params[] = { - {SDKType_PlainOldData, SDKPass_Plain} // TerrorNavArea - }; - g_hSDKCall_AddArea = gd.CreateSDKCallOrFail(SDKCall_Entity, SDKConf_Signature, "CEscapeRoute::AddArea", params, sizeof(params)); - - delete gd; - - g_aEscapeRouteAreas = new ArrayList(); - g_aAreaFlows = new ArrayList(sizeof(TerrorNavInfo)); - - HookEvent("round_start_post_nav", Event_RoundStartPostNav); +#if DEBUG + RequestFrame(NextFrame_ComputeFlowDistance); // escape route is rebuilt a few calls after +#endif + + return MRES_Ignored; } -void Event_RoundStartPostNav(Event event, const char[] name, bool dontBroadcast) +#if DEBUG +void Event_round_start(Event event, const char[] name, bool dontBroadcast) { - if (!L4D_IsVersusMode()) - return; - - // Uses a delay here in case GameRules han't been updated yet - CreateTimer(0.1, Timer_RoundStartPostNav, _, TIMER_FLAG_NO_MAPCHANGE); + g_bEscapeRouteChecked = false; } -Action Timer_RoundStartPostNav(Handle timer) +void NextFrame_ComputeFlowDistance() { - int entity = FindEntityByClassname(MaxClients+1, "escape_route"); - TheEscapeRoute = CEscapeRoute(entity); + if (g_bEscapeRouteChecked) + return; - Address pTheNavAreas = L4D_GetPointer(POINTER_THENAVAREAS); - TheNavAreas = NavAreaVector(pTheNavAreas); + int entity = FindEntityByClassname(INVALID_ENT_REFERENCE, "escape_route"); + if (entity == INVALID_ENT_REFERENCE) + return; - if (InSecondHalfOfRound()) - { - RestoreFromFirstHalf(); - } - else - { - InitFromFirstHalf(); - } + // Only check once eveny beginning of round + g_bEscapeRouteChecked = true; - return Plugin_Stop; -} + CEscapeRoute TheEscapeRoute = CEscapeRoute(entity); -void InitFromFirstHalf() -{ - g_aEscapeRouteAreas.Clear(); - g_aAreaFlows.Clear(); - - for (int i = 0, size = TheEscapeRoute.m_nMainPathAreaCount; i < size; ++i) - { - TerrorNavArea area = TheEscapeRoute.GetMainPathArea(i); - g_aEscapeRouteAreas.Push(area.GetID()); - } - - TerrorNavInfo info; - g_aAreaFlows.Resize(TheNavAreas.Size()); - for (int i = 0, size = TheNavAreas.Size(); i < size; ++i) + if (g_SavedEscapeRouteAreas.Length) { - TerrorNavArea area = TheNavAreas.At(i); - info.Save(area); - g_aAreaFlows.SetArray(i, info); - } - - g_flMapMaxFlowDistance = TheNavMesh.m_flMapMaxFlowDistance; - - PrintToServer("[l4d_consistent_escaperoute] Cached escape route of first half (%i / %i nav) (%.5f)", g_aEscapeRouteAreas.Length, TheNavAreas.Size(), g_flMapMaxFlowDistance); -} + bool fullyMatched = true; + PrintToServer("[%s] Checking escape route", PLUGIN_TAG); -void RestoreFromFirstHalf() -{ - if (g_aAreaFlows.Length) - { - TerrorNavInfo info; + if (g_SavedEscapeRouteAreas.Length != TheEscapeRoute.m_nMainPathAreaCount) + { + PrintToServer(" Count mismatches (was %d, now %d)", PLUGIN_TAG, g_SavedEscapeRouteAreas.Length, TheEscapeRoute.m_nMainPathAreaCount); + fullyMatched = false; + } + else + { + for (int i = 0, size = TheEscapeRoute.m_nMainPathAreaCount; i < size; ++i) + { + TerrorNavArea area = TheEscapeRoute.GetMainPathArea(i); + if (area.GetID() != g_SavedEscapeRouteAreas.Get(i)) + { + PrintToServer(" Area @ %d mismatches (was #%d, now #%d)", PLUGIN_TAG, i, g_SavedEscapeRouteAreas.Get(i), area.GetID()); + fullyMatched = false; + break; + } + } + } + + PrintToServer("[%s] Finish checking escape route (success = %s)", PLUGIN_TAG, fullyMatched ? "yes" : "no"); - for (int i = 0, size = g_aAreaFlows.Length; i < size; ++i) + if (!fullyMatched) { - g_aAreaFlows.GetArray(i, info); - info.Restore(); + LogError("Unexpected mismatching escape route. Please report to author."); } - - TheNavMesh.m_flMapMaxFlowDistance = g_flMapMaxFlowDistance; } - - if (g_aEscapeRouteAreas.Length) + else { + PrintToServer("[%s] Saving escape route (count = %d)", PLUGIN_TAG, TheEscapeRoute.m_nMainPathAreaCount); for (int i = 0, size = TheEscapeRoute.m_nMainPathAreaCount; i < size; ++i) { TerrorNavArea area = TheEscapeRoute.GetMainPathArea(i); - area.RemoveSpawnAttributes(NAV_SPAWN_ESCAPE_ROUTE); + g_SavedEscapeRouteAreas.Push(area.GetID()); } - - PrintToServer("[l4d_consistent_escaperoute] Second half (%i / %i nav) (%.5f)", TheEscapeRoute.m_nMainPathAreaCount, TheNavAreas.Size(), g_flMapMaxFlowDistance); - - TheEscapeRoute.ResetPath(); - for (int i = 0, size = g_aEscapeRouteAreas.Length; i < size; ++i) - { - int id = g_aEscapeRouteAreas.Get(i); - - TerrorNavArea area = TheNavMesh.GetNavAreaByID(id); - TheEscapeRoute.AddArea(area); - } - TheEscapeRoute.FinishPath(); } - - PrintToServer("[l4d_consistent_escaperoute] Restored escape route from last half (%i / %i nav)", g_aEscapeRouteAreas.Length, TheNavAreas.Size()); -} - -stock bool InSecondHalfOfRound() -{ - return view_as(GameRules_GetProp("m_bInSecondHalfOfRound")); } +#endif From 9e6c993c8fa0bccb9d87acaf72c1a5c429e82104 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:43:07 +0800 Subject: [PATCH 2/2] Fix missing include --- .../scripting/l4d_consistent_escaperoute.sp | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp b/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp index 106d88b5b..f5ac8cf5b 100644 --- a/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp +++ b/addons/sourcemod/scripting/l4d_consistent_escaperoute.sp @@ -8,7 +8,6 @@ #include #include #include -#include <@Forgetest/gamedatawrapper> #if DEBUG #include @@ -23,6 +22,46 @@ public Plugin myinfo = url = "https://github.com/Target5150/MoYu_Server_Stupid_Plugins" }; +methodmap GameDataWrapper < GameData { + public GameDataWrapper(const char[] file) { + GameData gd = new GameData(file); + if (!gd) SetFailState("Missing gamedata \"%s\"", file); + return view_as(gd); + } + property GameData Super { + public get() { return view_as(this); } + } + public int GetOffset(const char[] key) { + int offset = this.Super.GetOffset(key); + if (offset == -1) SetFailState("Missing offset \"%s\"", key); + return offset; + } + public Address GetAddress(const char[] key) { + Address ptr = this.Super.GetAddress(key); + if (ptr == Address_Null) SetFailState("Missing address \"%s\"", key); + return ptr; + } + public MemoryPatch CreatePatchOrFail(const char[] name, bool enable = false) { + MemoryPatch hPatch = MemoryPatch.CreateFromConf(this, name); + if (!(enable ? hPatch.Enable() : hPatch.Validate())) + SetFailState("Failed to patch \"%s\"", name); + return hPatch; + } + public DynamicDetour CreateDetourOrFail( + const char[] name, + DHookCallback preHook = INVALID_FUNCTION, + DHookCallback postHook = INVALID_FUNCTION) { + DynamicDetour hSetup = DynamicDetour.FromConf(this, name); + if (!hSetup) + SetFailState("Missing detour setup \"%s\"", name); + if (preHook != INVALID_FUNCTION && !hSetup.Enable(Hook_Pre, preHook)) + SetFailState("Failed to pre-detour \"%s\"", name); + if (postHook != INVALID_FUNCTION && !hSetup.Enable(Hook_Post, postHook)) + SetFailState("Failed to post-detour \"%s\"", name); + return hSetup; + } +} + methodmap Address {} #if DEBUG