1+ #pragma semicolon 1
2+ #pragma newdecls required
3+
4+ #include <sourcemod>
5+ #include <sdkhooks>
6+ #include <dhooks>
7+ #include <sourcescramble>
8+
9+ #define PLUGIN_VERSION " 2.5"
10+
11+ public Plugin myinfo =
12+ {
13+ name = " [L4D & 2] Vomit Trace Patch" ,
14+ author = " Forgetest" ,
15+ description = " Fix vomit stuck on Infected teammates & allow stricter collision test." ,
16+ version = PLUGIN_VERSION ,
17+ url = " https://github.com/Target5150/MoYu_Server_Stupid_Plugins"
18+ }
19+
20+ methodmap GameDataWrapper < GameData {
21+ public GameDataWrapper (const char [] file ) {
22+ GameData gd = new GameData (file );
23+ if (! gd ) SetFailState (" Missing gamedata \" %s \" " , file );
24+ return view_as <GameDataWrapper >(gd );
25+ }
26+ property GameData Super {
27+ public get () { return view_as <GameData >(this ); }
28+ }
29+ public int GetOffset (const char [] key ) {
30+ int offset = this .Super .GetOffset (key );
31+ if (offset == - 1 ) SetFailState (" Missing offset \" %s \" " , key );
32+ return offset ;
33+ }
34+ public Address GetAddress (const char [] key ) {
35+ Address ptr = this .Super .GetAddress (key );
36+ if (ptr == Address_Null ) SetFailState (" Missing address \" %s \" " , key );
37+ return ptr ;
38+ }
39+ public MemoryPatch CreatePatchOrFail (const char [] name , bool enable = false ) {
40+ MemoryPatch hPatch = MemoryPatch .CreateFromConf (this , name );
41+ if (! (enable ? hPatch .Enable () : hPatch .Validate ()))
42+ SetFailState (" Failed to patch \" %s \" " , name );
43+ return hPatch ;
44+ }
45+ public DynamicHook CreateDHookOrFail (const char [] name ) {
46+ DynamicHook hSetup = DynamicHook .FromConf (this , name );
47+ if (! hSetup )
48+ SetFailState (" Missing dhook setup \" %s \" " , name );
49+ return hSetup ;
50+ }
51+ }
52+
53+ #define OP_CALL_SIZE 5
54+
55+ bool g_bLeft4Dead2 ;
56+
57+ MemoryPatch g_hPatch ;
58+ DynamicHook g_hDHook ;
59+ int g_iPatchOffs , g_iFuncOffs ;
60+
61+ DynamicHook g_hDHook_PhysicsSolidMaskForEntity ;
62+
63+ ConVar g_cvStrictCollide ;
64+
65+ public APLRes AskPluginLoad2 (Handle myself , bool late , char [] error , int err_max )
66+ {
67+ switch (GetEngineVersion ())
68+ {
69+ case Engine_Left4Dead : { g_bLeft4Dead2 = false ; }
70+ case Engine_Left4Dead2 : { g_bLeft4Dead2 = true ; }
71+ default : { strcopy (error , err_max , " Plugin supports L4D & 2 only." ); return APLRes_SilentFailure ; }
72+ }
73+ return APLRes_Success ;
74+ }
75+
76+ public void OnPluginStart ()
77+ {
78+ GameDataWrapper conf = new GameDataWrapper (" l4d_vomit_trace_patch" );
79+
80+ g_hDHook_PhysicsSolidMaskForEntity = conf .CreateDHookOrFail (" CBaseEntity::PhysicsSolidMaskForEntity" );
81+
82+ if (! g_bLeft4Dead2 )
83+ {
84+ g_hPatch = conf .CreatePatchOrFail (" ShouldHitEntity_MyInfectedPointer" , false );
85+ g_hDHook = conf .CreateDHookOrFail (" CBaseAbility::UpdateAbility" );
86+
87+ Address pGetTeamNumberFuncAddr = conf .GetAddress (" CBaseEntity_GetTeamNumber" );
88+ g_iPatchOffs = conf .GetOffset (" PatchOffset" );
89+ g_iFuncOffs =
90+ view_as <int >(pGetTeamNumberFuncAddr ) - (view_as <int >(g_hPatch .Address ) + (g_iPatchOffs - 1 ) + OP_CALL_SIZE );
91+
92+ conf .CreatePatchOrFail (" OnVomitCollide__TraceRayMask_patch" , true );
93+ conf .CreatePatchOrFail (" OnVomitCollide__ClipRayMask_patch" , true );
94+ }
95+
96+ delete conf ;
97+
98+ g_cvStrictCollide = CreateConVar (" vomit_collide_strict" , " 1" , " Stricter vomit collision against hitbox instead of bounding box." , FCVAR_NONE , true , 0.0 , true , 1.0 );
99+ }
100+
101+ void ApplyPatch (bool patch )
102+ {
103+ static bool patched = false ;
104+ if (patch && ! patched )
105+ {
106+ if (! g_hPatch .Enable ())
107+ SetFailState (" Failed to enable patch \" ShouldHitEntity_MyInfectedPointer\" " );
108+
109+ StoreToAddress (g_hPatch .Address + view_as <Address >(g_iPatchOffs ), g_iFuncOffs , NumberType_Int32 );
110+ patched = true ;
111+ }
112+ else if (! patch && patched )
113+ {
114+ g_hPatch .Disable ();
115+ patched = false ;
116+ }
117+ }
118+
119+ public void OnEntityCreated (int entity , const char [] classname )
120+ {
121+ if (classname [0 ] == ' a' )
122+ {
123+ if (! strcmp (classname , " ability_vomit" ))
124+ {
125+ if (! g_bLeft4Dead2 )
126+ {
127+ g_hDHook .HookEntity (Hook_Pre , entity , CVomit_UpdateAbility );
128+ g_hDHook .HookEntity (Hook_Post , entity , CVomit_UpdateAbility_Post );
129+ }
130+
131+ g_hDHook_PhysicsSolidMaskForEntity .HookEntity (Hook_Post , entity , CVomit_PhysicsSolidMaskForEntity_Post );
132+ }
133+ }
134+ else if (classname [0 ] == ' v' )
135+ {
136+ if (! strcmp (classname , " vomit_particle" ))
137+ {
138+ g_hDHook_PhysicsSolidMaskForEntity .HookEntity (Hook_Post , entity , CVomitParticle_PhysicsSolidMaskForEntity_Post );
139+ }
140+ }
141+ }
142+
143+ MRESReturn CVomit_UpdateAbility (int pThis )
144+ {
145+ if (GetEntProp (pThis , Prop_Send , " m_isSpraying" ))
146+ {
147+ ApplyPatch (true );
148+ }
149+
150+ return MRES_Ignored ;
151+ }
152+
153+ MRESReturn CVomit_UpdateAbility_Post (int pThis )
154+ {
155+ ApplyPatch (false );
156+
157+ return MRES_Ignored ;
158+ }
159+
160+ MRESReturn CVomit_PhysicsSolidMaskForEntity_Post (DHookReturn hReturn )
161+ {
162+ // (L4D1) 0x200400B -> MASK_SOLID
163+ // (L4D2) 0x2004003 -> MASK_SOLID & ~CONTENTS_GRATE
164+
165+ int flags = hReturn .Value ;
166+
167+ flags &= (~ CONTENTS_GRATE ); // Unnecessary for L4D2 though
168+
169+ if (g_cvStrictCollide .BoolValue )
170+ {
171+ flags |= CONTENTS_HITBOX ;
172+ }
173+
174+ hReturn .Value = flags ;
175+ return MRES_Override ;
176+ }
177+
178+ MRESReturn CVomitParticle_PhysicsSolidMaskForEntity_Post (DHookReturn hReturn )
179+ {
180+ return CVomit_PhysicsSolidMaskForEntity_Post (hReturn );
181+ }
0 commit comments