11#pragma semicolon 1
22#pragma newdecls required
33
4- #include <sourcemod>
5- #include <sdkhooks>
6- #include <sdktools>
4+ #include <sourcemod>
5+ #include <sdktools>
6+ #include <sdkhooks>
77
8+ #define GAMEDATA " l4d2_car_alarm_bots"
9+ #define MAX_BYTES 33
10+ int g_ByteCount , g_ByteMatch ;
11+ int g_ByteSaved [MAX_BYTES ];
12+ Address g_Address ;
13+
14+ ConVar
15+ g_hCarAlarmSettings ,
16+ g_hCarTouchCapped ,
17+ g_hCarAI ;
18+
19+ int FLAGS [3 ] = {
20+ 1 << 0 , // Trigger Car Alarm on Survivor Touch
21+ 1 << 1 , // Trigger Car Alarm disabled when hit by another Hittable.
22+ };
23+
24+ int iFlags ;
25+ bool bCarTouchCapped ;
26+ bool bAI ;
27+
28+
29+ // ====================================================================================================
30+ // PLUGIN INFO / START / END
31+ // ====================================================================================================
832public Plugin myinfo =
933{
10- name = " L4D2 Car Alarm Hittable Fix " ,
11- author = " Sir" ,
12- description = " Disables the Car Alarm when a Tank hittable hits the alarmed car. " ,
13- version = " 1.1 " ,
14- url = " nah "
34+ name = " L4D2 Car Alarm Fixes " ,
35+ author = " Sir & Silvers (Gamedata and general idea from l4d2_car_alarm_bots) " ,
36+ description = " Disables the Car Alarm when a Tank hittable hits the alarmed car and makes sure the Car Alarm triggers whenever a Survivor touches it " ,
37+ version = " 1.2 " ,
38+ url = " https://github.com/SirPlease/L4D2-Competitive-Rework "
1539};
1640
17- public void OnEntityCreated ( int entity , const char [] classname )
41+ public void OnPluginStart ()
1842{
19- // Hook Alarmed Cars.
20- if (! StrEqual (classname , " prop_car_alarm" )) return ;
21- SDKHook (entity , SDKHook_Touch , OnAlarmCarTouch );
43+ // ====================================================================================================
44+ // GAMEDATA
45+ // ====================================================================================================
46+ char sPath [PLATFORM_MAX_PATH ];
47+ BuildPath (Path_SM , sPath , sizeof (sPath ), " gamedata/%s .txt" , GAMEDATA );
48+ if ( FileExists (sPath ) == false ) SetFailState (" \n ==========\n Missing required file: \" %s \" .\n Read installation instructions again.\n ==========" , sPath );
49+
50+ Handle hGameData = LoadGameConfigFile (GAMEDATA );
51+ if ( hGameData == null ) SetFailState (" Failed to load \" %s .txt\" gamedata." , GAMEDATA );
52+
53+ g_Address = GameConfGetAddress (hGameData , " CCarProp::InputSurvivorStandingOnCar" );
54+ if ( ! g_Address ) SetFailState (" Failed to load \" CCarProp::InputSurvivorStandingOnCar\" address." );
55+
56+ int offset = GameConfGetOffset (hGameData , " InputSurvivorStandingOnCar_Offset" );
57+ if ( offset == - 1 ) SetFailState (" Failed to load \" InputSurvivorStandingOnCar_Offset\" offset." );
58+
59+ g_ByteMatch = GameConfGetOffset (hGameData , " InputSurvivorStandingOnCar_Byte" );
60+ if ( g_ByteMatch == - 1 ) SetFailState (" Failed to load \" InputSurvivorStandingOnCar_Byte\" byte." );
61+
62+ g_ByteCount = GameConfGetOffset (hGameData , " InputSurvivorStandingOnCar_Count" );
63+ if ( g_ByteCount == - 1 ) SetFailState (" Failed to load \" InputSurvivorStandingOnCar_Count\" count." );
64+ if ( g_ByteCount > MAX_BYTES ) SetFailState (" Error: byte count exceeds scripts defined value (%d /%d )." , g_ByteCount , MAX_BYTES );
65+
66+ g_Address += view_as <Address >(offset );
67+
68+ for ( int i = 0 ; i < g_ByteCount ; i ++ )
69+ {
70+ g_ByteSaved [i ] = LoadFromAddress (g_Address + view_as <Address >(i ), NumberType_Int8 );
71+ }
72+ if ( g_ByteSaved [0 ] != g_ByteMatch ) SetFailState (" Failed to load, byte mis-match. %d (0x%02X != 0x%02X )" , offset , g_ByteSaved [0 ], g_ByteMatch );
73+
74+ delete hGameData ;
75+
76+ // =================================================================================================
77+ // CONVARS
78+ // =================================================================================================
79+
80+ g_hCarAlarmSettings = CreateConVar (" l4d2_car_alarm_settings" , " 3" , " Bitmask: 1-Trigger Alarm on Survivor Touch/ 2-Disable Alarm when a Hittable hits the Alarm Car" , FCVAR_NOTIFY );
81+ g_hCarTouchCapped = CreateConVar (" l4d2_car_alarm_touch_capped" , " 1" , " Only add the additional car alarm trigger when the Survivor is capped by an Infected when touching the car? (Requires bitmask settings)" , FCVAR_NOTIFY , true , 0.0 , true , 1.0 );
82+ g_hCarAI = CreateConVar (" l4d2_car_alarm_touch_ai" , " 0" , " Care about AI Survivors touching the car? (Default vanilla = 0) Requires bitmask settings" , FCVAR_NOTIFY , true , 0.0 , true , 1.0 );
83+
84+ iFlags = g_hCarAlarmSettings .IntValue ;
85+ bCarTouchCapped = g_hCarTouchCapped .BoolValue ;
86+ bAI = g_hCarAI .BoolValue ;
87+ g_hCarAlarmSettings .AddChangeHook (ChangedConVars );
88+ g_hCarTouchCapped .AddChangeHook (ChangedConVars );
89+ g_hCarAI .AddChangeHook (ChangedConVars );
2290}
2391
24- public Action OnAlarmCarTouch (int car , int entity )
25- {
26- // Speaks for itself
27- if (IsTankHittable (entity ))
92+ public void OnPluginEnd ()
93+ {
94+ PatchAddress (false );
95+ }
96+
97+ // ====================================================================================================
98+ // PATCH / HOOK
99+ // ====================================================================================================
100+ void PatchAddress (int patch )
101+ {
102+ static bool patched ;
103+
104+ if ( ! patched && patch )
105+ {
106+ patched = true ;
107+ for ( int i = 0 ; i < g_ByteCount ; i ++ )
108+ StoreToAddress (g_Address + view_as <Address >(i ), g_ByteMatch == 0x0F ? 0x90 : 0xEB , NumberType_Int8 );
109+ }
110+ else if ( patched && ! patch )
111+ {
112+ patched = false ;
113+ for ( int i = 0 ; i < g_ByteCount ; i ++ )
114+ StoreToAddress (g_Address + view_as <Address >(i ), g_ByteSaved [i ], NumberType_Int8 );
115+ }
116+ }
117+
118+ // ====================================================================================================
119+ // EVENTS
120+ // ====================================================================================================
121+ public void OnEntityCreated (int entity , const char [] classname )
122+ {
123+ if (strcmp (classname , " prop_car_alarm" ) == 0 )
124+ SDKHook (entity , SDKHook_Touch , OnTouch );
125+ }
126+
127+ public void OnTouch (int car , int other )
128+ {
129+ // Is the other entity a Survivor?
130+ if ((iFlags & FLAGS [0 ]) && other >= 1 && other <= MaxClients && GetClientTeam (other ) == 2 )
131+ {
132+ // We don't want the AI to trigger the car alarm.
133+ if (! bAI && IsFakeClient (other ))
134+ return ;
135+
136+ // We only care about capped players touching the car.
137+ if (bCarTouchCapped && ! IsPlayerCapped (other ))
138+ return ;
139+
140+ PatchAddress (true );
141+ AcceptEntityInput (car , " SurvivorStandingOnCar" , other , other );
142+ PatchAddress (false );
143+
144+ // Unhook car, we don't need it anymore.
145+ SDKUnhook (car , SDKHook_Touch , OnTouch );
146+ }
147+
148+ // Is the other entity a Hittable car?
149+ else if ((iFlags & FLAGS [1 ]) && IsTankHittable (other ))
28150 {
29151 // This returns 1 on every hittable at all times.
30- if (GetEntProp (entity , Prop_Send , " m_hasTankGlow" ) > 0 )
152+ if (GetEntProp (other , Prop_Send , " m_hasTankGlow" ) > 0 )
31153 {
32154 // Disable the Car Alarm
33155 AcceptEntityInput (car , " Disable" );
@@ -36,11 +158,9 @@ public Action OnAlarmCarTouch(int car, int entity)
36158 CreateTimer (0.3 , DisableAlarm , car );
37159
38160 // Unhook car, we don't need it anymore.
39- SDKUnhook (car , SDKHook_Touch , OnAlarmCarTouch );
161+ SDKUnhook (car , SDKHook_Touch , OnTouch );
40162 }
41163 }
42-
43- return Plugin_Continue ;
44164}
45165
46166public Action DisableAlarm (Handle timer , any car )
@@ -56,32 +176,55 @@ public Action DisableAlarm(Handle timer, any car)
56176 }
57177 }
58178
59- if (Tank != - 1 ) SDKHooks_TakeDamage (car , Tank , Tank , 0.0 );
179+ if (Tank != - 1 )
180+ SDKHooks_TakeDamage (car , Tank , Tank , 0.0 );
60181
61182 return Plugin_Stop ;
62183}
63184
185+
186+ // ====================================================================================================
187+ // STOCKS
188+ // ====================================================================================================
64189stock bool IsValidTank (int client )
65190{
66- if (client <= 0 || client > MaxClients || ! IsClientConnected (client )) return false ;
67- return (IsClientInGame (client ) && GetClientTeam (client ) == 3 && GetEntProp (client , Prop_Send , " m_zombieClass" ) == 8 );
191+ if (client <= 0 || client > MaxClients || ! IsClientConnected (client )) return false ;
192+ return (IsClientInGame (client ) && GetClientTeam (client ) == 3 && GetEntProp (client , Prop_Send , " m_zombieClass" ) == 8 );
68193}
69194
70195stock bool IsTankHittable (int iEntity )
71196{
72197 if (! IsValidEntity (iEntity ))
73- {
74- return false ;
75- }
198+ return false ;
76199
77200 char className [64 ];
78201
79202 GetEdictClassname (iEntity , className , sizeof (className ));
203+
80204 if (StrEqual (className , " prop_physics" ))
81205 {
82- if (GetEntProp (iEntity , Prop_Send , " m_hasTankGlow" , 1 )) return true ;
206+ if (GetEntProp (iEntity , Prop_Send , " m_hasTankGlow" , 1 ))
207+ return true ;
83208 }
84- else if (StrEqual (className , " prop_car_alarm" )) return true ;
209+ else if (StrEqual (className , " prop_car_alarm" ))
210+ return true ;
85211
86212 return false ;
213+ }
214+
215+ stock bool IsPlayerCapped (int client )
216+ {
217+ if (GetEntPropEnt (client , Prop_Send , " m_jockeyAttacker" ) > 0 ||
218+ GetEntPropEnt (client , Prop_Send , " m_tongueOwner" ) > 0 ||
219+ GetEntPropEnt (client , Prop_Send , " m_carryAttacker" ) > 0 )
220+ return true ;
221+
222+ return false ;
223+ }
224+
225+ void ChangedConVars (ConVar convar , const char [] oldValue , const char [] newValue )
226+ {
227+ iFlags = g_hCarAlarmSettings .IntValue ;
228+ bCarTouchCapped = g_hCarTouchCapped .BoolValue ;
229+ bAI = g_hCarAI .BoolValue ;
87230}
0 commit comments