· 6 years ago · Dec 03, 2019, 05:08 PM
1#define PLUGIN_VERSION "1.15.1"
2
3/*====================================================================================================
4 Plugin Info:
5
6* Name : [L4D & L4D2] Prototype Grenades
7* Author : SilverShot
8* Descrp : Creates a selection of different grenade types.
9* Link : https://forums.alliedmods.net/showthread.php?t=318965
10* Plugins : https://sourcemod.net/plugins.php?exact=exact&sortby=title&search=1&author=Silvers
11
12======================================================================================================
13 Change Log:
14
151.15.1 (03-Dec-2019) by Dragokas
16 - Added ability to open grenade menu by middle mouse button
17
181.15 (29-Nov-2019)
19 - Added "messages" option in the config to disable hint messages as requested by "BlackSabbarh".
20 - Fixed "preferences" option "0" not resetting grenade type when players take over bots. Thanks to "Voevoda" for reporting.
21
221.14 (24-Nov-2019)
23 - Added Simplified Chinese translations. Thanks to "asd2323208" for providing.
24 - Fix for potential godmode zombies when using LMC.
25 - Fixed error msg: "Entity 157 (class 'pipe_bomb_projectile') reported ENTITY_CHANGE_NONE but 'm_flCreateTime' changed.".
26
271.13 (11-Nov-2019)
28 - Added option "0" to "preferences" in the config to give stock grenades on pickup.
29
301.12 (10-Nov-2019)
31 - Fixed breaking client preferences after map change due to last version fixes.
32
331.11 (09-Nov-2019)
34 - Small optimizations.
35 - Fixed breaking equip on round restart.
36 - Fixed Shield type not working. Thanks to "fbef0102" for reporting.
37
381.10 (01-Nov-2019)
39 - Changed the way grenade bounce sounds are replaced to prevent plugin conflicts. Thanks to "Lux" for the idea.
40 - Optimizations: Changed string creation to static char for faster CPU cycles. Various string comparison changes.
41 - Now only supports "Gear Transfer" plugin version 2.0 or greater to preserve random grenade type preferences.
42 - Removed 1 frame delay on weapon equip from previous version of supporting "Gear Transfer" plugin.
43 - Fixed "GrenadeMenu_Invalid" PrintToChat not replacing thef colors. Thanks to "BHaType" for reporting.
44
451.9 (23-Oct-2019)
46 - Fixed Freezer mode not preserving special infected render color. Thanks to "Dragokas" for reporting.
47
481.8 (23-Oct-2019)
49 - Changed "Bullets" mode projectile sound.
50 - Maybe fixed invalid entity errors again, reported by "KRUTIK".
51 - Minor changes to late loading and turning the plugin on/off.
52
531.7 (18-Oct-2019)
54 - Fixed handle memory leak.
55
561.6 (18-Oct-2019)
57 - Fixed invalid entity errors reported by "KRUTIK".
58 - Fixed L4D1 errors reported by "Dragokas".
59 - Fixed not completely disabling everything when the plugin is turned off.
60 - Now prevents ledge hanging when floating in Anti-Gravity.
61
621.5 (17-Oct-2019)
63 - Added 6 new types: "Extinguisher", "Glow", "Anti-Gravity", "Fire Cluster", "Bullets" and "Flak".
64 - Added command "sm_grenade" to open a menu for choosing the grenade type. Optional args to specify a type.
65 - Added "mode_switch" in the config to control how to change grenade type. Menu and/or key combination.
66 - Auto display and close menu with "mode_switch" when selecting a different type via key combination.
67 - Changed L4D2 vocalizations from "throwing pipebomb" or "throwing molotov" etc to "throwing grenade" when not stock.
68 - Changed grenade bounce impact sound.
69 - Cleaned up the sounds by changing some and adding a few missing ones.
70 - Feature to push and stumble Common Infected now works in L4D1.
71 - Fixed wrong Deafen offset for L4D1 Linux. Fixes Flashbang.
72 - Fixed wrong OnStaggered signature for Linux L4D1. Fixes staggering clients.
73 - Fixed Freezer type not following the "targets" setting.
74 - Fixed "damage_tick" to function for most types. Values smaller than "effect_tick" will use the effect tick time.
75 - Thanks to "Dragokas" for the menu ideas and reporting problems in L4D1.
76
77 - Required updated files:
78 - Config: l4d_grenades.cfg
79 - Gamedata: l4d_grenades.txt
80 - Translations: grenades.phrases.txt.
81
821.4 (10-Oct-2019)
83 - Added Russian translations. Thanks to "KRUTIK" for providing.
84 - Fixed OnNextEquip errors. Thanks to "KRUTIK" for reporting.
85
861.3 (10-Oct-2019)
87 - Added support for "Gear Transfer" plugin. For persistent grenade types when "preferences" is set to random grenade mode.
88 - Changed Vaporizer to inflict full damage on Common instead of range scaled. Original functionality before 1.1.
89
901.2 (10-Oct-2019)
91 - Fixed OnWeaponDrop errors. Thanks to "BlackSabbarh" for reporting.
92 - Some optimizations.
93
941.1 (08-Oct-2019)
95 - Added "bots" in the config to control if bots can use Prototype Grenades. Requires external plugin.
96 - Added "damage_special", "damage_survivors", "damage_tank", "damage_witch" and "damage_physics" in the config to scale damage.
97 - Added "preferences" in the config to save a players selected mode, or give a random grenade type. Persistent with dropping.
98 - Added "targets" in the config to control who can be affected by the grenade effects.
99 - Changed "nade" in the config to use bit flags, which allows all grenade modes to work for all grenades.
100 - Fixed map transition breaking the ability to change grenade modes.
101 - Fixed over-healing when a player has temp health.
102 - Fixed sometimes detonating immediately after throwing.
103 - Fixed sounds not stopping when reloading the plugin during an active grenade.
104 - Updated data config, plugin requires new version, or effects will break: l4d_grenades.cfg.
105
1061.0 (03-Oct-2019)
107 - Initial release.
108
109======================================================================================================
110 Thanks:
111
112 This plugin was made using source code from the following plugins.
113 If I have used your code and not credited you, please let me know.
114
115* Thanks to "Lux" for "L4D_TE_Create_Particle" - stock function.
116 https://gist.github.com/LuxLuma/73a8fab2b5f44ef800070bfd5e7fe257
117
118* Thanks to "AtomicStryker" for "[L4D & L4D2] Smoker Cloud Damage" - Modified IsVisibleTo() function.
119 https://forums.alliedmods.net/showthread.php?p=866613
120
121* "Zuko & McFlurry" for "[L4D2] Weapon/Zombie Spawner" - Modified SetTeleportEndPoint() function.
122 https://forums.alliedmods.net/showthread.php?t=109659
123
124====================================================================================================*/
125
126#pragma semicolon 1
127#pragma newdecls required
128
129#include <sourcemod>
130#include <clientprefs>
131#include <sdktools>
132#include <sdkhooks>
133
134
135
136//LMC
137#undef REQUIRE_PLUGIN
138#tryinclude <LMCCore>
139#define REQUIRE_PLUGIN
140
141#if !defined _LMCCore_included
142 native int LMC_GetEntityOverlayModel(int iEntity);
143#endif
144
145bool bLMC_Available;
146//LMC
147
148
149
150// DEFINES
151#define CVAR_FLAGS FCVAR_NOTIFY
152#define CONFIG_DATA "data/l4d_grenades.cfg"
153#define GAMEDATA "l4d_grenades"
154
155
156
157// EFFECTS
158#define MODEL_BOUNDING "models/props/cs_militia/silo_01.mdl"
159#define MODEL_CRATE "models/props_junk/explosive_box001.mdl"
160#define MODEL_GASCAN "models/props_junk/gascan001a.mdl"
161#define MODEL_SPRAYCAN "models/props_junk/garbage_spraypaintcan01a.mdl"
162#define MODEL_SPRITE "models/sprites/glow01.spr"
163
164#define PARTICLE_MUZZLE "weapon_muzzle_flash_autoshotgun"
165#define PARTICLE_BASHED "screen_bashed"
166#define PARTICLE_TRACERS "weapon_tracers"
167#define PARTICLE_TRACER_50 "weapon_tracers_50cal"
168#define PARTICLE_SMOKER1 "smoker_smokecloud_cheap"
169#define PARTICLE_SMOKER2 "smoker_spore_trail"
170#define PARTICLE_BOOMER "boomer_explode_E"
171#define PARTICLE_SMOKE2 "apc_wheel_smoke2" // White smoke
172#define PARTICLE_BURST "gas_explosion_initialburst" // Large explosion
173#define PARTICLE_BLAST "gas_explosion_initialburst_blast" // Large explosion HD
174#define PARTICLE_BLAST2 "weapon_pipebomb_water_child_fire" // Cluster explosion
175#define PARTICLE_MINIG "weapon_muzzle_flash_minigun" // Constant white flashing glow
176#define PARTICLE_STEAM "steam_long" // Small water steam
177#define PARTICLE_BLACK "smoke_window" // Large black smoke
178#define PARTICLE_SMOKER "smoker_smokecloud" // Smoker cloud
179#define PARTICLE_IMPACT "impact_steam" // Long water impact, points in 1 direction
180#define PARTICLE_VOMIT "boomer_vomit"
181#define PARTICLE_TRAIL "water_trail_directional"
182#define PARTICLE_SPLASH "weapon_pipebomb_water_splash" // Large water splash
183#define PARTICLE_PIPE1 "weapon_pipebomb" // Explosion
184#define PARTICLE_PIPE2 "weapon_pipebomb_child_fire" // Medium blast
185#define PARTICLE_PIPE3 "weapon_pipebomb_water_child_flash" // Flash
186#define PARTICLE_SHORT "impact_steam_short" // Medium water impact, points in 1 direction
187#define PARTICLE_CHARGE "charger_wall_impact_b"
188#define PARTICLE_FLARE "flare_burning"
189#define PARTICLE_SPIT_T "spitter_projectile_trail_old"
190#define PARTICLE_SPIT_P "spitter_projectile_explode"
191#define PARTICLE_SMOKE "apc_wheel_smoke1"
192#define PARTICLE_DEFIB "item_defibrillator_body"
193#define PARTICLE_ELMOS "st_elmos_fire_cp0"
194#define PARTICLE_TES1 "electrical_arc_01"
195#define PARTICLE_TES2 "electrical_arc_01_system"
196#define PARTICLE_TES3 "st_elmos_fire"
197#define PARTICLE_TES6 "impact_ricochet_sparks"
198#define PARTICLE_TES7 "railroad_wheel_sparks"
199#define PARTICLE_GSPARKS "sparks_generic_random"
200#define PARTICLE_SPARKS "fireworks_sparkshower_01e"
201
202#define SOUND_SHOOTING "weapons/flash/flash01.wav"
203#define SOUND_EXPLODE3 "weapons/hegrenade/explode3.wav"
204#define SOUND_EXPLODE5 "weapons/hegrenade/explode5.wav"
205#define SOUND_FIREWORK1 "ambient/atmosphere/firewerks_burst_01.wav"
206#define SOUND_FIREWORK2 "ambient/atmosphere/firewerks_burst_02.wav"
207#define SOUND_FIREWORK3 "ambient/atmosphere/firewerks_burst_03.wav"
208#define SOUND_FIREWORK4 "ambient/atmosphere/firewerks_burst_04.wav"
209#define SOUND_FREEZER "physics/glass/glass_impact_bullet4.wav"
210#define SOUND_BUTTON1 "buttons/blip2.wav"
211#define SOUND_BUTTON2 "ui/menu_countdown.wav"
212#define SOUND_FLICKER "ambient/spacial_loops/lights_flicker.wav"
213#define SOUND_GAS "ambient/gas/cannister_loop.wav"
214#define SOUND_GATE "ambient/machines/floodgate_stop1.wav"
215#define SOUND_NOISE "ambient/atmosphere/noise2.wav"
216#define SOUND_SPATIAL "ambient/spacial_loops/computer_spatial_amb_loop.wav"
217#define SOUND_SQUEAK "ambient/random_amb_sfx/randommetalsqueak01.wav"
218#define SOUND_STEAM "ambient/gas/steam_loop1.wav"
219#define SOUND_TUNNEL "ambient/atmosphere/tunnel1.wav"
220#define SOUND_SPLASH1 "ambient/water/water_splash1.wav"
221#define SOUND_SPLASH2 "ambient/water/water_splash2.wav"
222#define SOUND_SPLASH3 "ambient/water/water_splash3.wav"
223
224#define SPRITE_BEAM "materials/sprites/laserbeam.vmt"
225#define SPRITE_HALO "materials/sprites/glow01.vmt"
226#define SPRITE_GLOW "sprites/blueglow1.vmt"
227// L4D2 client? is missing "sprites/blueglow1.vmt" - used by env_entity_dissolver.
228// Precache prevents server's error message, and clients can attempt to precache before round_start to avoid any possible stutter on the first attempt live in-game
229// Error messages:
230// Client: Unable to load sprite material materials/sprites/blueglow1.vmt!
231// Server: Late precache of sprites/blueglow1.vmt
232
233static const char g_sSoundsHit[][] =
234{
235 "physics/plastic/plastic_barrel_impact_soft1.wav",
236 "physics/plastic/plastic_barrel_impact_soft2.wav",
237 "physics/plastic/plastic_barrel_impact_soft3.wav",
238 "physics/plastic/plastic_barrel_impact_soft4.wav",
239 "physics/plastic/plastic_barrel_impact_soft5.wav",
240 "physics/plastic/plastic_barrel_impact_soft6.wav"
241};
242
243static const char g_sSoundsMiss[][] =
244{
245 "weapons/fx/nearmiss/bulletltor08.wav",
246 "weapons/fx/nearmiss/bulletltor10.wav",
247 "weapons/fx/nearmiss/bulletltor11.wav",
248 "weapons/fx/nearmiss/bulletltor13.wav",
249 "weapons/fx/nearmiss/bulletltor14.wav"
250};
251
252static const char g_sSoundsZap[][] =
253{
254 "ambient/energy/zap1.wav",
255 "ambient/energy/zap2.wav",
256 "ambient/energy/zap3.wav",
257 "ambient/energy/zap5.wav",
258 "ambient/energy/zap6.wav",
259 "ambient/energy/zap7.wav",
260 "ambient/energy/zap8.wav",
261 "ambient/energy/zap9.wav"
262};
263
264// Grenade vocalizations unused by default. Maybe more, only briefly checked.
265static const char g_sSoundsMissing[][] =
266{
267 "player/survivor/voice/gambler/grenade10.wav",
268 "player/survivor/voice/gambler/grenade12.wav"
269};
270
271
272
273// ARRAYS etc
274#define MAX_ENTS 2048 // Max ents
275#define MAX_DATA 16 // Total data entries to read from a grenades config.
276#define MAX_TYPES 18 // Number of grenade types.
277#define MAX_WAIT 0.2 // Delay between +USE mode changes.
278#define BEAM_OFFSET 100.0 // Increase beam diameter by this value to correct visual size.
279#define BEAM_RINGS 5 // Number of beam rings.
280#define SHAKE_RANGE 150.0 // How far to increase the shake from the effect range.
281
282float g_fLastTesla[MAX_ENTS]; // Last time damage taken, used by Tesla mode.
283float g_fLastFreeze[MAXPLAYERS+1]; // Last time in the freezer area.
284float g_fLastShield[MAXPLAYERS+1]; // Last time in the shield, for damage hook.
285float g_fLastUse[MAXPLAYERS+1]; // Clients last time pressing +USE.
286bool g_bChangingTypesMenu[MAXPLAYERS+1]; // Store when clients are changing type, to close menu when ended.
287bool g_bMiddleMouseActivation[MAXPLAYERS+1]; // Whether menu activated using middle mouse button
288int g_iClientGrenadeType[MAXPLAYERS+1] = { -1, ... }; // The current mode a player has selected
289int g_iClientGrenadePref[MAXPLAYERS+1][3]; // Client cookie preferences - mode client last used for all grenades
290
291float g_GrenadeData[MAX_TYPES][MAX_DATA]; // Config data for all grenade modes.
292int g_GrenadeSlot[MAX_TYPES][2]; // [0]=L4D2, [1]=L4D1. Which grenade slot the grenade mode uses.
293int g_GrenadeTarg[MAX_TYPES]; // Who the grenade affects.
294int g_BeamSprite, g_HaloSprite; // Beam Rings
295int g_iConfigBots; // Can bots use Prototype Grenades
296int g_iConfigStock; // Which grenades have their default feature.
297int g_iConfigTypes; // Which grenade modes are allowed.
298int g_iConfigBinds; // Menu or Pressing keys to change mode.
299int g_iConfigMsgs; // Display chat messages?
300int g_iConfigPrefs; // Client preferences save/load mode or give random mode.
301float g_fConfigSurvivors; // Survivors damage multiplier.
302float g_fConfigSpecial; // Special Infected damage multiplier.
303float g_fConfigTank; // Tank damage multiplier.
304float g_fConfigWitch; // Witch damage multiplier.
305float g_fConfigPhysics; // Physics props damage multiplier.
306int g_iEntityHurt; // Hurt entity.
307int g_iParticleTracer; // Particle index for TE.
308int g_iParticleTracer50;
309int g_iParticleBashed;
310UserMsg g_FadeUserMsgId;
311
312
313// VARS
314Handle sdkDissolveCreate, sdkActivateSpit, sdkStaggerClient, sdkDeafenClient;
315ConVar g_hCvarAllow, g_hDecayDecay, g_hCvarMPGameMode, g_hCvarModes, g_hCvarModesOff, g_hCvarModesTog;
316bool g_bCvarAllow, g_bLeft4Dead2, g_bLateLoad, g_bHookFire, g_bBlockHook, g_bBlockSound;
317Handle g_hCookie;
318
319enum ()
320{
321 INDEX_BOMB = 0,
322 INDEX_CLUSTER,
323 INDEX_FIREWORK,
324 INDEX_SMOKE,
325 INDEX_BLACKHOLE,
326 INDEX_FLASHBANG,
327 INDEX_SHIELD,
328 INDEX_TESLA,
329 INDEX_CHEMICAL,
330 INDEX_FREEZER,
331 INDEX_MEDIC,
332 INDEX_VAPORIZER,
333 INDEX_EXTINGUISHER,
334 INDEX_GLOW,
335 INDEX_ANTIGRAVITY,
336 INDEX_FIRECLUSTER,
337 INDEX_BULLETS,
338 INDEX_FLAK
339}
340
341enum ()
342{
343 CONFIG_ELASTICITY = 0,
344 CONFIG_GRAVITY,
345 CONFIG_DMG_PHYSICS,
346 CONFIG_DMG_SPECIAL,
347 CONFIG_DMG_SURVIVORS,
348 CONFIG_DMG_TANK,
349 CONFIG_DMG_WITCH,
350 CONFIG_DAMAGE,
351 CONFIG_DMG_TICK,
352 CONFIG_FUSE,
353 CONFIG_SHAKE,
354 CONFIG_STICK,
355 CONFIG_STUMBLE,
356 CONFIG_RANGE,
357 CONFIG_TICK,
358 CONFIG_TIME
359}
360
361enum ()
362{
363 TARGET_COMMON = 0,
364 TARGET_SURVIVOR,
365 TARGET_SPECIAL,
366 TARGET_TANK,
367 TARGET_WITCH,
368 TARGET_PHYSICS
369}
370
371
372
373// ====================================================================================================
374// PLUGIN INFO / START / END
375// ====================================================================================================
376public Plugin myinfo =
377{
378 name = "[L4D & L4D2] Prototype Grenades",
379 author = "SilverShot",
380 description = "Creates a selection of different grenade types.",
381 version = PLUGIN_VERSION,
382 url = "https://forums.alliedmods.net/showthread.php?t=318965"
383}
384
385public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
386{
387 EngineVersion test = GetEngineVersion();
388 if( test == Engine_Left4Dead ) g_bLeft4Dead2 = false;
389 else if( test == Engine_Left4Dead2 ) g_bLeft4Dead2 = true;
390 else
391 {
392 strcopy(error, err_max, "Plugin only supports Left 4 Dead 1 & 2.");
393 return APLRes_SilentFailure;
394 }
395
396 MarkNativeAsOptional("LMC_GetEntityOverlayModel"); // LMC
397
398 g_bLateLoad = late;
399 return APLRes_Success;
400}
401
402public void OnAllPluginsLoaded()
403{
404 bLMC_Available = LibraryExists("LMCEDeathHandler");
405}
406
407public void OnLibraryAdded(const char[] sName)
408{
409 if(StrEqual(sName, "LMCEDeathHandler"))
410 bLMC_Available = true;
411}
412
413public void OnLibraryRemoved(const char[] sName)
414{
415 if(StrEqual(sName, "LMCEDeathHandler"))
416 bLMC_Available = false;
417}
418
419public void OnPluginStart()
420{
421 // ====================================================================================================
422 // GAMEDATA
423 // ====================================================================================================
424 Handle hGamedata = LoadGameConfigFile(GAMEDATA);
425 if( hGamedata == null ) SetFailState("Failed to load \"%s.txt\" gamedata.", GAMEDATA);
426
427 // Deafen
428 StartPrepSDKCall(SDKCall_Player);
429 if( PrepSDKCall_SetFromConf(hGamedata, SDKConf_Virtual, "CTerrorPlayer::Deafen") == false )
430 SetFailState("Failed to find signature: CTerrorPlayer::Deafen");
431 PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain);
432 PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain);
433 PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain);
434 sdkDeafenClient = EndPrepSDKCall();
435 if( sdkDeafenClient == null )
436 SetFailState("Failed to create SDKCall: CTerrorPlayer::Deafen");
437
438 // Dissolve
439 StartPrepSDKCall(SDKCall_Static);
440 if( PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CEntityDissolve_Create") == false )
441 SetFailState("Could not load the \"CEntityDissolve_Create\" gamedata signature.");
442 PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer);
443 PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer);
444 PrepSDKCall_AddParameter(SDKType_Float, SDKPass_Plain);
445 PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain);
446 PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain);
447 PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer);
448 sdkDissolveCreate = EndPrepSDKCall();
449 if( sdkDissolveCreate == null )
450 SetFailState("Could not prep the \"CEntityDissolve_Create\" function.");
451
452 // Stagger
453 StartPrepSDKCall(SDKCall_Player);
454 if( PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CTerrorPlayer::OnStaggered") == false )
455 SetFailState("Could not load the 'CTerrorPlayer::OnStaggered' gamedata signature.");
456 PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer);
457 PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_Pointer);
458 sdkStaggerClient = EndPrepSDKCall();
459 if( sdkStaggerClient == null )
460 SetFailState("Could not prep the 'CTerrorPlayer::OnStaggered' function.");
461
462 // Spitter Projectile
463 if( g_bLeft4Dead2 )
464 {
465 StartPrepSDKCall(SDKCall_Static);
466 if( PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CSpitterProjectile_Create") == false )
467 SetFailState("Could not load the \"CSpitterProjectile_Create\" gamedata signature.");
468 PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
469 PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
470 PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
471 PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef);
472 PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer);
473 PrepSDKCall_SetReturnInfo(SDKType_CBaseEntity, SDKPass_Pointer);
474 sdkActivateSpit = EndPrepSDKCall();
475 if( sdkActivateSpit == null )
476 SetFailState("Could not prep the \"CSpitterProjectile_Create\" function.");
477 }
478
479 delete hGamedata;
480
481
482
483 // ====================================================================================================
484 // CVARS
485 // ====================================================================================================
486 g_hCvarAllow = CreateConVar( "l4d_grenades_allow", "1", "0=Plugin off, 1=Plugin on.", CVAR_FLAGS );
487 g_hCvarModes = CreateConVar( "l4d_grenades_modes", "", "Turn on the plugin in these game modes, separate by commas (no spaces). (Empty = all).", CVAR_FLAGS );
488 g_hCvarModesOff = CreateConVar( "l4d_grenades_modes_off", "", "Turn off the plugin in these game modes, separate by commas (no spaces). (Empty = none).", CVAR_FLAGS );
489 g_hCvarModesTog = CreateConVar( "l4d_grenades_modes_tog", "0", "Turn on the plugin in these game modes. 0=All, 1=Coop, 2=Survival, 4=Versus, 8=Scavenge. Add numbers together.", CVAR_FLAGS );
490 CreateConVar( "l4d_grenades_version", PLUGIN_VERSION, "Prototype Grenades plugin version.", CVAR_FLAGS|FCVAR_DONTRECORD);
491 AutoExecConfig(true, "l4d_grenades");
492
493 g_hDecayDecay = FindConVar("pain_pills_decay_rate");
494 g_hCvarMPGameMode = FindConVar("mp_gamemode");
495 g_hCvarMPGameMode.AddChangeHook(ConVarChanged_Allow);
496 g_hCvarModesTog.AddChangeHook(ConVarChanged_Allow);
497 g_hCvarModes.AddChangeHook(ConVarChanged_Allow);
498 g_hCvarModesOff.AddChangeHook(ConVarChanged_Allow);
499 g_hCvarAllow.AddChangeHook(ConVarChanged_Allow);
500
501
502
503 // ====================================================================================================
504 // COMMANDS
505 // ====================================================================================================
506 RegConsoleCmd("sm_grenade", Cmd_Grenade, "Opens a menu to choose the current grenades mode. Force change with args, usage: sm_grenade [type: 1 - 18]");
507 RegAdminCmd("sm_grenade_reload", Cmd_Reload, ADMFLAG_ROOT, "Reloads the settings config.");
508 RegAdminCmd("sm_grenade_spawn", Cmd_SpawnSpawn, ADMFLAG_ROOT, "Spawn grenade explosions: <type: 1 - 18>");
509 RegAdminCmd("sm_grenade_throw", Cmd_SpawnThrow, ADMFLAG_ROOT, "Spawn grenade projectile: <type: 1 - 18>");
510
511
512
513 // ====================================================================================================
514 // OTHER
515 // ====================================================================================================
516 // Translations
517 char sPath[PLATFORM_MAX_PATH];
518 BuildPath(Path_SM, sPath, sizeof(sPath), "translations/grenades.phrases.txt");
519 if( !FileExists(sPath) )
520 SetFailState("Required translation file is missing: 'translations/grenades.phrases.txt'");
521
522 LoadTranslations("grenades.phrases");
523
524
525
526 // Saved client options
527 g_hCookie = RegClientCookie("l4d_grenades_modes", "Prototype Grenades - Modes", CookieAccess_Protected);
528
529
530
531 // UserMsg
532 g_FadeUserMsgId = GetUserMessageId("Fade");
533
534
535
536 // Late load
537 if( g_bLateLoad )
538 {
539 LoadDataConfig();
540 IsAllowed();
541 }
542}
543
544public void OnPluginEnd()
545{
546 ResetPlugin(true);
547}
548
549
550
551// ====================================================================================================
552// CLIENT PREFS
553// ====================================================================================================
554public void OnClientPutInServer(int client)
555{
556 if( g_bCvarAllow )
557 {
558 SDKHook(client, SDKHook_WeaponEquip, OnWeaponEquip);
559 SDKHook(client, SDKHook_WeaponDrop, OnWeaponDrop);
560 }
561}
562
563public void OnClientCookiesCached(int client)
564{
565 if( g_bCvarAllow )
566 {
567 if( g_iConfigPrefs != 1 )
568 g_iClientGrenadeType[client] = -1;
569 }
570
571 if( !IsFakeClient(client) )
572 {
573 // Get client cookies, set type if available or default.
574 static char sCookie[10];
575 static char sChars[3][3];
576 GetClientCookie(client, g_hCookie, sCookie, sizeof(sCookie));
577
578 if( strlen(sCookie) >= 5 )
579 {
580 ExplodeString(sCookie, ",", sChars, sizeof sChars, sizeof sChars[]);
581 } else {
582 sChars[0] = "0";
583 sChars[1] = "0";
584 sChars[2] = "0";
585 }
586
587 g_iClientGrenadePref[client][0] = StringToInt(sChars[0]);
588 g_iClientGrenadePref[client][1] = StringToInt(sChars[1]);
589 g_iClientGrenadePref[client][2] = StringToInt(sChars[2]);
590 }
591}
592
593void SetCurrentNadePref(int client)
594{
595 // Get current nade if applicable.
596 if( IsPlayerAlive(client) )
597 {
598 int weapon = GetPlayerWeaponSlot(client, 2);
599 if( weapon != -1 )
600 {
601 OnWeaponEquip(client, weapon);
602 }
603 }
604}
605
606void SetClientPrefs(int client)
607{
608 if( !IsFakeClient(client) )
609 {
610 static char sCookie[10];
611 Format(sCookie, sizeof sCookie, "%d,%d,%d", g_iClientGrenadePref[client][0], g_iClientGrenadePref[client][1], g_iClientGrenadePref[client][2]);
612 SetClientCookie(client, g_hCookie, sCookie);
613 }
614}
615
616
617
618// ====================================================================================================
619// COMMANDS
620// ====================================================================================================
621public Action Cmd_Reload(int client, int args)
622{
623 LoadDataConfig();
624 PrintToChat(client, "\x04[\x05Grenade\x04] Reloaded config");
625 return Plugin_Handled;
626}
627
628public Action Cmd_SpawnThrow(int client, int args)
629{
630 DoSpawnCommand(client, args, true);
631 return Plugin_Handled;
632}
633
634public Action Cmd_SpawnSpawn(int client, int args)
635{
636 DoSpawnCommand(client, args, false);
637 return Plugin_Handled;
638}
639
640void DoSpawnCommand(int client, int args, bool projectile)
641{
642 // Validate
643 if( !client )
644 {
645 ReplyToCommand(client, "Command can only be used in game.");
646 return;
647 }
648
649 if( args != 1 )
650 {
651 ReplyToCommand(client, "Usage: sm_grenade_%s <1=Bomb, 2=Cluster, 3=Firework, 4=Smoke, 5=Black Hole, 6=Flashbang, 7=Shield, 8=Tesla, 9=Chemical, 10=Freeze, 11=Medic, 12=Vaporizer, 13=Extinguisher, 14=Glow, 15=Anti-Gravity, 16=Fire Cluster, 17=Bullets, 18=Flak>", projectile ? "throw" : "spawn");
652 return;
653 }
654
655 // Index
656 char sTemp[4];
657 GetCmdArg(1, sTemp, sizeof sTemp);
658 int index = StringToInt(sTemp);
659
660 if( index < 1 || index > MAX_TYPES )
661 {
662 ReplyToCommand(client, "Usage: sm_grenade_%s <1=Bomb, 2=Cluster, 3=Firework, 4=Smoke, 5=Black Hole, 6=Flashbang, 7=Shield, 8=Tesla, 9=Chemical, 10=Freeze, 11=Medic, 12=Vaporizer, 13=Extinguisher, 14=Glow, 15=Anti-Gravity, 16=Fire Cluster, 17=Bullets, 18=Flak>", projectile ? "throw" : "spawn");
663 return;
664 }
665
666 // Create
667 int entity = CreateEntityByName("pipe_bomb_projectile");
668 if( entity != -1 )
669 {
670 SetEntityModel(entity, MODEL_SPRAYCAN);
671 SetEntProp(entity, Prop_Data, "m_iHammerID", index); // Store mode type
672 SetEntPropEnt(entity, Prop_Send, "m_hThrower", client); // Store owner
673 SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", client); // Store owner
674 SetEntPropVector(entity, Prop_Send, "m_vInitialVelocity", view_as<float>({ 0.0, 0.0, 1.0 }));
675 g_iClientGrenadeType[client] = index;
676
677 float vPos[3];
678 if( projectile )
679 {
680 float vAng[3];
681 float vDir[3];
682 GetClientEyePosition(client, vPos);
683 GetClientEyeAngles(client, vAng);
684
685 GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR);
686 vPos[0] += vDir[0] * 20;
687 vPos[1] += vDir[1] * 20;
688 vPos[2] += vDir[2] * 20;
689
690 NormalizeVector(vDir, vDir);
691 ScaleVector(vDir, 600.0);
692 vDir[2] += 200.0;
693 TeleportEntity(entity, vPos, NULL_VECTOR, vDir);
694 SetEntPropVector(entity, Prop_Send, "m_vInitialVelocity", vDir);
695 } else {
696 SetTeleportEndPoint(client, vPos);
697 vPos[2] += 20.0;
698 TeleportEntity(entity, vPos, NULL_VECTOR, NULL_VECTOR);
699 }
700 DispatchSpawn(entity);
701
702 static char translation[256];
703 Format(translation, sizeof translation, "GrenadeMod_Title_%d", index);
704 PrintToChat(client, "\x04[\x05Grenade\x04] \x05Created: \x04%T", translation, client);
705 }
706}
707
708
709
710// ====================================================================================================
711// MENU
712// ====================================================================================================
713public Action Cmd_Grenade(int client, int args)
714{
715 if( !client )
716 {
717 ReplyToCommand(client, "Command can only be used in game.");
718 return Plugin_Handled;
719 }
720
721 // If grenade mode not allowed to change
722 if( g_bCvarAllow == false || g_iConfigPrefs == 3 )
723 {
724 return Plugin_Handled;
725 }
726
727 if( args == 0 )
728 {
729 ShowGrenadeMenu(client);
730 } else {
731 // Validate weapon
732 int iWeapon = GetPlayerWeaponSlot(client, 2);
733 if( iWeapon > MaxClients && IsValidEntity(iWeapon) )
734 {
735 int type = IsGrenade(iWeapon);
736 if( type )
737 {
738 char temp[4];
739 GetCmdArg(1, temp, sizeof temp);
740 int index = StringToInt(temp);
741
742 // Validate index
743 if( index >= 0 && index <= MAX_TYPES )
744 {
745 g_iClientGrenadeType[client] = index - 1;
746 GetGrenadeIndex(client, type); // Iterate to valid index.
747 index = g_iClientGrenadeType[client];
748
749 static char translation[256];
750 Format(translation, sizeof translation, "GrenadeMod_Title_%d", index);
751 Format(translation, sizeof translation, "%T %T", "GrenadeMod_Mode", client, translation, client);
752 ReplaceColors(translation, sizeof translation);
753 PrintToChat(client, translation);
754 }
755 }
756 }
757 }
758
759 return Plugin_Handled;
760}
761
762void ShowGrenadeMenu(int client)
763{
764 // Validate weapon
765 int iWeapon = GetPlayerWeaponSlot(client, 2);
766 if( iWeapon > MaxClients && IsValidEntity(iWeapon) )
767 {
768 int type = IsGrenade(iWeapon);
769 if( type )
770 {
771 // Create menu
772 Menu menu = new Menu(Menu_Grenade);
773 static char text[64];
774 static char temp[4];
775
776 Format(text, sizeof(text), "%T", "GrenadeMenu_Title", client);
777 menu.SetTitle(text);
778
779 // Cycle through valid modes
780 int selected;
781 int count;
782 int index;
783 bool ins;
784
785 for( int i = -1; i < MAX_TYPES; i++ )
786 {
787 ins = false;
788
789 if( i == -1 )
790 {
791 index = 0;
792 if( g_iConfigStock & (1<<(type - 1)) )
793 {
794 ins = true;
795 }
796 } else {
797 if( g_GrenadeSlot[i][!g_bLeft4Dead2] & (1<<type - 1) && g_iConfigTypes & (1<<i) )
798 {
799 ins = true;
800 index = i + 1;
801 }
802 }
803
804 // Add to menu
805 if( ins )
806 {
807
808 if( index == g_iClientGrenadeType[client] )
809 selected = count;
810
811 count++;
812
813 IntToString(index, temp, sizeof temp);
814 Format(text, sizeof text, "GrenadeMod_Title_%d", index);
815 Format(text, sizeof text, "%s%T", index == g_iClientGrenadeType[client] ? "(*) " : "", text, client); // Mark selected
816 menu.AddItem(temp, text);
817 }
818 }
819
820 // Display
821 menu.ExitButton = true;
822 if (g_bMiddleMouseActivation[client])
823 {
824 menu.Display(client, 30);
825 }
826 else {
827 menu.DisplayAt(client, 7 * RoundToFloor(selected / 7.0), 30); // Display on selected page
828 }
829 return;
830 }
831 }
832
833 static char translation[256];
834 Format(translation, sizeof translation, "%T", "GrenadeMenu_Invalid", client);
835 ReplaceColors(translation, sizeof translation);
836 PrintToChat(client, translation);
837}
838
839public int Menu_Grenade(Menu menu, MenuAction action, int client, int index)
840{
841 switch( action )
842 {
843 case MenuAction_Select:
844 {
845 static char translation[256];
846
847 // Validate weapon
848 int iWeapon = GetPlayerWeaponSlot(client, 2);
849 if( iWeapon > MaxClients && IsValidEntity(iWeapon) )
850 {
851 int type = IsGrenade(iWeapon);
852 if( type )
853 {
854 // Get index
855 static char sTemp[4];
856 menu.GetItem(index, sTemp, sizeof(sTemp));
857 index = StringToInt(sTemp);
858
859 // Validate index
860 g_iClientGrenadeType[client] = index - 1; // Iterate to valid index.
861 GetGrenadeIndex(client, type);
862 index = g_iClientGrenadeType[client];
863 SetEntProp(iWeapon, Prop_Data, "m_iHammerID", index + 1);
864
865 // Print
866 Format(translation, sizeof translation, "GrenadeMod_Title_%d", index);
867 Format(translation, sizeof translation, "%T %T", "GrenadeMod_Mode", client, translation, client);
868 ReplaceColors(translation, sizeof translation);
869 PrintToChat(client, translation);
870
871 // Redisplay menu
872 //ShowGrenadeMenu(client);
873 }
874 } else {
875 Format(translation, sizeof translation, "%T", "GrenadeMenu_Invalid", client);
876 ReplaceColors(translation, sizeof translation);
877 PrintToChat(client, translation);
878 }
879 }
880 case MenuAction_End:
881 delete menu;
882 }
883}
884
885
886
887// ====================================================================================================
888// CVARS
889// ====================================================================================================
890public void OnConfigsExecuted()
891{
892 IsAllowed();
893}
894
895public void ConVarChanged_Allow(Handle convar, const char[] oldValue, const char[] newValue)
896{
897 IsAllowed();
898}
899
900void IsAllowed()
901{
902 bool bCvarAllow = g_hCvarAllow.BoolValue;
903 bool bAllowMode = IsAllowedGameMode();
904
905 if( g_bCvarAllow == false && bCvarAllow == true && bAllowMode == true )
906 {
907 g_bCvarAllow = true;
908
909 AddNormalSoundHook(view_as<NormalSHook>(SoundHook));
910 HookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
911 HookEvent("bot_player_replace", Event_BotReplace);
912
913 for( int i = 1; i <= MaxClients; i++ )
914 if( IsClientInGame(i) )
915 {
916 // Hook WeaponEquip, get cookies
917 OnClientPutInServer(i);
918 OnClientCookiesCached(i);
919 SetCurrentNadePref(i);
920 }
921 }
922
923 else if( g_bCvarAllow == true && (bCvarAllow == false || bAllowMode == false) )
924 {
925 ResetPlugin(true);
926 g_bCvarAllow = false;
927
928 RemoveNormalSoundHook(view_as<NormalSHook>(SoundHook));
929 UnhookEvent("round_end", Event_RoundEnd, EventHookMode_PostNoCopy);
930 UnhookEvent("bot_player_replace", Event_BotReplace);
931 }
932}
933
934int g_iCurrentMode;
935bool IsAllowedGameMode()
936{
937 if( g_hCvarMPGameMode == null )
938 return false;
939
940 int iCvarModesTog = g_hCvarModesTog.IntValue;
941 if( iCvarModesTog != 0 )
942 {
943 g_iCurrentMode = 0;
944
945 int entity = CreateEntityByName("info_gamemode");
946 DispatchSpawn(entity);
947 HookSingleEntityOutput(entity, "OnCoop", OnGamemode, true);
948 HookSingleEntityOutput(entity, "OnSurvival", OnGamemode, true);
949 HookSingleEntityOutput(entity, "OnVersus", OnGamemode, true);
950 HookSingleEntityOutput(entity, "OnScavenge", OnGamemode, true);
951 ActivateEntity(entity);
952 AcceptEntityInput(entity, "PostSpawnActivate");
953 AcceptEntityInput(entity, "Kill");
954
955 if( g_iCurrentMode == 0 )
956 return false;
957
958 if( !(iCvarModesTog & g_iCurrentMode) )
959 return false;
960 }
961
962 char sGameModes[64], sGameMode[64];
963 g_hCvarMPGameMode.GetString(sGameMode, sizeof(sGameMode));
964 Format(sGameMode, sizeof(sGameMode), ",%s,", sGameMode);
965
966 g_hCvarModes.GetString(sGameModes, sizeof(sGameModes));
967 if( strcmp(sGameModes, "") )
968 {
969 Format(sGameModes, sizeof(sGameModes), ",%s,", sGameModes);
970 if( StrContains(sGameModes, sGameMode, false) == -1 )
971 return false;
972 }
973
974 g_hCvarModesOff.GetString(sGameModes, sizeof(sGameModes));
975 if( strcmp(sGameModes, "") )
976 {
977 Format(sGameModes, sizeof(sGameModes), ",%s,", sGameModes);
978 if( StrContains(sGameModes, sGameMode, false) != -1 )
979 return false;
980 }
981
982 return true;
983}
984
985public void OnGamemode(const char[] output, int caller, int activator, float delay)
986{
987 if( strcmp(output, "OnCoop") == 0 )
988 g_iCurrentMode = 1;
989 else if( strcmp(output, "OnSurvival") == 0 )
990 g_iCurrentMode = 2;
991 else if( strcmp(output, "OnVersus") == 0 )
992 g_iCurrentMode = 4;
993 else if( strcmp(output, "OnScavenge") == 0 )
994 g_iCurrentMode = 8;
995}
996
997
998
999// ====================================================================================================
1000// MAP END / START
1001// ====================================================================================================
1002public void OnMapEnd()
1003{
1004 ResetPlugin(true);
1005}
1006
1007public void OnMapStart()
1008{
1009 // PRECACHE
1010 PrecacheModel(MODEL_BOUNDING, true);
1011 PrecacheModel(MODEL_CRATE, true);
1012 PrecacheModel(MODEL_GASCAN, true);
1013 PrecacheModel(MODEL_SPRAYCAN, true);
1014 PrecacheModel(MODEL_SPRITE, true);
1015 PrecacheModel(SPRITE_GLOW, true);
1016 g_BeamSprite = PrecacheModel(SPRITE_BEAM);
1017 g_HaloSprite = PrecacheModel(SPRITE_HALO);
1018
1019 if( g_bLeft4Dead2 )
1020 g_iParticleTracer50 = PrecacheParticle(PARTICLE_TRACER_50);
1021 g_iParticleTracer = PrecacheParticle(PARTICLE_TRACERS);
1022 g_iParticleBashed = PrecacheParticle(PARTICLE_BASHED);
1023 PrecacheParticle(PARTICLE_MUZZLE);
1024 PrecacheParticle(PARTICLE_SMOKER1);
1025 PrecacheParticle(PARTICLE_SMOKER2);
1026 PrecacheParticle(PARTICLE_BOOMER);
1027 PrecacheParticle(PARTICLE_SMOKE2);
1028 PrecacheParticle(PARTICLE_BURST);
1029 PrecacheParticle(PARTICLE_BLAST);
1030 PrecacheParticle(PARTICLE_BLAST2);
1031 PrecacheParticle(PARTICLE_MINIG);
1032 PrecacheParticle(PARTICLE_STEAM);
1033 PrecacheParticle(PARTICLE_BLACK);
1034 PrecacheParticle(PARTICLE_SMOKER);
1035 PrecacheParticle(PARTICLE_IMPACT);
1036 PrecacheParticle(PARTICLE_VOMIT);
1037 PrecacheParticle(PARTICLE_SPLASH);
1038 PrecacheParticle(PARTICLE_PIPE1);
1039 PrecacheParticle(PARTICLE_PIPE2);
1040 PrecacheParticle(PARTICLE_PIPE3);
1041 PrecacheParticle(PARTICLE_SMOKE);
1042 PrecacheParticle(PARTICLE_TES1);
1043 PrecacheParticle(PARTICLE_TES2);
1044 PrecacheParticle(PARTICLE_TES3);
1045 PrecacheParticle(PARTICLE_TES6);
1046 PrecacheParticle(PARTICLE_TES7);
1047 PrecacheParticle(PARTICLE_ELMOS);
1048
1049 if( g_bLeft4Dead2 )
1050 {
1051 PrecacheParticle(PARTICLE_SPARKS);
1052 PrecacheParticle(PARTICLE_GSPARKS);
1053 PrecacheParticle(PARTICLE_FLARE);
1054 PrecacheParticle(PARTICLE_SPIT_T);
1055 PrecacheParticle(PARTICLE_SPIT_P);
1056 PrecacheParticle(PARTICLE_DEFIB);
1057
1058 PrecacheSound(SOUND_FIREWORK1, true);
1059 PrecacheSound(SOUND_FIREWORK2, true);
1060 PrecacheSound(SOUND_FIREWORK3, true);
1061 PrecacheSound(SOUND_FIREWORK4, true);
1062 }
1063
1064 PrecacheSound(SOUND_SHOOTING, true);
1065 PrecacheSound(SOUND_EXPLODE3, true);
1066 PrecacheSound(SOUND_EXPLODE5, true);
1067 PrecacheSound(SOUND_FREEZER, true);
1068 PrecacheSound(SOUND_BUTTON1, true);
1069 PrecacheSound(SOUND_BUTTON2, true);
1070 PrecacheSound(SOUND_FLICKER, true);
1071 PrecacheSound(SOUND_GAS, true);
1072 PrecacheSound(SOUND_GATE, true);
1073 PrecacheSound(SOUND_NOISE, true);
1074 PrecacheSound(SOUND_SPATIAL, true);
1075 PrecacheSound(SOUND_SQUEAK, true);
1076 PrecacheSound(SOUND_STEAM, true);
1077 PrecacheSound(SOUND_TUNNEL, true);
1078 PrecacheSound(SOUND_SPLASH1, true);
1079 PrecacheSound(SOUND_SPLASH2, true);
1080 PrecacheSound(SOUND_SPLASH3, true);
1081
1082 for( int i = 0; i < sizeof g_sSoundsHit; i++ ) PrecacheSound(g_sSoundsHit[i], true);
1083 for( int i = 0; i < sizeof g_sSoundsMiss; i++ ) PrecacheSound(g_sSoundsMiss[i], true);
1084 for( int i = 0; i < sizeof g_sSoundsZap; i++ ) PrecacheSound(g_sSoundsZap[i], true);
1085 for( int i = 0; i < sizeof g_sSoundsMissing; i++ ) PrecacheSound(g_sSoundsMissing[i], true);
1086
1087
1088
1089 // LOAD CONFIG
1090 if( g_bLateLoad )
1091 {
1092 g_bLateLoad = false; // No double load from lateload
1093 } else {
1094 LoadDataConfig();
1095 }
1096}
1097
1098
1099
1100// ====================================================================================================
1101// CONFIG
1102// ====================================================================================================
1103void LoadDataEntry(int index, KeyValues hFile, const char[] KeyName)
1104{
1105 if( hFile.JumpToKey(KeyName) )
1106 {
1107 g_GrenadeData[index][CONFIG_ELASTICITY] = hFile.GetFloat("elasticity", 0.4);
1108 g_GrenadeData[index][CONFIG_GRAVITY] = hFile.GetFloat("gravity", 1.0);
1109
1110 g_GrenadeData[index][CONFIG_DMG_PHYSICS] = hFile.GetFloat("damage_physics", 1.0);
1111 g_GrenadeData[index][CONFIG_DMG_SPECIAL] = hFile.GetFloat("damage_special", 1.0);
1112 g_GrenadeData[index][CONFIG_DMG_SURVIVORS] = hFile.GetFloat("damage_survivors", 1.0);
1113 g_GrenadeData[index][CONFIG_DMG_TANK] = hFile.GetFloat("damage_tank", 1.0);
1114 g_GrenadeData[index][CONFIG_DMG_WITCH] = hFile.GetFloat("damage_witch", 1.0);
1115 g_GrenadeData[index][CONFIG_DAMAGE] = hFile.GetFloat("damage", 1.0);
1116 g_GrenadeData[index][CONFIG_DMG_TICK] = hFile.GetFloat("damage_tick", 1.0);
1117
1118 g_GrenadeData[index][CONFIG_FUSE] = hFile.GetFloat("detonate_fuse", 0.0);
1119 g_GrenadeData[index][CONFIG_SHAKE] = hFile.GetFloat("detonate_shake", 0.0);
1120 g_GrenadeData[index][CONFIG_STICK] = hFile.GetFloat("detonate_stick", 0.0);
1121 g_GrenadeData[index][CONFIG_STUMBLE] = hFile.GetFloat("range_stumble", 0.0);
1122 g_GrenadeData[index][CONFIG_RANGE] = hFile.GetFloat("effect_range", 0.0);
1123 g_GrenadeData[index][CONFIG_TICK] = hFile.GetFloat("effect_tick", 0.0);
1124 g_GrenadeData[index][CONFIG_TIME] = hFile.GetFloat("effect_time", 0.0);
1125 g_GrenadeSlot[index][0] = hFile.GetNum("nade", 1);
1126 g_GrenadeSlot[index][1] = hFile.GetNum("nade_l4d1", 0);
1127 g_GrenadeTarg[index] = hFile.GetNum("targets", 31);
1128 }
1129 hFile.Rewind();
1130}
1131
1132void LoadDataConfig()
1133{
1134 // Load Config
1135 char sPath[PLATFORM_MAX_PATH];
1136 BuildPath(Path_SM, sPath, sizeof(sPath), CONFIG_DATA);
1137 if( !FileExists(sPath) )
1138 {
1139 SetFailState("Missing config '%s' please re-install.", CONFIG_DATA);
1140 }
1141
1142 // Import
1143 KeyValues hFile = new KeyValues("Grenades");
1144 if( !hFile.ImportFromFile(sPath) )
1145 {
1146 delete hFile;
1147 SetFailState("Error reading config '%s' please re-install.", CONFIG_DATA);
1148 }
1149
1150 // Read
1151 if( hFile.JumpToKey("Settings") )
1152 {
1153 g_iConfigBots = hFile.GetNum("bots", 0);
1154 g_iConfigBots = Clamp(g_iConfigBots, (1<<MAX_TYPES) - 1, 0);
1155
1156 g_iConfigBinds = hFile.GetNum("mode_switch", 3);
1157 g_iConfigBinds = Clamp(g_iConfigBinds, 4, 1);
1158
1159 g_iConfigMsgs = hFile.GetNum("messages", 1);
1160 g_iConfigMsgs = Clamp(g_iConfigMsgs, 1, 0);
1161
1162 g_iConfigPrefs = hFile.GetNum("preferences", 1);
1163 g_iConfigPrefs = Clamp(g_iConfigPrefs, 3, 0);
1164
1165 g_fConfigSurvivors = hFile.GetFloat("damage_survivors", 1.0);
1166 g_fConfigSurvivors = Clamp(g_fConfigSurvivors, 1000.0, 0.0);
1167
1168 g_fConfigSpecial = hFile.GetFloat("damage_special", 1.0);
1169 g_fConfigSpecial = Clamp(g_fConfigSpecial, 1000.0, 0.0);
1170
1171 g_fConfigTank = hFile.GetFloat("damage_tank", 1.0);
1172 g_fConfigTank = Clamp(g_fConfigTank, 1000.0, 0.0);
1173
1174 g_fConfigWitch = hFile.GetFloat("damage_witch", 1.0);
1175 g_fConfigWitch = Clamp(g_fConfigWitch, 1000.0, 0.0);
1176
1177 g_fConfigPhysics = hFile.GetFloat("damage_physics", 1.0);
1178 g_fConfigPhysics = Clamp(g_fConfigPhysics, 1000.0, 0.0);
1179
1180 g_iConfigStock = hFile.GetNum("stocks", 0);
1181 g_iConfigStock = Clamp(g_iConfigStock, 7, 0);
1182
1183 g_iConfigTypes = hFile.GetNum("types", 0);
1184 g_iConfigTypes = Clamp(g_iConfigTypes, (1<<MAX_TYPES) - 1, 0);
1185 hFile.Rewind();
1186 }
1187
1188 LoadDataEntry(INDEX_BOMB, hFile, "Mod_Bomb");
1189 LoadDataEntry(INDEX_CLUSTER, hFile, "Mod_Cluster");
1190 LoadDataEntry(INDEX_FIREWORK, hFile, "Mod_Firework");
1191 LoadDataEntry(INDEX_SMOKE, hFile, "Mod_Smoke");
1192 LoadDataEntry(INDEX_BLACKHOLE, hFile, "Mod_Black_Hole");
1193 LoadDataEntry(INDEX_FLASHBANG, hFile, "Mod_Flashbang");
1194 LoadDataEntry(INDEX_SHIELD, hFile, "Mod_Shield");
1195 LoadDataEntry(INDEX_TESLA, hFile, "Mod_Tesla");
1196 LoadDataEntry(INDEX_CHEMICAL, hFile, "Mod_Chemical");
1197 LoadDataEntry(INDEX_FREEZER, hFile, "Mod_Freezer");
1198 LoadDataEntry(INDEX_MEDIC, hFile, "Mod_Medic");
1199 LoadDataEntry(INDEX_VAPORIZER, hFile, "Mod_Vaporizer");
1200 LoadDataEntry(INDEX_EXTINGUISHER, hFile, "Mod_Extinguisher");
1201 LoadDataEntry(INDEX_GLOW, hFile, "Mod_Glow");
1202 LoadDataEntry(INDEX_ANTIGRAVITY, hFile, "Mod_Anti_Gravity");
1203 LoadDataEntry(INDEX_FIRECLUSTER, hFile, "Mod_Cluster_Fire");
1204 LoadDataEntry(INDEX_BULLETS, hFile, "Mod_Bullets");
1205 LoadDataEntry(INDEX_FLAK, hFile, "Mod_Flak");
1206
1207 delete hFile;
1208}
1209
1210any Clamp(any value, any max, any min = 0.0)
1211{
1212 if( value < min )
1213 value = min;
1214 else if( value > max )
1215 value = max;
1216 return value;
1217}
1218
1219
1220
1221// ====================================================================================================
1222// EVENTS - WEAPON EQUIP
1223// ====================================================================================================
1224public void OnWeaponEquip(int client, int weapon)
1225{
1226 int type = IsGrenade(weapon);
1227 if( type )
1228 {
1229 // Random grenade prefs
1230 if( g_iConfigPrefs != 1 || g_iConfigBots && IsFakeClient(client) )
1231 {
1232 int index = GetEntProp(weapon, Prop_Data, "m_iHammerID"); // Was previously picked up / set type
1233 if( index > 0 )
1234 {
1235 g_iClientGrenadeType[client] = index - 1;
1236 } else {
1237 int types = g_iConfigBots && IsFakeClient(client) ? g_iConfigBots : g_iConfigTypes; // Allowed types for bots / players
1238 int min = MAX_TYPES;
1239 int max;
1240 int slot;
1241
1242 // Cycle through modes
1243 for( int i = 0; i < MAX_TYPES; i++ )
1244 {
1245 if( g_GrenadeSlot[i][!g_bLeft4Dead2] & (1<<type - 1) && types & (1<<i) )
1246 {
1247 if( i < min ) min = i - 1;
1248 if( i > max) max = i;
1249 }
1250 }
1251
1252 slot = GetRandomInt(min, max);
1253 g_iClientGrenadeType[client] = slot == min ? -1 : slot;
1254 GetGrenadeIndex(client, type); // Iterate to valid index.
1255 }
1256 } else {
1257 g_iClientGrenadeType[client] = -1;
1258 }
1259
1260 // Client prefs default
1261 if( g_iConfigPrefs == 0 )
1262 {
1263 g_iClientGrenadeType[client] = 0;
1264 g_iClientGrenadePref[client][type - 1] = 0;
1265 }
1266 else if( g_iClientGrenadeType[client] == -1 )
1267 {
1268 g_iClientGrenadeType[client] = g_iClientGrenadePref[client][type - 1];
1269
1270 if( g_iClientGrenadeType[client] == -1 )
1271 {
1272 ThrowError("OnWeaponEquip == -1. This should never happen.");
1273 }
1274 }
1275
1276 SetEntProp(weapon, Prop_Data, "m_iHammerID", g_iClientGrenadeType[client] + 1); // Store type
1277
1278 // Hints
1279 if( g_bCvarAllow && g_iConfigMsgs && !IsFakeClient(client) )
1280 {
1281 static char translation[256];
1282 Format(translation, sizeof translation, "GrenadeMod_Title_%d", g_iClientGrenadeType[client]);
1283
1284 Format(translation, sizeof translation, "%T %T", "GrenadeMod_Mode", client, translation, client);
1285 ReplaceColors(translation, sizeof translation);
1286 PrintToChat(client, translation);
1287
1288 // If grenade mode allowed to change
1289 if( g_iConfigPrefs != 3 )
1290 {
1291 if( g_iConfigBinds == 2 )
1292 Format(translation, sizeof translation, "%T", "GrenadeMod_Hint2", client);
1293 else
1294 Format(translation, sizeof translation, "%T", "GrenadeMod_Hint", client);
1295
1296 ReplaceColors(translation, sizeof translation);
1297 PrintToChat(client, translation);
1298 }
1299 }
1300 }
1301}
1302
1303public void OnWeaponDrop(int client, int weapon)
1304{
1305 // Random grenade prefs
1306 if( g_iConfigPrefs != 1 && weapon != -1 && IsValidEntity(weapon) )
1307 {
1308 // Validate weapon
1309 int type = IsGrenade(weapon);
1310 if( type )
1311 {
1312 SetEntProp(weapon, Prop_Data, "m_iHammerID", g_iClientGrenadeType[client] + 1);
1313 }
1314 }
1315}
1316
1317
1318
1319// ====================================================================================================
1320// SOUND HOOK
1321// ====================================================================================================
1322// From "boomerjar##.wav" filenames.
1323int g_iGambl_InvalidBile[] = {08, 10};
1324int g_iMecha_InvalidBile[] = {08, 09, 10, 14, 13};
1325int g_iProdu_InvalidBile[] = {05, 07, 08, 09};
1326// From "Grenade##.wav" filenames.
1327int g_iCoach_InvalidNade[] = {08, 09, 10, 11, 12};
1328int g_iGambl_InvalidNade[] = {02, 03, 05, 06, 07, 08, 09, 11, 13};
1329int g_iMecha_InvalidNade[] = {03, 04, 05, 06, 07, 08, 11, 12, 13};
1330int g_iProdu_InvalidNade[] = {02, 03, 04, 05};
1331// Valid alternatives
1332int g_iCoach_ValidSample[] = {01, 02, 03, 04, 05, 06, 07};
1333int g_iGambl_ValidSample[] = {01, 04, 10, 12};
1334int g_iMecha_ValidSample[] = {01, 02, 09, 10};
1335int g_iProdu_ValidSample[] = {01, 06, 07};
1336
1337public Action SoundHook(int clients[64], int &numClients, char sample[PLATFORM_MAX_PATH], int &entity, int &channel, float &volume, int &level, int &pitch, int &flags)
1338{
1339 // Block molotov sound when throwing. Deleting the molotov straight away causes the sound to loop endlessly.
1340 if( g_bBlockSound )
1341 {
1342 if( strcmp(sample, "weapons/molotov/fire_loop_1.wav") == 0 )
1343 {
1344 volume = 0.0;
1345 return Plugin_Changed;
1346 }
1347 }
1348
1349
1350
1351 // Replace Prototype Grenades bounce sound.
1352 // weapons/hegrenade/he_bounce-1.wav
1353 if( sample[0] == 'w' && sample[8] == 'h' && sample[18] == 'h' )
1354 {
1355 if( GetEntProp(entity, Prop_Data, "m_iHammerID") )
1356 {
1357 PlaySound(entity, g_sSoundsHit[GetRandomInt(0, sizeof g_sSoundsHit - 1)]);
1358
1359 volume = 0.0;
1360 // volume = 0.6;
1361 // strcopy(sample, sizeof sample, g_sSoundsHit[GetRandomInt(0, sizeof g_sSoundsHit - 1)]);
1362 return Plugin_Changed;
1363 }
1364 }
1365
1366
1367
1368 // L4D2 survivors only.
1369 // Change players saying "throwing molotov" or "throwing pipebomb" to "throwing grenade" when a Prototype Grenade mode is selected.
1370
1371 // Info for anyone who reads:
1372 // Players can vocalize from other objects, eg an "info_target" entity (see Mic plugin) and not a client index.
1373 // No point doing excess classname checking etc to block the rare vocalizations through non-player entities.
1374 // For other serious vocalization plugins this should be considered. Eg replacing a characters voices with another.
1375 if( g_bLeft4Dead2 && entity > 0 && entity <= MaxClients && g_iClientGrenadeType[entity] )
1376 {
1377 // player/survivor/voice/
1378 if( sample[0] == 'p' && sample[7] == 's' && sample[16] == 'v' )
1379 {
1380 // Coach, Gambler, Mechanic, Producer
1381 int dot;
1382 int pos;
1383 int edit;
1384 switch( sample[22] )
1385 {
1386 case 'G': pos = 30;
1387 case 'M': pos = 31;
1388 case 'P': pos = 31;
1389 }
1390
1391
1392
1393 // "Grenade##.wav"
1394 if( pos && sample[pos] == 'G' && sample[pos + 9] == '.' )
1395 {
1396 dot = 9;
1397 sample[pos + dot] = '\x0';
1398 int num = StringToInt(sample[pos + dot - 2]); // Get grenade vocalize sound number.
1399 sample[pos + dot] = '.';
1400
1401 switch( sample[22] ) // Match invalid number.
1402 {
1403 case 'C': for( int i = 0; i < sizeof g_iCoach_InvalidNade; i++ ) if( num == g_iCoach_InvalidNade[i] ) { edit = g_iCoach_ValidSample[GetRandomInt(0, sizeof g_iCoach_ValidSample - 1)]; break; }
1404 case 'G': for( int i = 0; i < sizeof g_iGambl_InvalidNade; i++ ) if( num == g_iGambl_InvalidNade[i] ) { edit = g_iGambl_ValidSample[GetRandomInt(0, sizeof g_iGambl_ValidSample - 1)]; break; }
1405 case 'M': for( int i = 0; i < sizeof g_iMecha_InvalidNade; i++ ) if( num == g_iMecha_InvalidNade[i] ) { edit = g_iMecha_ValidSample[GetRandomInt(0, sizeof g_iMecha_ValidSample - 1)]; break; }
1406 case 'P': for( int i = 0; i < sizeof g_iProdu_InvalidNade; i++ ) if( num == g_iProdu_InvalidNade[i] ) { edit = g_iProdu_ValidSample[GetRandomInt(0, sizeof g_iProdu_ValidSample - 1)]; break; }
1407 }
1408 }
1409
1410
1411
1412 // boomerjar##.wav
1413 else if( pos && sample[pos] == 'B' && sample[pos + 6] == 'J' && sample[22] != 'C' ) // Not Coach
1414 {
1415 dot = 11;
1416 sample[pos + dot] = '\x0';
1417 int num = StringToInt(sample[pos + dot - 2]); // Get grenade vocalize sound number.
1418 sample[pos + dot] = '.';
1419
1420 switch( sample[22] ) // Match invalid number.
1421 {
1422 case 'G': for( int i = 0; i < sizeof g_iGambl_InvalidBile; i++ ) if( num == g_iGambl_InvalidBile[i] ) { edit = g_iGambl_ValidSample[GetRandomInt(0, sizeof g_iGambl_ValidSample - 1)]; break; }
1423 case 'M': for( int i = 0; i < sizeof g_iMecha_InvalidBile; i++ ) if( num == g_iMecha_InvalidBile[i] ) { edit = g_iMecha_ValidSample[GetRandomInt(0, sizeof g_iMecha_ValidSample - 1)]; break; }
1424 case 'P': for( int i = 0; i < sizeof g_iProdu_InvalidBile; i++ ) if( num == g_iProdu_InvalidBile[i] ) { edit = g_iProdu_ValidSample[GetRandomInt(0, sizeof g_iProdu_ValidSample - 1)]; break; }
1425 }
1426
1427 if( edit ) // Replace name
1428 {
1429 sample[pos] = '\x0';
1430 StrCat(sample, sizeof sample, "Grenade##.wav");
1431 }
1432 }
1433
1434 if( edit ) // Replace invalid number with valid number
1435 {
1436 static char val[3];
1437 Format(val, sizeof val, "%02d", edit);
1438 sample[pos + 7] = val[0];
1439 sample[pos + 8] = val[1];
1440 return Plugin_Changed;
1441 }
1442 }
1443 }
1444
1445 return Plugin_Continue;
1446}
1447
1448
1449
1450// ====================================================================================================
1451// RESET PLUGIN
1452// ====================================================================================================
1453public void Event_BotReplace(Event event, const char[] name, bool dontBroadcast)
1454{
1455 int client = GetClientOfUserId(event.GetInt("player"));
1456 if( client ) SetCurrentNadePref(client);
1457}
1458
1459public void Event_RoundEnd(Event event, const char[] name, bool dontBroadcast)
1460{
1461 ResetPlugin();
1462}
1463
1464void ResetPlugin(bool all = false)
1465{
1466 for( int i = 1; i <= MaxClients; i++ )
1467 {
1468 g_bChangingTypesMenu[i] = false;
1469 g_fLastFreeze[i] = 0.0;
1470 g_fLastShield[i] = 0.0;
1471 g_fLastUse[i] = 0.0;
1472
1473 if( all )
1474 {
1475 SDKUnhook(i, SDKHook_OnTakeDamageAlive, OnShield);
1476 SDKUnhook(i, SDKHook_WeaponEquip, OnWeaponEquip);
1477 SDKUnhook(i, SDKHook_WeaponDrop, OnWeaponDrop);
1478 }
1479 }
1480
1481 int entity = -1;
1482 while( (entity = FindEntityByClassname(entity, "pipe_bomb_projectile")) != INVALID_ENT_REFERENCE )
1483 {
1484 StopSounds(entity);
1485 }
1486
1487 if( g_iEntityHurt && EntRefToEntIndex(g_iEntityHurt) != INVALID_ENT_REFERENCE )
1488 {
1489 AcceptEntityInput(g_iEntityHurt, "Kill");
1490 }
1491}
1492
1493// ====================================================================================================
1494// CHANGE MODE
1495// ====================================================================================================
1496public Action OnPlayerRunCmd(int client, int &buttons, int &impulse, float vel[3], float angles[3], int &weapon)
1497{
1498 bool bShowMenu;
1499
1500 // Preferences allow to change grenade type, holding Shoot and pressing Shove
1501 if( g_bCvarAllow && g_iConfigPrefs != 3 )
1502 {
1503 if (buttons & IN_ZOOM)
1504 {
1505 bShowMenu = true;
1506 g_bMiddleMouseActivation[client] = true;
1507 }
1508 else {
1509 if( buttons & IN_ATTACK )
1510 {
1511 if( buttons & IN_ATTACK2 )
1512 {
1513 bShowMenu = true;
1514 g_bMiddleMouseActivation[client] = false;
1515 }
1516 }
1517 else if( g_bChangingTypesMenu[client] && !g_bMiddleMouseActivation[client] )
1518 {
1519 g_bChangingTypesMenu[client] = false;
1520
1521 if( GetClientMenu(client, null) != MenuSource_None )
1522 {
1523 InternalShowMenu(client, "\10", 1); // thanks to Zira
1524 CancelClientMenu(client, true, null);
1525 }
1526 }
1527 }
1528
1529 if (bShowMenu )
1530 {
1531 // Check only a few times per second
1532 if( GetGameTime() - g_fLastUse[client] > MAX_WAIT )
1533 {
1534 g_fLastUse[client] = GetGameTime();
1535
1536 // Validate weapon
1537 int iWeapon = GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon");
1538 if( iWeapon > MaxClients && IsValidEntity(iWeapon) )
1539 {
1540 int type = IsGrenade(iWeapon);
1541 if( type )
1542 {
1543 if (!g_bMiddleMouseActivation[client])
1544 {
1545 // Cycle through modes
1546 int index = GetGrenadeIndex(client, type);
1547 SetEntProp(iWeapon, Prop_Data, "m_iHammerID", index + 1);
1548
1549 static char translation[256];
1550 Format(translation, sizeof translation, "GrenadeMod_Title_%d", index);
1551 Format(translation, sizeof translation, "%T %T", "GrenadeMod_Mode", client, translation, client);
1552 ReplaceColors(translation, sizeof translation);
1553 PrintToChat(client, translation);
1554 }
1555
1556 if( g_iConfigBinds == 4 )
1557 {
1558 g_bChangingTypesMenu[client] = true;
1559 ShowGrenadeMenu(client);
1560 }
1561 }
1562 }
1563 }
1564 }
1565 }
1566}
1567
1568int GetGrenadeIndex(int client, int type)
1569{
1570 int index = g_iClientGrenadeType[client];
1571
1572 if( index == -1 )
1573 index = 0;
1574 else
1575 index++;
1576
1577 // Default to stock
1578 if( index == 0 || index > MAX_TYPES )
1579 {
1580 if( g_iConfigStock & (1<<(type - 1)) )
1581 index = 0;
1582 else
1583 index = 1;
1584 }
1585
1586 // If modded
1587 if( index > 0 )
1588 {
1589 int types = g_iConfigBots && IsFakeClient(client) ? g_iConfigBots : g_iConfigTypes; // Allowed types for bots / players
1590
1591 // Loop next
1592 for( int i = index - 1; i < MAX_TYPES; i++ )
1593 {
1594 if( g_GrenadeSlot[i][!g_bLeft4Dead2] & (1<<type - 1) && types & (1<<i) )
1595 {
1596 index = i + 1;
1597 break;
1598 }
1599 else if( i == MAX_TYPES - 1 )
1600 {
1601 // Allow stock
1602 if( g_iConfigStock & (1<<(type - 1)) )
1603 {
1604 index = 0;
1605 }
1606 else
1607 {
1608 // Loop from 0
1609 for( int x = 0; x < MAX_TYPES; x++ )
1610 {
1611 if( g_GrenadeSlot[i][!g_bLeft4Dead2] & (1<<type - 1) && types & (1<<x) )
1612 {
1613 index = x + 1;
1614 break;
1615 }
1616 }
1617 }
1618 }
1619 }
1620 }
1621
1622 g_iClientGrenadeType[client] = index;
1623 g_iClientGrenadePref[client][type - 1] = index;
1624 SetClientPrefs(client);
1625 return index;
1626}
1627
1628
1629
1630// ====================================================================================================
1631// PROJECTILE THROWN
1632// ====================================================================================================
1633// Listen for thrown grenades to hook and replace.
1634public void OnEntityCreated(int entity, const char[] classname)
1635{
1636 if( g_bCvarAllow )
1637 {
1638 if( !g_bBlockHook )
1639 {
1640 if(
1641 classname[0] == 'm' ||
1642 classname[0] == 'p' ||
1643 classname[0] == 'v'
1644 )
1645 {
1646 if(
1647 strncmp(classname, "molotov_projectile", 13) == 0 ||
1648 strncmp(classname, "pipe_bomb_projectile", 13) == 0 ||
1649 g_bLeft4Dead2 && strncmp(classname, "vomitjar_projectile", 13) == 0
1650 )
1651 {
1652 SDKHook(entity, SDKHook_SpawnPost, SpawnPost);
1653 return;
1654 }
1655 }
1656 }
1657
1658 if( g_bHookFire && strcmp(classname, "inferno") == 0 ) // Small fires
1659 {
1660 SDKHook(entity, SDKHook_ThinkPost, OnPostThink);
1661 }
1662 }
1663}
1664
1665public void OnPostThink(int entity)
1666{
1667 SetEntProp(entity, Prop_Send, "m_fireXDelta", 1, 1, 0);
1668 SetEntProp(entity, Prop_Send, "m_fireCount", 1);
1669}
1670
1671public void SpawnPost(int entity)
1672{
1673 // 1 frame later required to get velocity
1674 RequestFrame(OnNextFrame, EntIndexToEntRef(entity));
1675
1676 // Stop molotov loop sound since it gets stuck.
1677 g_bBlockSound = true;
1678}
1679
1680public void OnNextFrame(int entity)
1681{
1682 g_bBlockSound = false;
1683
1684 // Validate entity
1685 if( EntRefToEntIndex(entity) == INVALID_ENT_REFERENCE || !IsValidEntity(entity) )
1686 return;
1687
1688 // Get Client
1689 int client = GetEntPropEnt(entity, Prop_Data, "m_hThrower");
1690 if( client > 0 && client <= MaxClients && IsClientInGame(client) )
1691 {
1692 int index = g_iClientGrenadeType[client];
1693 if( index > 0 )
1694 {
1695 // Game bug: when "weapon_oxygentank" and "weapon_propanetank" explode they create a "pipe_bomb_projectile". This prevents those erroneous ents.
1696 static float vTest[3];
1697 GetEntPropVector(entity, Prop_Send, "m_vInitialVelocity", vTest);
1698 if( vTest[0] == 0.0 && vTest[1] == 0.0 && vTest[2] == 0.0 )
1699 return;
1700
1701 // Recreate projectile to deactivate it.
1702 entity = CreateProjectile(entity, client, index);
1703 if( entity == 0 )
1704 return;
1705
1706 // Hurt entity
1707 CreateHurtEntity();
1708
1709 // Detonate / Stick on contact
1710 float detonate = g_GrenadeData[index - 1][CONFIG_FUSE];
1711 if( detonate == 0.0 )
1712 SDKHook(entity, SDKHook_Touch, OnTouch_Detonate);
1713 else
1714 CreateTimer(detonate, Timer_Detonate, EntIndexToEntRef(entity));
1715
1716 // Create projectile effects
1717 DoPrjEffects(entity, index);
1718 }
1719 }
1720}
1721
1722
1723
1724// ====================================================================================================
1725// PROJECTILE EFFECTS - Create projectile effects
1726// ====================================================================================================
1727void DoPrjEffects(int entity, int index)
1728{
1729 switch( index - 1 )
1730 {
1731 case INDEX_BOMB: PrjEffects_Bomb (entity);
1732 case INDEX_CLUSTER: PrjEffects_Cluster (entity);
1733 case INDEX_FIREWORK: PrjEffects_Firework (entity);
1734 case INDEX_SMOKE: PrjEffects_Smoke (entity);
1735 case INDEX_BLACKHOLE: PrjEffects_BlackHole (entity);
1736 case INDEX_FLASHBANG: PrjEffects_Flashbang (entity);
1737 case INDEX_SHIELD: PrjEffects_Shield (entity);
1738 case INDEX_TESLA: PrjEffects_Tesla (entity);
1739 case INDEX_CHEMICAL: PrjEffects_Chemical (entity);
1740 case INDEX_FREEZER: PrjEffects_Freezer (entity);
1741 case INDEX_MEDIC: PrjEffects_Medic (entity);
1742 case INDEX_VAPORIZER: PrjEffects_Vaporizer (entity);
1743 case INDEX_EXTINGUISHER: PrjEffects_Extinguisher (entity);
1744 case INDEX_GLOW: PrjEffects_Glow (entity);
1745 case INDEX_ANTIGRAVITY: PrjEffects_AntiGravity (entity);
1746 case INDEX_FIRECLUSTER: PrjEffects_FireCluster (entity);
1747 case INDEX_BULLETS: PrjEffects_Bullets (entity);
1748 case INDEX_FLAK: PrjEffects_Flak (entity);
1749 }
1750}
1751
1752void SetupPrjEffects(int entity, float vPos[3], const char[] color)
1753{
1754 // Grenade Pos
1755 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
1756
1757 // Sprite
1758 CreateEnvSprite(entity, color);
1759
1760 // Steam
1761 static float vAng[3];
1762 GetEntPropVector(entity, Prop_Data, "m_angRotation", vAng);
1763 MakeEnvSteam(entity, vPos, vAng, color);
1764
1765 // Light
1766 int light = MakeLightDynamic(entity, vPos);
1767 SetVariantEntity(light);
1768 SetVariantString(color);
1769 AcceptEntityInput(light, "color");
1770 AcceptEntityInput(light, "TurnOn");
1771}
1772
1773// ====================================================================================================
1774// PRJ EFFECT - BOMB
1775// ====================================================================================================
1776void PrjEffects_Bomb(int entity)
1777{
1778 // Grenade Pos + Effects
1779 static float vPos[3];
1780 SetupPrjEffects(entity, vPos, "255 0 0"); // Red
1781
1782 // Particles
1783 DisplayParticle(entity, PARTICLE_MINIG, vPos, NULL_VECTOR);
1784 DisplayParticle(entity, PARTICLE_FLARE, vPos, NULL_VECTOR);
1785 if( g_bLeft4Dead2 )
1786 {
1787 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
1788 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
1789 }
1790
1791 // Sound
1792 PlaySound(entity, SOUND_STEAM);
1793}
1794
1795// ====================================================================================================
1796// PRJ EFFECT - CLUSTER
1797// ====================================================================================================
1798void PrjEffects_Cluster(int entity)
1799{
1800 // Grenade Pos + Effects
1801 static float vPos[3];
1802 SetupPrjEffects(entity, vPos, "255 255 0"); // Yellow
1803
1804 // Particles
1805 DisplayParticle(entity, PARTICLE_MINIG, vPos, NULL_VECTOR);
1806 if( g_bLeft4Dead2 )
1807 {
1808 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
1809 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
1810 }
1811
1812 // Sound
1813 PlaySound(entity, SOUND_STEAM);
1814}
1815
1816// ====================================================================================================
1817// PRJ EFFECT - FIREWORK
1818// ====================================================================================================
1819void PrjEffects_Firework(int entity)
1820{
1821 // Grenade Pos + Effects
1822 static float vPos[3];
1823 SetupPrjEffects(entity, vPos, "255 150 0"); // Orange
1824
1825 // Particles
1826 DisplayParticle(entity, PARTICLE_MINIG, vPos, NULL_VECTOR);
1827 DisplayParticle(entity, PARTICLE_TES7, vPos, NULL_VECTOR);
1828 if( g_bLeft4Dead2 )
1829 {
1830 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
1831 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
1832 }
1833}
1834
1835// ====================================================================================================
1836// PRJ EFFECT - SMOKE
1837// ====================================================================================================
1838void PrjEffects_Smoke(int entity)
1839{
1840 // Grenade Pos + Effects
1841 static float vPos[3];
1842 SetupPrjEffects(entity, vPos, "100 100 100"); // Grey
1843
1844 // Particles
1845 if( g_bLeft4Dead2 )
1846 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
1847 DisplayParticle(entity, PARTICLE_SMOKE2, vPos, NULL_VECTOR);
1848 DisplayParticle(entity, PARTICLE_IMPACT, vPos, NULL_VECTOR);
1849 DisplayParticle(entity, PARTICLE_SMOKE, vPos, NULL_VECTOR);
1850
1851 // Sound
1852 PlaySound(entity, SOUND_GAS);
1853}
1854
1855// ====================================================================================================
1856// PRJ EFFECT - BLACK HOLE
1857// ====================================================================================================
1858void PrjEffects_BlackHole(int entity)
1859{
1860 // Grenade Pos + Effects
1861 static float vPos[3];
1862 SetupPrjEffects(entity, vPos, "200 0 255"); // Purple
1863
1864 // Particles
1865 if( g_bLeft4Dead2 )
1866 DisplayParticle(entity, PARTICLE_DEFIB, vPos, NULL_VECTOR, 0.5);
1867
1868 // Sound
1869 PlaySound(entity, SOUND_SQUEAK);
1870 PlaySound(entity, SOUND_SPATIAL);
1871 PlaySound(entity, SOUND_FLICKER);
1872 PlaySound(entity, SOUND_TUNNEL);
1873 PlaySound(entity, SOUND_NOISE);
1874}
1875
1876// ====================================================================================================
1877// PRJ EFFECT - FLASHBANG
1878// ====================================================================================================
1879void PrjEffects_Flashbang(int entity)
1880{
1881 // Grenade Pos + Effects
1882 static float vPos[3];
1883 SetupPrjEffects(entity, vPos, "255 255 255"); // White
1884
1885 // Particles
1886 if( g_bLeft4Dead2 )
1887 {
1888 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
1889 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
1890 }
1891
1892 // Sound
1893 PlaySound(entity, SOUND_BUTTON1);
1894}
1895
1896// ====================================================================================================
1897// PRJ EFFECT - SHIELD
1898// ====================================================================================================
1899void PrjEffects_Shield(int entity)
1900{
1901 // Grenade Pos + Effects
1902 static float vPos[3];
1903 SetupPrjEffects(entity, vPos, "0 220 255"); // Light Blue
1904
1905 // Particles
1906 if( g_bLeft4Dead2 )
1907 DisplayParticle(entity, PARTICLE_DEFIB, vPos, NULL_VECTOR);
1908 DisplayParticle(entity, PARTICLE_ELMOS, vPos, NULL_VECTOR);
1909
1910 // Sound
1911 PlaySound(entity, SOUND_SPATIAL);
1912 PlaySound(entity, SOUND_TUNNEL);
1913 PlaySound(entity, SOUND_NOISE);
1914}
1915
1916// ====================================================================================================
1917// PRJ EFFECT - TESLA
1918// ====================================================================================================
1919void PrjEffects_Tesla(int entity)
1920{
1921 // Grenade Pos + Effects
1922 static float vPos[3];
1923 SetupPrjEffects(entity, vPos, "0 50 155"); // Blue
1924
1925 // Particles
1926 if( g_bLeft4Dead2 )
1927 DisplayParticle(entity, PARTICLE_DEFIB, vPos, NULL_VECTOR, 0.5);
1928 DisplayParticle(entity, PARTICLE_ELMOS, vPos, NULL_VECTOR, 0.5);
1929
1930 // Sound
1931 PlaySound(entity, SOUND_SQUEAK);
1932 PlaySound(entity, SOUND_SPATIAL);
1933 PlaySound(entity, SOUND_FLICKER);
1934 PlaySound(entity, SOUND_TUNNEL);
1935}
1936
1937// ====================================================================================================
1938// PRJ EFFECT - CHEMICAL
1939// ====================================================================================================
1940void PrjEffects_Chemical(int entity)
1941{
1942 // Grenade Pos + Effects
1943 static float vPos[3];
1944 SetupPrjEffects(entity, vPos, "150 255 0"); // Lime green
1945
1946 // Particles
1947 if( g_bLeft4Dead2 )
1948 DisplayParticle(entity, PARTICLE_SPIT_T, vPos, NULL_VECTOR);
1949 DisplayParticle(entity, PARTICLE_VOMIT, vPos, NULL_VECTOR);
1950
1951 // Sound
1952 PlaySound(entity, SOUND_GAS);
1953}
1954
1955// ====================================================================================================
1956// PRJ EFFECT - FREEZER
1957// ====================================================================================================
1958void PrjEffects_Freezer(int entity)
1959{
1960 // Grenade Pos + Effects
1961 static float vPos[3];
1962 SetupPrjEffects(entity, vPos, "0 150 255"); // Light Blue
1963
1964 // Particles
1965 DisplayParticle(entity, PARTICLE_STEAM, vPos, NULL_VECTOR, 0.5);
1966 DisplayParticle(entity, PARTICLE_ELMOS, vPos, NULL_VECTOR, 0.5);
1967
1968 // Sound
1969 PlaySound(entity, SOUND_GAS);
1970 PlaySound(entity, SOUND_STEAM);
1971 PlaySound(entity, SOUND_NOISE);
1972}
1973
1974// ====================================================================================================
1975// PRJ EFFECT - MEDIC
1976// ====================================================================================================
1977void PrjEffects_Medic(int entity)
1978{
1979 // Grenade Pos + Effects
1980 static float vPos[3];
1981 SetupPrjEffects(entity, vPos, "0 150 0"); // Green
1982
1983 // Sound
1984 PlaySound(entity, SOUND_TUNNEL);
1985 PlaySound(entity, SOUND_NOISE);
1986 PlaySound(entity, SOUND_SQUEAK);
1987}
1988
1989// ====================================================================================================
1990// PRJ EFFECT - VAPORIZER
1991// ====================================================================================================
1992void PrjEffects_Vaporizer(int entity)
1993{
1994 // Grenade Pos + Effects
1995 static float vPos[3];
1996 SetupPrjEffects(entity, vPos, "50 0 255"); // Purple
1997
1998 // Particles
1999 if( g_bLeft4Dead2 )
2000 DisplayParticle(entity, PARTICLE_DEFIB, vPos, NULL_VECTOR, 0.5);
2001 DisplayParticle(entity, PARTICLE_TES2, vPos, NULL_VECTOR, 0.5);
2002 DisplayParticle(entity, PARTICLE_ELMOS, vPos, NULL_VECTOR, 0.5);
2003
2004 // Sound
2005 PlaySound(entity, SOUND_SQUEAK);
2006 PlaySound(entity, SOUND_SPATIAL);
2007 PlaySound(entity, SOUND_FLICKER);
2008 PlaySound(entity, SOUND_TUNNEL);
2009}
2010
2011// ====================================================================================================
2012// PRJ EFFECT - EXTINGUISHER
2013// ====================================================================================================
2014void PrjEffects_Extinguisher(int entity)
2015{
2016 // Grenade Pos + Effects
2017 static float vPos[3];
2018 SetupPrjEffects(entity, vPos, "0 50 255"); // Blue
2019
2020 // Particles
2021 DisplayParticle(entity, PARTICLE_STEAM, vPos, NULL_VECTOR, 0.5);
2022}
2023
2024// ====================================================================================================
2025// PRJ EFFECT - GLOW
2026// ====================================================================================================
2027void PrjEffects_Glow(int entity)
2028{
2029 // Grenade Pos + Effects
2030 static float vPos[3];
2031 SetupPrjEffects(entity, vPos, "255 150 0"); // Yellow-ish
2032
2033 // Particles
2034 DisplayParticle(entity, PARTICLE_STEAM, vPos, NULL_VECTOR, 0.5);
2035
2036 // Sound
2037 PlaySound(entity, SOUND_TUNNEL);
2038 PlaySound(entity, SOUND_SPATIAL);
2039}
2040
2041// ====================================================================================================
2042// PRJ EFFECT - ANTI-GRAVITY
2043// ====================================================================================================
2044void PrjEffects_AntiGravity(int entity)
2045{
2046 // Grenade Pos + Effects
2047 static float vPos[3];
2048 SetupPrjEffects(entity, vPos, "0 255 100"); // Lime Green
2049
2050 // Particles
2051 DisplayParticle(entity, PARTICLE_ELMOS, vPos, NULL_VECTOR, 0.5);
2052
2053 // Sound
2054 PlaySound(entity, SOUND_TUNNEL);
2055
2056 // Slow projectile
2057 CreateTimer(0.1, tmrSlowdown, EntIndexToEntRef(entity), TIMER_REPEAT);
2058}
2059
2060public Action tmrSlowdown(Handle timer, any entity)
2061{
2062 if( (entity = EntRefToEntIndex(entity)) != INVALID_ENT_REFERENCE )
2063 {
2064 static float vVel[3];
2065 GetEntPropVector(entity, Prop_Data, "m_vecAbsVelocity", vVel);
2066 float speed = GetVectorLength(vVel);
2067
2068 ScaleVector(vVel, speed < 100 ? 0.6 : 0.9);
2069 TeleportEntity(entity, NULL_VECTOR, NULL_VECTOR, vVel);
2070
2071 if( speed > 20 )
2072 {
2073 return Plugin_Continue;
2074 }
2075
2076 SetEntityGravity(entity, -0.01);
2077 }
2078 return Plugin_Stop;
2079}
2080
2081// ====================================================================================================
2082// PRJ EFFECT - CLUSTER
2083// ====================================================================================================
2084void PrjEffects_FireCluster(int entity)
2085{
2086 // Grenade Pos + Effects
2087 static float vPos[3];
2088 SetupPrjEffects(entity, vPos, "255 50 0"); // Orange
2089
2090 // Particles
2091 DisplayParticle(entity, PARTICLE_MINIG, vPos, NULL_VECTOR);
2092 if( g_bLeft4Dead2 )
2093 {
2094 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
2095 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
2096 }
2097
2098 // Sound
2099 PlaySound(entity, SOUND_STEAM);
2100}
2101
2102// ====================================================================================================
2103// PRJ EFFECT - BULLETS
2104// ====================================================================================================
2105void PrjEffects_Bullets(int entity)
2106{
2107 // Grenade Pos + Effects
2108 static float vPos[3];
2109 SetupPrjEffects(entity, vPos, "255 100 0"); // Yellow orange
2110
2111 // Particles
2112 if( g_bLeft4Dead2 )
2113 {
2114 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
2115 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
2116 }
2117
2118 // Sound
2119 PlaySound(entity, SOUND_FLICKER);
2120}
2121
2122// ====================================================================================================
2123// PRJ EFFECT - FLAK
2124// ====================================================================================================
2125void PrjEffects_Flak(int entity)
2126{
2127 // Grenade Pos + Effects
2128 static float vPos[3];
2129 SetupPrjEffects(entity, vPos, "255 100 100"); // Rose
2130
2131 // Particles
2132 DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
2133 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
2134
2135 // Sound
2136 PlaySound(entity, SOUND_STEAM);
2137}
2138
2139
2140
2141// ====================================================================================================
2142// PROJECTILE EXPLODED
2143// ====================================================================================================
2144public Action Timer_Detonate(Handle timer, any entity)
2145{
2146 if( g_bCvarAllow && (entity = EntRefToEntIndex(entity)) != INVALID_ENT_REFERENCE )
2147 {
2148 Detonate_Grenade(entity);
2149 }
2150}
2151
2152public void OnTouch_Detonate(int entity, int other)
2153{
2154 if( other > MaxClients )
2155 {
2156 static char classname[12];
2157 GetEdictClassname(other, classname, sizeof classname);
2158 if( strncmp(classname, "trigger_", 8) == 0 ) return;
2159 }
2160
2161 Detonate_Grenade(entity);
2162 SDKUnhook(entity, SDKHook_Touch, OnTouch_Detonate);
2163}
2164
2165void Detonate_Grenade(int entity)
2166{
2167 // Validate client
2168 int client = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
2169 if( client > 0 && IsClientInGame(client) )
2170 {
2171 // Get index
2172 int index = GetEntProp(entity, Prop_Data, "m_iHammerID");
2173 static float vPos[3];
2174
2175 // Stick to surface
2176 if( g_GrenadeData[index - 1][CONFIG_STICK] )
2177 {
2178 GetEntPropVector(entity, Prop_Data, "m_vecAbsVelocity", vPos);
2179 if(
2180 vPos[0] > -1.0 && vPos[0] < 1.0 &&
2181 vPos[1] > -1.0 && vPos[1] < 1.0 &&
2182 vPos[2] > -1.0 && vPos[2] < 1.0
2183 )
2184 {
2185 SetEntPropVector(entity, Prop_Data, "m_vecAbsVelocity", view_as<float>({ 0.0, 0.0, 0.0 }));
2186 SetEntityMoveType(entity, MOVETYPE_NONE);
2187 SetEntProp(entity, Prop_Send, "m_nSolidType", 6);
2188 }
2189 }
2190
2191 // Grenade Pos
2192 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2193
2194 // Explosion start time
2195 SetEntPropFloat(entity, Prop_Data, "m_flCreateTime", GetGameTime());
2196
2197 // Prevent error msg: "Entity 157 (class 'pipe_bomb_projectile') reported ENTITY_CHANGE_NONE but 'm_flCreateTime' changed."
2198 int offset = FindDataMapInfo(entity, "m_flCreateTime");
2199 ChangeEdictState(entity, offset);
2200
2201 // Do explode
2202 Explode_Effects(client, entity, index, false);
2203
2204 // Detonation duration
2205 float tick = g_GrenadeData[index - 1][CONFIG_TICK];
2206 if( tick != 0.0 )
2207 {
2208 CreateTimer(tick, Timer_Repeat_Explode, EntIndexToEntRef(entity), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
2209 }
2210
2211 // Stop env_steam
2212 static char sTemp[64];
2213 Format(sTemp, sizeof sTemp, "OnUser4 silv_steam_%d:TurnOff::0.0:-1", entity);
2214 SetVariantString(sTemp);
2215 AcceptEntityInput(entity, "AddOutput");
2216 Format(sTemp, sizeof sTemp, "OnUser4 silv_steam_%d:Kill::2.0:-1", entity);
2217 SetVariantString(sTemp);
2218 AcceptEntityInput(entity, "AddOutput");
2219 AcceptEntityInput(entity, "FireUser4");
2220 }
2221}
2222
2223public Action Timer_Repeat_Explode(Handle timer, any entity)
2224{
2225 if( g_bCvarAllow && (entity = EntRefToEntIndex(entity)) != INVALID_ENT_REFERENCE )
2226 {
2227 // Get index
2228 int index = GetEntProp(entity, Prop_Data, "m_iHammerID");
2229
2230 // Validate client
2231 int client = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
2232 if( client > 0 && IsClientInGame(client) )
2233 {
2234 // Do explode
2235 Explode_Effects(client, entity, index);
2236 }
2237
2238 // Check duration
2239 if( GetGameTime() - GetEntPropFloat(entity, Prop_Data, "m_flCreateTime") > g_GrenadeData[index - 1][CONFIG_TIME] )
2240 {
2241 InputKill(entity, 0.2);
2242
2243 // Stop sounds
2244 StopSounds(entity);
2245 return Plugin_Stop;
2246 }
2247
2248 return Plugin_Continue;
2249 }
2250
2251 return Plugin_Stop;
2252}
2253
2254
2255
2256// ====================================================================================================
2257// EXPLOSION EFFECTS - Create explosion effects
2258// ====================================================================================================
2259void Explode_Effects(int client, int entity, int index, bool fromTimer = true)
2260{
2261 CreateHurtEntity();
2262
2263 switch( index - 1 )
2264 {
2265 case INDEX_BOMB: Explode_Bomb (client, entity, index);
2266 case INDEX_CLUSTER: Explode_Cluster (client, entity, index, fromTimer);
2267 case INDEX_FIREWORK: Explode_Firework (client, entity, index, fromTimer);
2268 case INDEX_SMOKE: Explode_Smoke (client, entity, index, fromTimer);
2269 case INDEX_BLACKHOLE: Explode_BlackHole (client, entity, index, fromTimer);
2270 case INDEX_FLASHBANG: Explode_Flashbang (client, entity, index);
2271 case INDEX_SHIELD: Explode_Shield (client, entity, index, fromTimer);
2272 case INDEX_TESLA: Explode_Tesla (client, entity, index, fromTimer);
2273 case INDEX_CHEMICAL: Explode_Chemical (client, entity, index, fromTimer);
2274 case INDEX_FREEZER: Explode_Freezer (client, entity, index, fromTimer);
2275 case INDEX_MEDIC: Explode_Medic (client, entity, index);
2276 case INDEX_VAPORIZER: Explode_Vaporizer (client, entity, index, fromTimer);
2277 case INDEX_EXTINGUISHER: Explode_Extinguisher (client, entity, index);
2278 case INDEX_GLOW: Explode_Glow (client, entity, index);
2279 case INDEX_ANTIGRAVITY: Explode_AntiGravity (client, entity, index);
2280 case INDEX_FIRECLUSTER: Explode_Cluster (client, entity, index, fromTimer);
2281 case INDEX_BULLETS: Explode_Bullets (client, entity, index, fromTimer);
2282 case INDEX_FLAK: Explode_Flak (client, entity, index);
2283 }
2284
2285
2286
2287 // Detonation duration - instant explode only
2288 float tick = g_GrenadeData[index - 1][CONFIG_TICK];
2289 float time = g_GrenadeData[index - 1][CONFIG_TIME];
2290 if( tick == 0.0 || time == 0.0 )
2291 {
2292 InputKill(entity, 0.2);
2293
2294 // Stop sounds
2295 StopSounds(entity);
2296 }
2297}
2298
2299
2300
2301// ====================================================================================================
2302// EXPLOSION FX - BOMB
2303// ====================================================================================================
2304public void Explode_Bomb(int client, int entity, int index)
2305{
2306 // Grenade Pos
2307 static float vPos[3];
2308 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2309
2310 // Explosion
2311 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BLAST);
2312
2313 // Shake
2314 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2315
2316 // Particles
2317 if( g_bLeft4Dead2 )
2318 DisplayParticle(entity, PARTICLE_CHARGE, vPos, NULL_VECTOR);
2319 DisplayParticle(entity, PARTICLE_BURST, vPos, NULL_VECTOR);
2320 DisplayParticle(entity, PARTICLE_PIPE1, vPos, NULL_VECTOR);
2321
2322 // Sound
2323 if( g_bLeft4Dead2 )
2324 {
2325 int random = GetRandomInt(1, 4);
2326 switch( random )
2327 {
2328 case 1: PlaySound(entity, SOUND_FIREWORK1);
2329 case 2: PlaySound(entity, SOUND_FIREWORK2);
2330 case 3: PlaySound(entity, SOUND_FIREWORK3);
2331 case 4: PlaySound(entity, SOUND_FIREWORK4);
2332 }
2333 } else {
2334 if( GetRandomInt(0, 1) ) PlaySound(entity, SOUND_EXPLODE3);
2335 else PlaySound(entity, SOUND_EXPLODE5);
2336 }
2337}
2338
2339// ====================================================================================================
2340// EXPLOSION FX - CLUSTER
2341// ====================================================================================================
2342public void Explode_Cluster(int client, int entity, int index, bool fromTimer)
2343{
2344 // Grenade Pos
2345 static float vPos[3];
2346 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2347
2348 if( fromTimer == false )
2349 {
2350 // Explosion
2351 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BLAST);
2352
2353 // Shake
2354 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2355 }
2356
2357 // Particles
2358 DisplayParticle(entity, PARTICLE_BLAST, vPos, NULL_VECTOR);
2359 DisplayParticle(entity, PARTICLE_BLAST2, vPos, NULL_VECTOR);
2360 DisplayParticle(entity, PARTICLE_PIPE2, vPos, NULL_VECTOR);
2361
2362 // Sound
2363 if( g_bLeft4Dead2 )
2364 {
2365 int random = GetRandomInt(1, 4);
2366 switch( random )
2367 {
2368 case 1: PlaySound(entity, SOUND_FIREWORK1);
2369 case 2: PlaySound(entity, SOUND_FIREWORK2);
2370 case 3: PlaySound(entity, SOUND_FIREWORK3);
2371 case 4: PlaySound(entity, SOUND_FIREWORK4);
2372 }
2373 } else {
2374 if( GetRandomInt(0, 1) ) PlaySound(entity, SOUND_EXPLODE3);
2375 else PlaySound(entity, SOUND_EXPLODE5);
2376 }
2377
2378 // Projectiles
2379 static float vVel[3];
2380 int max = 3;
2381 int particle;
2382 vPos[2] += 10.0;
2383
2384 if( index - 1 == INDEX_FIRECLUSTER ) max = 1;
2385
2386 for( int i = 0; i < max; i++ )
2387 {
2388 // Create new projectile
2389 g_bBlockHook = true;
2390 entity = CreateEntityByName("pipe_bomb_projectile");
2391 g_bBlockHook = false;
2392
2393 if( entity != -1 )
2394 {
2395 // Fire and forget - cluster projectiles
2396 InputKill(entity, 3.0);
2397
2398 SetEntProp(entity, Prop_Data, "m_iHammerID", index); // Store mode type
2399 SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", client); // Store owner
2400
2401 // Set origin and velocity
2402 if( index - 1 == INDEX_FIRECLUSTER )
2403 {
2404 vVel[0] = GetRandomFloat(-g_GrenadeData[index - 1][CONFIG_RANGE], g_GrenadeData[index - 1][CONFIG_RANGE] / 2);
2405 vVel[1] = GetRandomFloat(-g_GrenadeData[index - 1][CONFIG_RANGE], g_GrenadeData[index - 1][CONFIG_RANGE] / 2);
2406 vVel[2] = GetRandomFloat(270.0, g_GrenadeData[index - 1][CONFIG_RANGE]);
2407 } else {
2408 vVel[0] = GetRandomFloat(-g_GrenadeData[index - 1][CONFIG_RANGE], g_GrenadeData[index - 1][CONFIG_RANGE]);
2409 vVel[1] = GetRandomFloat(-g_GrenadeData[index - 1][CONFIG_RANGE], g_GrenadeData[index - 1][CONFIG_RANGE]);
2410 vVel[2] = GetRandomFloat(270.0, g_GrenadeData[index - 1][CONFIG_RANGE] / 2);
2411 }
2412
2413 TeleportEntity(entity, vPos, NULL_VECTOR, vVel);
2414 DispatchSpawn(entity);
2415 SetEntityModel(entity, MODEL_SPRAYCAN); // Model after Dispatch otherwise the projectile effects (flashing light + trail) show.
2416 SetEntityRenderMode(entity, RENDER_NONE);
2417
2418 // Particles
2419 particle = DisplayParticle(entity, PARTICLE_MINIG, vPos, NULL_VECTOR);
2420 InputKill(particle, 3.0);
2421
2422 if( g_bLeft4Dead2 )
2423 {
2424 particle = DisplayParticle(entity, PARTICLE_SPARKS, vPos, NULL_VECTOR);
2425 InputKill(particle, 3.0);
2426
2427 particle = DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR, 0.2);
2428 InputKill(particle, 3.0);
2429 }
2430
2431 SDKHook(entity, SDKHook_Touch, OnTouchTrigger_Cluster);
2432 }
2433 }
2434}
2435
2436public void OnTouchTrigger_Cluster(int entity, int target)
2437{
2438 static char classname[12];
2439 GetEdictClassname(target, classname, sizeof classname);
2440 if( strncmp(classname, "trigger_", 8) == 0 ) return;
2441
2442 static float vPos[3];
2443 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2444 DisplayParticle(entity, PARTICLE_BLAST2, vPos, NULL_VECTOR);
2445 DisplayParticle(entity, PARTICLE_PIPE2, vPos, NULL_VECTOR);
2446
2447 InputKill(entity, 0.1);
2448
2449 // Hurt enemies
2450 int client = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
2451 int index = GetEntProp(entity, Prop_Data, "m_iHammerID");
2452
2453 if( index - 1 == INDEX_CLUSTER )
2454 CreateExplosion(client, entity, INDEX_CLUSTER + 1, 200.0, vPos, DMG_BLAST);
2455 else
2456 {
2457 if( g_bLeft4Dead2 ) // Property "m_fireXDelta" does not exist in L4D1. TODO: Make compatible alternative effect.
2458 g_bHookFire = true;
2459 CreateFires(entity, client, true);
2460 g_bHookFire = false;
2461 }
2462
2463 // Shake
2464 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE] / 2, 200.0, vPos);
2465}
2466
2467// ====================================================================================================
2468// EXPLOSION FX - FIREWORK
2469// ====================================================================================================
2470public void Explode_Firework(int client, int entity, int index, bool fromTimer)
2471{
2472 // Only want to trigger effects on initial detonation. Timers will fire past the effect life time when deleting, sometimes wanted in other modes.
2473 if( fromTimer == false )
2474 {
2475 // Grenade Pos
2476 static float vPos[3];
2477 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2478
2479 // Explosion
2480 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BURN);
2481
2482 // Shake
2483 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2484
2485 // Particles
2486 DisplayParticle(entity, PARTICLE_BLAST, vPos, NULL_VECTOR);
2487 DisplayParticle(entity, PARTICLE_BLAST2, vPos, NULL_VECTOR);
2488 DisplayParticle(entity, PARTICLE_PIPE2, vPos, NULL_VECTOR);
2489
2490 // Sound
2491 if( GetRandomInt(0, 1) ) PlaySound(entity, SOUND_EXPLODE3);
2492 else PlaySound(entity, SOUND_EXPLODE5);
2493
2494 // Fire
2495 CreateFires(entity, client, !g_bLeft4Dead2);
2496
2497 // Fire Particles
2498 if( g_bLeft4Dead2 == false )
2499 {
2500 DisplayParticle(entity, PARTICLE_TES6, vPos, NULL_VECTOR);
2501 DisplayParticle(entity, PARTICLE_TES7, vPos, NULL_VECTOR);
2502 } else {
2503 DisplayParticle(entity, PARTICLE_GSPARKS, vPos, NULL_VECTOR);
2504 }
2505 }
2506}
2507
2508// ====================================================================================================
2509// EXPLOSION FX - SMOKE
2510// ====================================================================================================
2511public void Explode_Smoke(int client, int entity, int index, bool fromTimer)
2512{
2513 // Grenade Pos
2514 static float vPos[3];
2515 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2516
2517 if( fromTimer == false )
2518 {
2519 // Explosion
2520 CreateExplosion(client, entity, index, 0.0, vPos, DMG_NERVEGAS);
2521
2522 // Shake
2523 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2524 }
2525
2526 // Particles
2527 DisplayParticle(entity, PARTICLE_BLACK, vPos, NULL_VECTOR);
2528 DisplayParticle(entity, PARTICLE_SMOKER, vPos, NULL_VECTOR);
2529}
2530
2531// ====================================================================================================
2532// EXPLOSION FX - BLACK HOLE
2533// ====================================================================================================
2534public void Explode_BlackHole(int client, int entity, int index, bool fromTimer)
2535{
2536 // Grenade Pos
2537 static float vPos[3];
2538 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2539
2540 // Create Explosion
2541 CreateExplosion(client, entity, index, 0.0, vPos, DMG_GENERIC);
2542
2543 if( fromTimer == false )
2544 {
2545 // Shake
2546 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2547
2548 // Particles
2549 if( g_bLeft4Dead2 )
2550 DisplayParticle(entity, PARTICLE_CHARGE, vPos, NULL_VECTOR);
2551 DisplayParticle(entity, PARTICLE_TES2, vPos, NULL_VECTOR, 1.0);
2552 }
2553
2554 // Sound
2555 PlaySound(entity, g_sSoundsZap[GetRandomInt(0, sizeof g_sSoundsZap - 1)]);
2556
2557 // Beam Ring
2558 float range = g_GrenadeData[index - 1][CONFIG_RANGE] * 2 + BEAM_OFFSET;
2559 CreateBeamRing(entity, { 255, 0, 255, 255 }, range - (range / BEAM_RINGS), 0.1);
2560}
2561
2562// ====================================================================================================
2563// EXPLOSION FX - FLASHBANG
2564// ====================================================================================================
2565public void Explode_Flashbang(int client, int entity, int index)
2566{
2567 // Grenade Pos
2568 static float vPos[3];
2569 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2570
2571 // Explosion
2572 CreateExplosion(client, entity, index, 0.0, vPos, DMG_GENERIC);
2573
2574 // Shake
2575 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2576
2577 // Particles
2578 int ent;
2579 if( g_bLeft4Dead2 )
2580 {
2581 ent = DisplayParticle(0, PARTICLE_CHARGE, vPos, NULL_VECTOR);
2582 if( ent ) InputKill(ent, 1.0);
2583 }
2584 ent = DisplayParticle(0, PARTICLE_PIPE3, vPos, NULL_VECTOR);
2585 if( ent ) InputKill(ent, 1.0);
2586
2587 // Sound
2588 if( GetRandomInt(0, 1) ) PlaySound(entity, SOUND_EXPLODE3);
2589 else PlaySound(entity, SOUND_EXPLODE5);
2590
2591 // Kill flashbang
2592 AcceptEntityInput(entity, "Kill");
2593}
2594
2595// ====================================================================================================
2596// EXPLOSION FX - SHIELD
2597// ====================================================================================================
2598public void Explode_Shield(int client, int entity, int index, bool fromTimer)
2599{
2600 // Grenade Pos
2601 static float vPos[3];
2602 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2603
2604 // Particles
2605 DisplayParticle(entity, PARTICLE_TES2, vPos, NULL_VECTOR);
2606
2607 // Sound
2608 PlaySound(entity, SOUND_BUTTON1);
2609
2610 // Beam rings
2611 float range = g_GrenadeData[index - 1][CONFIG_RANGE] * 2 + BEAM_OFFSET;
2612 static bool flip;
2613 flip = !flip;
2614 CreateBeamRing(entity, { 0, 220, 255, 255 }, flip ? 0.1 : range - (range / BEAM_RINGS), flip ? range - (range / BEAM_RINGS) : 0.1);
2615
2616
2617 if( fromTimer == false )
2618 {
2619 // Create Trigger
2620 TriggerMultipleDamage(entity, index, range, vPos);
2621 }
2622}
2623
2624public void OnTouchTriggerShield(int entity, int target)
2625{
2626 if( target <= MaxClients && GetClientTeam(target) == 2 )
2627 {
2628 if( g_fLastShield[target] == 0.0 )
2629 {
2630 SDKHook(target, SDKHook_OnTakeDamageAlive, OnShield);
2631 }
2632
2633 g_fLastShield[target] = GetGameTime();
2634 }
2635}
2636
2637public Action OnShield(int victim, int &attacker, int &inflictor, float &damage, int &damagetype)
2638{
2639 // Check hook time
2640 if( GetGameTime() - g_fLastShield[victim] > 0.5 )
2641 {
2642 g_fLastShield[victim] = 0.0;
2643 SDKUnhook(victim, SDKHook_OnTakeDamageAlive, OnShield);
2644 return Plugin_Continue;
2645 }
2646
2647 if( GetClientTeam(victim) == 2 )
2648 {
2649 damage *= (100 - g_GrenadeData[INDEX_SHIELD][CONFIG_DAMAGE]) / 100;
2650 if( damage < 0.0 ) damage = 0.0;
2651 return Plugin_Changed;
2652 }
2653 return Plugin_Continue;
2654}
2655
2656// ====================================================================================================
2657// EXPLOSION FX - TESLA
2658// ====================================================================================================
2659public void Explode_Tesla(int client, int grenade, int index, bool fromTimer)
2660{
2661 // Grenade Pos
2662 static float vPos[3];
2663 GetEntPropVector(grenade, Prop_Data, "m_vecAbsOrigin", vPos);
2664
2665 // Explosion
2666 CreateExplosion(client, grenade, index, 0.0, vPos, DMG_PLASMA, true);
2667
2668 // Particles
2669 if( GetGameTime() - g_fLastTesla[grenade] >= 1.8 )
2670 {
2671 DisplayParticle(grenade, PARTICLE_TES2, vPos, NULL_VECTOR);
2672 g_fLastTesla[grenade] = GetGameTime();
2673 }
2674
2675 if( fromTimer == false )
2676 {
2677 // Shake
2678 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2679 }
2680}
2681
2682void TeslaShock(int grenade, int target)
2683{
2684 static char sTemp[32];
2685 static float vPos[3];
2686 int entity;
2687 int iType = GetRandomInt(0, 1);
2688
2689
2690
2691 // PARTICLE TARGET
2692 if( g_bLeft4Dead2 )
2693 entity = CreateEntityByName("info_particle_target");
2694 else
2695 {
2696 entity = CreateEntityByName("info_particle_system");
2697 }
2698
2699 if( iType == 0 )
2700 DispatchKeyValue(entity, "effect_name", PARTICLE_TES1);
2701 else if( iType == 1 )
2702 DispatchKeyValue(entity, "effect_name", PARTICLE_TES3);
2703
2704 Format(sTemp, sizeof(sTemp), "tesla%d%d%d", entity, grenade, target);
2705 DispatchKeyValue(entity, "targetname", sTemp);
2706
2707 SetVariantString("!activator");
2708 AcceptEntityInput(entity, "SetParent", grenade);
2709 vPos[2] = 10.0;
2710 TeleportEntity(entity, vPos, NULL_VECTOR, NULL_VECTOR);
2711 DispatchSpawn(entity);
2712 ActivateEntity(entity);
2713 AcceptEntityInput(entity, "Start");
2714
2715 InputKill(entity, 1.5);
2716
2717
2718
2719 // PARTICLE
2720 entity = CreateEntityByName("info_particle_system");
2721 DispatchKeyValue(entity, "cpoint1", sTemp);
2722 if( iType == 0 )
2723 DispatchKeyValue(entity, "effect_name", PARTICLE_TES1);
2724 else if( iType == 1 )
2725 DispatchKeyValue(entity, "effect_name", PARTICLE_TES3);
2726
2727 AcceptEntityInput(entity, "Start");
2728
2729 SetVariantString("!activator");
2730 AcceptEntityInput(entity, "SetParent", target);
2731 vPos[2] = GetRandomFloat(10.0, 50.0);
2732 TeleportEntity(entity, vPos, NULL_VECTOR, NULL_VECTOR);
2733 DispatchSpawn(entity);
2734 ActivateEntity(entity);
2735
2736 InputKill(entity, 1.2);
2737
2738
2739
2740 // SOUND
2741 PlaySound(entity, g_sSoundsZap[GetRandomInt(0, sizeof g_sSoundsZap - 1)]);
2742}
2743
2744// ====================================================================================================
2745// EXPLOSION FX - CHEMICAL
2746// ====================================================================================================
2747public void Explode_Chemical(int client, int entity, int index, bool fromTimer)
2748{
2749 // Grenade Pos
2750 static float vPos[3];
2751 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2752
2753 // Explosion
2754 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BULLET);
2755
2756 if( fromTimer == false )
2757 {
2758 // Shake
2759 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2760
2761 // Particles
2762 if( g_bLeft4Dead2 )
2763 {
2764 // Would prefer the particles above the grenade, but need to parent so it moves with the grenade and not bugged.
2765 DisplayParticle(entity, PARTICLE_SPIT_P, vPos, NULL_VECTOR, 0.5);
2766
2767 // SetParent forces this particle to 0,0,0 on the parent entity, to teleport slightly above it cannot be parented.
2768 // vPos[2] += 10.0;
2769 // entity = DisplayParticle(0, PARTICLE_SPIT_P, vPos, NULL_VECTOR, 0.5);
2770 // vPos[2] -= 10.0;
2771
2772 // Fire and forget itself
2773 // char sTime[64];
2774 // InputKill(entity, g_GrenadeData[index - 1][CONFIG_TIME]);
2775 }
2776 else
2777 {
2778 DisplayParticle(entity, PARTICLE_TRAIL, vPos, NULL_VECTOR, 0.5);
2779 DisplayParticle(entity, PARTICLE_SPLASH, vPos, NULL_VECTOR, 0.7);
2780 DisplayParticle(entity, PARTICLE_STEAM, vPos, NULL_VECTOR, 0.5);
2781 }
2782
2783 // Spitter Goo
2784 if( g_bLeft4Dead2 )
2785 {
2786 vPos[1] += 5.0;
2787 vPos[2] += 5.0;
2788 entity = SDKCall(sdkActivateSpit, vPos, view_as<float>({ 0.0, 0.0, 0.0 }), view_as<float>({ 0.0, 0.0, 0.0 }), view_as<float>({ 0.0, 0.0, 0.0 }), client);
2789 SetEntPropEnt(entity, Prop_Data, "m_hThrower", client);
2790 vPos[1] -= 5.0;
2791 vPos[2] -= 5.0;
2792 }
2793 }
2794}
2795
2796// ====================================================================================================
2797// EXPLOSION FX - FREEZER
2798// ====================================================================================================
2799public void Explode_Freezer(int client, int entity, int index, bool fromTimer)
2800{
2801 // Grenade Pos
2802 static float vPos[3];
2803 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2804
2805 if( fromTimer == false )
2806 {
2807 // Explosion
2808 CreateExplosion(client, entity, index, 0.0, vPos, DMG_GENERIC);
2809
2810 // Shake
2811 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2812
2813 // Trigger
2814 float range = g_GrenadeData[index - 1][CONFIG_RANGE];
2815 TriggerMultipleDamage(entity, index, range, vPos);
2816 }
2817
2818 // Particles
2819 DisplayParticle(entity, PARTICLE_SPLASH, vPos, NULL_VECTOR);
2820
2821 // Sound
2822 int random = GetRandomInt(1, 3);
2823 switch( random )
2824 {
2825 case 1: PlaySound(entity, SOUND_SPLASH1);
2826 case 2: PlaySound(entity, SOUND_SPLASH2);
2827 case 3: PlaySound(entity, SOUND_SPLASH3);
2828 }
2829}
2830
2831public void OnTouchTriggerFreezer2(int entity, int target)
2832{
2833 if( target > MaxClients )
2834 {
2835 static char classname[12];
2836 GetEdictClassname(target, classname, sizeof classname);
2837
2838 int targ = g_GrenadeTarg[INDEX_FREEZER];
2839 if( (targ & (1 << TARGET_COMMON) && strcmp(classname, "infected") == 0) || (targ & (1 << TARGET_WITCH) && strcmp(classname, "witch") == 0) )
2840 {
2841 if( g_bLeft4Dead2 )
2842 SetEntPropFloat(target, Prop_Data, "m_flFrozen", 0.3); // Only exists in L4D2
2843
2844 PlaySound(target, SOUND_FREEZER);
2845
2846 static float vPos[3];
2847 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vPos);
2848 int client = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
2849 PushCommon(client, target, vPos);
2850 }
2851 }
2852}
2853
2854public void OnTouchTriggerFreezer(int entity, int target)
2855{
2856 if( target <= MaxClients )
2857 {
2858 int targ = g_GrenadeTarg[INDEX_FREEZER];
2859
2860 if( (targ & (1<<TARGET_SURVIVOR) || targ & (1<<TARGET_SPECIAL)) )
2861 {
2862 if( GetEntProp(target, Prop_Send, "m_fFlags") & FL_ONGROUND )
2863 {
2864 bool pass;
2865
2866 int team = GetClientTeam(target);
2867 if( team == 2 && targ & (1<<TARGET_SURVIVOR) )
2868 pass = true;
2869 else if( team == 3 && targ & (1<<TARGET_SPECIAL) )
2870 pass = true;
2871 else if( team == 3 && targ & (1<<TARGET_TANK) && GetEntProp(target, Prop_Send, "m_zombieClass") == (g_bLeft4Dead2 ? 8 : 5) )
2872 pass = true;
2873
2874 if( pass )
2875 {
2876 if( GetGameTime() - g_fLastFreeze[target] > 1.0 )
2877 {
2878 CreateTimer(0.5, tmrFreezer, GetClientUserId(target), TIMER_REPEAT|TIMER_FLAG_NO_MAPCHANGE);
2879
2880 PlaySound(target, SOUND_FREEZER);
2881
2882 if( GetEntProp(target, Prop_Send, "m_clrRender") == -1 )
2883 {
2884 SetEntityRenderColor(target, 0, 128, 255, 192);
2885 }
2886 }
2887
2888 if( GetEntityMoveType(target) != MOVETYPE_NONE )
2889 SetEntityMoveType(target, MOVETYPE_NONE); // Has to be outside the timer, if player staggers they'll be able to move, so constantly apply.
2890 g_fLastFreeze[target] = GetGameTime();
2891 }
2892 }
2893 }
2894 }
2895}
2896
2897public Action tmrFreezer(Handle timer, any client)
2898{
2899 if( (client = GetClientOfUserId(client)) && IsClientInGame(client) && IsPlayerAlive(client) )
2900 {
2901 if( GetGameTime() - g_fLastFreeze[client] < 1.0 )
2902 {
2903 return Plugin_Continue;
2904 }
2905
2906 PlaySound(client, SOUND_FREEZER);
2907
2908 if( GetEntProp(client, Prop_Send, "m_clrRender") == -1056997376 ) // Our render color
2909 SetEntityRenderColor(client, 255, 255, 255, 255);
2910
2911 SetEntityMoveType(client, MOVETYPE_WALK);
2912 }
2913 return Plugin_Stop;
2914}
2915
2916// ====================================================================================================
2917// EXPLOSION FX - MEDIC
2918// ====================================================================================================
2919public void Explode_Medic(int client, int entity, int index)
2920{
2921 // Grenade Pos
2922 static float vPos[3];
2923 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2924
2925 // Shake
2926 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2927
2928 // Sound
2929 PlaySound(entity, SOUND_BUTTON2);
2930
2931 // Beam Ring
2932 float range = g_GrenadeData[index - 1][CONFIG_RANGE] * 2 + BEAM_OFFSET;
2933 CreateBeamRing(entity, { 0, 150, 0, 255 }, 0.1, range - (range / BEAM_RINGS));
2934
2935 // Heal survivors
2936 int iHealth;
2937 float fHealth;
2938 float vEnd[3];
2939 for( int i = 1; i <= MaxClients; i++ )
2940 {
2941 if( IsClientInGame(i) && GetClientTeam(i) == 2 && IsPlayerAlive(i) )
2942 {
2943 GetClientAbsOrigin(i, vEnd);
2944 if( GetVectorDistance(vPos, vEnd) <= g_GrenadeData[index - 1][CONFIG_RANGE] )
2945 {
2946 iHealth = GetClientHealth(i);
2947 if( iHealth < 100 )
2948 {
2949 iHealth += RoundFloat(g_GrenadeData[index - 1][CONFIG_DAMAGE]);
2950 if( iHealth > 100 )
2951 iHealth = 100;
2952
2953 fHealth = GetTempHealth(i);
2954 if( iHealth + fHealth > 100 )
2955 {
2956 fHealth = 100.0 - iHealth;
2957 SetTempHealth(i, fHealth);
2958 }
2959
2960 SetEntityHealth(i, iHealth);
2961 }
2962 }
2963 }
2964 }
2965}
2966
2967// ====================================================================================================
2968// EXPLOSION FX - VAPORIZER
2969// ====================================================================================================
2970public void Explode_Vaporizer(int client, int entity, int index, bool fromTimer)
2971{
2972 // Grenade Pos
2973 static float vPos[3];
2974 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
2975
2976 // Explosion
2977 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BULLET, true);
2978
2979 // Shake
2980 if( fromTimer == false )
2981 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
2982}
2983
2984void DissolveCommon(int client, int entity, int target, float fDamage)
2985{
2986 // Pos
2987 static float vPos[3], vEnd[3];
2988 GetEntPropVector(entity, Prop_Data, "m_vecOrigin", vPos);
2989 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vEnd);
2990 vEnd[2] += 50;
2991
2992 // Beam
2993 TE_SetupBeamPoints(vPos, vEnd, g_BeamSprite, g_HaloSprite, 30, 0, 0.2, 1.0, 2.0, 0, 0.0, { 50, 50, 150, 150 }, 0);
2994 TE_SendToAll();
2995
2996 // Sound
2997 PlaySound(target, g_sSoundsZap[GetRandomInt(0, 7)]);
2998
2999 // Damage - only dissolve when dead
3000 if( GetEntProp(target, Prop_Data, "m_iHealth") - fDamage > 0 )
3001 return;
3002
3003 // Dissolve
3004 int iOverlayModel = -1;
3005 if( bLMC_Available )
3006 iOverlayModel = LMC_GetEntityOverlayModel(target);
3007
3008 if( target <= MaxClients )
3009 {
3010 int clone = AttachFakeRagdoll(target);
3011 if( clone > 0 )
3012 {
3013 SetEntityRenderMode(clone, RENDER_NONE); // Hide and dissolve clone - method to show more particles
3014 DissolveTarget(client, clone, GetEntProp(target, Prop_Send, "m_zombieClass") == 2 ? 0 : target); // Exclude boomer to producer gibs
3015 }
3016 } else {
3017 SetEntityRenderFx(target, RENDERFX_FADE_FAST);
3018 if( iOverlayModel < 1 )
3019 DissolveTarget(client, target);
3020 else
3021 DissolveTarget(client, target, iOverlayModel);
3022 }
3023}
3024
3025void DissolveTarget(int client, int target, int iOverlayModel = 0)
3026{
3027 // CreateEntityByName "env_entity_dissolver" has broken particles, this way works 100% of the time
3028 float time = GetRandomFloat(0.2, 0.7);
3029
3030 int dissolver = SDKCall(sdkDissolveCreate, iOverlayModel ? iOverlayModel : target, "", GetGameTime() + time, 2, false);
3031 if( dissolver > MaxClients && IsValidEntity(dissolver) )
3032 {
3033 if( target > MaxClients )
3034 {
3035 // Have to kill here because this function is called above the actual hurt in CreateExplosion.
3036 static char sTemp[8];
3037 IntToString(GetEntProp(target, Prop_Data, "m_iHealth") - 10, sTemp, sizeof(sTemp));
3038 DispatchKeyValue(g_iEntityHurt, "Damage", sTemp);
3039 DispatchKeyValue(g_iEntityHurt, "DamageType", "0");
3040 DispatchKeyValue(target, "targetname", "silvershot");
3041 AcceptEntityInput(g_iEntityHurt, "Hurt", client, client);
3042
3043 // Prevent common infected from crashing the server when taking damage from the dissolver.
3044 SDKHook(target, SDKHook_OnTakeDamage, OnCommonDamage);
3045
3046 // Prevent immortal common infected if they fail to die from the dissolver.
3047 // Bug should not happen because the SDKHook was in the wrong section in version <= 1.5 (from Dissolve Infected plugin).
3048 InputKill(target, time + 0.5);
3049 }
3050
3051 SetEntPropFloat(dissolver, Prop_Send, "m_flFadeOutStart", 0.0); // Fixes broken particles
3052
3053 int fader = CreateEntityByName("func_ragdoll_fader");
3054 if( fader != -1 )
3055 {
3056 static float vec[3];
3057 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vec);
3058 TeleportEntity(fader, vec, NULL_VECTOR, NULL_VECTOR);
3059 DispatchSpawn(fader);
3060
3061 SetEntPropVector(fader, Prop_Send, "m_vecMaxs", view_as<float>({ 50.0, 50.0, 50.0 }));
3062 SetEntPropVector(fader, Prop_Send, "m_vecMins", view_as<float>({ -50.0, -50.0, -50.0 }));
3063 SetEntProp(fader, Prop_Send, "m_nSolidType", 2);
3064
3065 InputKill(fader, 0.1);
3066 }
3067 }
3068}
3069
3070int AttachFakeRagdoll(int target)
3071{
3072 int entity = CreateEntityByName("prop_dynamic_ornament");
3073 if( entity != -1 )
3074 {
3075 static char sModel[64];
3076 GetEntPropString(target, Prop_Data, "m_ModelName", sModel, sizeof(sModel));
3077 DispatchKeyValue(entity, "model", sModel);
3078 DispatchSpawn(entity);
3079
3080 SetVariantString("!activator");
3081 AcceptEntityInput(entity, "SetParent", target);
3082 SetVariantString("!activator");
3083 AcceptEntityInput(entity, "SetAttached", target);
3084 }
3085
3086 return entity;
3087}
3088
3089public Action OnCommonDamage(int victim, int &attacker, int &inflictor, float &damage, int &damagetype)
3090{
3091 damage = 0.0;
3092 return Plugin_Handled;
3093}
3094
3095// ====================================================================================================
3096// EXPLOSION FX - EXTINGUISHER
3097// ====================================================================================================
3098void Explode_Extinguisher(int client, int entity, int index)
3099{
3100 // Grenade Pos
3101 static float vPos[3];
3102 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
3103
3104 // Explosion
3105 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BULLET, true);
3106
3107 // Shake
3108 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE] + SHAKE_RANGE, vPos);
3109
3110 // Particles
3111 DisplayParticle(entity, PARTICLE_SPLASH, vPos, NULL_VECTOR, 0.7);
3112
3113 // Sound
3114 int random = GetRandomInt(1, 3);
3115 switch( random )
3116 {
3117 case 1: PlaySound(entity, SOUND_SPLASH1);
3118 case 2: PlaySound(entity, SOUND_SPLASH2);
3119 case 3: PlaySound(entity, SOUND_SPLASH3);
3120 }
3121
3122 // Extinguish fires
3123 float vEnd[3];
3124 int inferno = -1;
3125 while( (inferno = FindEntityByClassname(inferno, "inferno")) != INVALID_ENT_REFERENCE )
3126 {
3127 GetEntPropVector(inferno, Prop_Data, "m_vecAbsOrigin", vEnd);
3128 if( GetVectorDistance(vPos, vEnd) < g_GrenadeData[INDEX_EXTINGUISHER][CONFIG_RANGE] )
3129 {
3130 AcceptEntityInput(inferno, "Kill");
3131 }
3132 }
3133
3134 inferno = -1;
3135 while( (inferno = FindEntityByClassname(inferno, "fire_cracker_blast")) != INVALID_ENT_REFERENCE )
3136 {
3137 GetEntPropVector(inferno, Prop_Data, "m_vecAbsOrigin", vEnd);
3138 if( GetVectorDistance(vPos, vEnd) < g_GrenadeData[INDEX_EXTINGUISHER][CONFIG_RANGE] )
3139 {
3140 AcceptEntityInput(inferno, "Kill");
3141 }
3142 }
3143}
3144
3145// ====================================================================================================
3146// EXPLOSION FX - GLOW
3147// ====================================================================================================
3148void Explode_Glow(int client, int entity, int index)
3149{
3150 // Grenade Pos
3151 static float vPos[3];
3152 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
3153
3154 // Explosion
3155 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BULLET, true);
3156
3157 // Shake
3158 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE], vPos);
3159
3160 // Particles
3161 int particle = DisplayParticle(0, PARTICLE_BOOMER, vPos, NULL_VECTOR);
3162 if( particle ) InputKill(particle, 3.0);
3163
3164 particle = DisplayParticle(0, PARTICLE_SMOKER1, vPos, NULL_VECTOR);
3165 if( particle ) InputKill(particle, 3.0);
3166
3167 particle = DisplayParticle(0, PARTICLE_SMOKER2, vPos, NULL_VECTOR);
3168 if( particle ) InputKill(particle, 3.0);
3169
3170 // Sound
3171 PlaySound(entity, SOUND_GATE, SNDLEVEL_RAIDSIREN);
3172}
3173
3174// ====================================================================================================
3175// EXPLOSION FX - ANTI-GRAVITY
3176// ====================================================================================================
3177void Explode_AntiGravity(int client, int entity, int index)
3178{
3179 // Grenade Pos
3180 static float vPos[3];
3181 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
3182
3183 // Explosion
3184 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BULLET, true);
3185
3186 // Shake
3187 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE], vPos);
3188
3189 // Sound
3190 PlaySound(entity, SOUND_BUTTON2, SNDLEVEL_RAIDSIREN);
3191
3192 // Beam Ring
3193 float range = g_GrenadeData[index - 1][CONFIG_RANGE] * 2 + BEAM_OFFSET;
3194 CreateBeamRing(entity, { 0, 255, 100, 255 }, range - (range / BEAM_RINGS), 0.1);
3195}
3196
3197// ====================================================================================================
3198// EXPLOSION FX - BULLETS
3199// ====================================================================================================
3200void Explode_Bullets(int client, int entity, int index, bool fromTimer)
3201{
3202 // Grenade Pos
3203 static float vPos[3];
3204 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
3205
3206 // Explosion
3207 if( fromTimer == false )
3208 {
3209 CreateExplosion(client, entity, index, 0.0, vPos, DMG_BULLET, true);
3210
3211 // Shake
3212 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE], vPos);
3213 }
3214
3215 // Sound
3216 if( GetRandomInt(0,3) == 3 )
3217 PlaySound(entity, g_sSoundsMiss[GetRandomInt(0, sizeof g_sSoundsMiss - 1)]);
3218 PlaySound(entity, SOUND_SHOOTING, SNDLEVEL_RAIDSIREN);
3219
3220
3221
3222 // Bullets
3223 Handle trace;
3224 static char classname[16];
3225 static float vEnd[3], vAng[3];
3226 float fDamage = g_GrenadeData[index - 1][CONFIG_DAMAGE];
3227 int particle;
3228 int target;
3229 int targ;
3230 bool pass;
3231 vPos[2] += 5.0;
3232
3233 for( int x = 1; x <= 8; x++ )
3234 {
3235 vAng[0] = GetRandomFloat(-20.0, 5.0); // How far up/down tracers point (0=Horizontal, -90=Up.)
3236 vAng[1] = GetRandomFloat(-180.0, 180.0); // Random direction
3237
3238 // Trace + Particles
3239 trace = TR_TraceRayFilterEx(vPos, vAng, MASK_SHOT, RayType_Infinite, _TraceFilter);
3240 TR_GetEndPosition(vEnd, trace);
3241
3242 if( g_bLeft4Dead2 && GetRandomInt(0, 3) == 0 )
3243 L4D_TE_Create_Particle(vPos, vEnd, g_iParticleTracer50);
3244 else
3245 L4D_TE_Create_Particle(vPos, vEnd, g_iParticleTracer);
3246
3247 if( x <= 3 )
3248 {
3249 particle = DisplayParticle(0, PARTICLE_MUZZLE, vPos, vAng);
3250 InputKill(particle, 0.01);
3251 }
3252
3253 // /* // Test to show traces:
3254 // #include <neon_beams> // Put outside of function
3255 // float vEnd[3];
3256 // TR_GetEndPosition(vEnd, trace);
3257 // NeonBeams_TempMap(0, vPos, vEnd, 5.0);
3258 // */
3259
3260 // Validate entity hit
3261 if( TR_DidHit(trace) == true )
3262 {
3263 target = TR_GetEntityIndex(trace);
3264 targ = g_GrenadeTarg[index - 1];
3265 pass = false;
3266
3267 // Valid target. Scale damage
3268 if( target > 0 && target <= MaxClients && (targ & (1<<TARGET_SURVIVOR) || targ & (1<<TARGET_SPECIAL)) )
3269 {
3270 int team = GetClientTeam(target);
3271 if( team == 2 && targ & (1<<TARGET_SURVIVOR) )
3272 {
3273 fDamage = fDamage * g_fConfigSurvivors * g_GrenadeData[index - 1][CONFIG_DMG_SURVIVORS];
3274 pass = true;
3275 }
3276 else if( team == 3 && targ & (1<<TARGET_SPECIAL) )
3277 {
3278 fDamage = fDamage * g_fConfigSpecial * g_GrenadeData[index - 1][CONFIG_DMG_SPECIAL];
3279 pass = true;
3280 }
3281 else if( team == 3 && targ & (1<<TARGET_TANK) && GetEntProp(target, Prop_Send, "m_zombieClass") == (g_bLeft4Dead2 ? 8 : 5) )
3282 {
3283 fDamage = fDamage * g_fConfigTank * g_GrenadeData[index - 1][CONFIG_DMG_TANK];
3284 pass = true;
3285 }
3286 }
3287
3288 if( target > MaxClients && (targ & (1<<TARGET_WITCH) || targ & (1<<TARGET_COMMON) || targ & (1<<TARGET_PHYSICS)) )
3289 {
3290 // Check classname
3291 GetEdictClassname(target, classname, sizeof classname);
3292
3293 if( targ & (1 << TARGET_WITCH) && strcmp(classname, "witch") == 0 )
3294 {
3295 fDamage = fDamage * g_fConfigWitch * g_GrenadeData[index - 1][CONFIG_DMG_WITCH];
3296
3297 pass = true;
3298 }
3299 else if( targ & (1 << TARGET_COMMON) && strcmp(classname, "infected") == 0 )
3300 {
3301 pass = true;
3302 }
3303 else if( targ & (1<<TARGET_PHYSICS) )
3304 {
3305 if(
3306 strcmp(classname, "prop_physics") == 0 ||
3307 strcmp(classname, "weapon_gascan") == 0
3308 )
3309 {
3310 fDamage = fDamage * g_fConfigPhysics * g_GrenadeData[index - 1][CONFIG_DMG_PHYSICS];
3311
3312 pass = true;
3313 }
3314 }
3315 }
3316
3317 if( pass )
3318 {
3319 IntToString(RoundFloat(fDamage), classname, sizeof classname);
3320 DispatchKeyValue(g_iEntityHurt, "Damage", classname);
3321 DispatchKeyValue(target, "targetname", "silvershot");
3322 AcceptEntityInput(g_iEntityHurt, "Hurt", client, client);
3323 DispatchKeyValue(target, "targetname", "");
3324 }
3325 }
3326 delete trace;
3327 }
3328}
3329
3330// ====================================================================================================
3331// EXPLOSION FX - FLAK
3332// ====================================================================================================
3333void Explode_Flak(int client, int entity, int index)
3334{
3335 // Grenade Pos
3336 static float vPos[3];
3337 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
3338
3339 // Shake
3340 CreateShake(g_GrenadeData[index - 1][CONFIG_SHAKE], g_GrenadeData[index - 1][CONFIG_RANGE], vPos);
3341
3342 // Sound
3343 if( g_bLeft4Dead2 )
3344 {
3345 int random = GetRandomInt(1, 4);
3346 switch( random )
3347 {
3348 case 1: PlaySound(entity, SOUND_FIREWORK1);
3349 case 2: PlaySound(entity, SOUND_FIREWORK2);
3350 case 3: PlaySound(entity, SOUND_FIREWORK3);
3351 case 4: PlaySound(entity, SOUND_FIREWORK4);
3352 }
3353 } else {
3354 if( GetRandomInt(0, 1) ) PlaySound(entity, SOUND_EXPLODE3);
3355 else PlaySound(entity, SOUND_EXPLODE5);
3356 }
3357
3358 // Random position
3359 vPos[0] += GetRandomFloat(-g_GrenadeData[index - 1][CONFIG_RANGE], g_GrenadeData[index - 1][CONFIG_RANGE]);
3360 vPos[1] += GetRandomFloat(-g_GrenadeData[index - 1][CONFIG_RANGE], g_GrenadeData[index - 1][CONFIG_RANGE]);
3361 vPos[2] += GetRandomFloat(75.0, 120.0);
3362
3363 // Sparks
3364 int spark = CreateEntityByName("env_spark");
3365 DispatchKeyValue(spark, "angles", "-90 0 0");
3366 DispatchKeyValue(spark, "TrailLength", "1");
3367 DispatchKeyValue(spark, "Magnitude", "5");
3368 TeleportEntity(spark, vPos, NULL_VECTOR, NULL_VECTOR);
3369 DispatchSpawn(spark);
3370 ActivateEntity(spark);
3371 AcceptEntityInput(spark, "SparkOnce");
3372 InputKill(spark, 0.5);
3373
3374 // Particles
3375 if( g_bLeft4Dead2 )
3376 DisplayParticle(spark, PARTICLE_CHARGE, vPos, NULL_VECTOR);
3377 DisplayParticle(spark, PARTICLE_BLAST, vPos, NULL_VECTOR);
3378 DisplayParticle(spark, PARTICLE_PIPE2, vPos, NULL_VECTOR);
3379
3380 // Explosion
3381 vPos[2] -= 75.0;
3382 CreateExplosion(client, entity, index, 150.0, vPos, DMG_BURN);
3383}
3384
3385
3386
3387// ====================================================================================================
3388// STOCKS - CREATE PROJECTILE
3389// ====================================================================================================
3390int CreateProjectile(int entity, int client, int index)
3391{
3392 // Save origin and velocity
3393 static float vPos[3], vAng[3], vVel[3];
3394 GetEntPropVector(entity, Prop_Data, "m_angRotation", vAng);
3395 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
3396 GetEntPropVector(entity, Prop_Send, "m_vInitialVelocity", vVel);
3397 AcceptEntityInput(entity, "Kill");
3398
3399 // Create new projectile
3400 g_bBlockHook = true;
3401 entity = CreateEntityByName("pipe_bomb_projectile"); // prop_physics_override doesn't work with MODEL_SPRAYCAN.
3402 g_bBlockHook = false;
3403
3404 if( entity == -1 )
3405 {
3406 return 0;
3407 }
3408
3409 SetEntProp(entity, Prop_Data, "m_iHammerID", index); // Store mode type
3410 SetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity", client); // Store owner
3411 SetEntPropFloat(entity, Prop_Data, "m_flGravity", g_GrenadeData[index - 1][CONFIG_GRAVITY]);
3412 SetEntPropFloat(entity, Prop_Data, "m_flElasticity", g_GrenadeData[index - 1][CONFIG_ELASTICITY]);
3413
3414 // Set origin and velocity
3415 static float vDir[3];
3416 GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR);
3417 vPos[0] += vDir[0] * 10;
3418 vPos[1] += vDir[1] * 10;
3419 vPos[2] += vDir[2] * 10;
3420 TeleportEntity(entity, vPos, vAng, vVel);
3421 DispatchSpawn(entity);
3422 SetEntityModel(entity, MODEL_SPRAYCAN); // Model after Dispatch otherwise the projectile effects (flashing light + trail) show and model doesn't change.
3423
3424 // Fire and forget - if plugins unloaded the entity and fx will still be deleted.
3425 float tick = g_GrenadeData[index - 1][CONFIG_TICK];
3426 float time = g_GrenadeData[index - 1][CONFIG_TIME];
3427
3428 // + 10 to account for flight time before impact.
3429 InputKill(entity, (time > tick ? time : tick) + g_GrenadeData[index - 1][CONFIG_FUSE] + 10.0);
3430
3431 return entity;
3432}
3433
3434
3435
3436// ====================================================================================================
3437// STOCKS - GRENADE MODE
3438// ====================================================================================================
3439int IsGrenade(int weapon)
3440{
3441 static char classname[20];
3442 GetEdictClassname(weapon, classname, sizeof classname);
3443
3444 if( strcmp(classname[7], "molotov") == 0 ) return 1;
3445 if( strcmp(classname[7], "pipe_bomb") == 0 ) return 2;
3446 if( g_bLeft4Dead2 && strcmp(classname[7], "vomitjar") == 0 ) return 3;
3447
3448 return 0;
3449}
3450
3451void ReplaceColors(char[] translation, int size)
3452{
3453 ReplaceString(translation, size, "{white}", "\x01");
3454 ReplaceString(translation, size, "{cyan}", "\x03");
3455 ReplaceString(translation, size, "{orange}", "\x04");
3456 ReplaceString(translation, size, "{green}", "\x05");
3457}
3458
3459
3460
3461// ====================================================================================================
3462// STOCKS - SOUND
3463// ====================================================================================================
3464void PlaySound(int entity, const char[] sound, int level = SNDLEVEL_NORMAL)
3465{
3466 EmitSoundToAll(sound, entity, level == SNDLEVEL_RAIDSIREN ? SNDCHAN_ITEM : SNDCHAN_AUTO, level);
3467}
3468
3469void StopSounds(int entity)
3470{
3471 StopSound(entity, SNDCHAN_AUTO, SOUND_FLICKER);
3472 StopSound(entity, SNDCHAN_AUTO, SOUND_GAS);
3473 StopSound(entity, SNDCHAN_AUTO, SOUND_GATE);
3474 StopSound(entity, SNDCHAN_AUTO, SOUND_NOISE);
3475 StopSound(entity, SNDCHAN_AUTO, SOUND_SPATIAL);
3476 StopSound(entity, SNDCHAN_AUTO, SOUND_SQUEAK);
3477 StopSound(entity, SNDCHAN_AUTO, SOUND_STEAM);
3478 StopSound(entity, SNDCHAN_AUTO, SOUND_TUNNEL);
3479}
3480
3481
3482
3483// ====================================================================================================
3484// STOCKS - STAGGER
3485// ====================================================================================================
3486void PushCommon(int client, int target, float vPos[3], bool common = true)
3487{
3488 CreateHurtEntity();
3489
3490 if( common && g_bLeft4Dead2 ) DispatchKeyValue(g_iEntityHurt, "DamageType", "33554432"); // DMG_AIRBOAT (1<<25) // Common L4D2
3491 else if( common ) DispatchKeyValue(g_iEntityHurt, "DamageType", "536870912"); // DMG_BUCKSHOT (1<<29) // Common L4D1
3492 else DispatchKeyValue(g_iEntityHurt, "DamageType", "64"); // DMG_BLAST (1<<6) // Witch
3493
3494 DispatchKeyValue(g_iEntityHurt, "Damage", "0");
3495 DispatchKeyValue(target, "targetname", "silvershot");
3496 TeleportEntity(g_iEntityHurt, vPos, NULL_VECTOR, NULL_VECTOR);
3497 AcceptEntityInput(g_iEntityHurt, "Hurt", client, client);
3498 DispatchKeyValue(target, "targetname", "");
3499}
3500
3501void StaggerClient(int userid, const float vPos[3])
3502{
3503 if( g_bLeft4Dead2 )
3504 {
3505 // Credit to Timocop on VScript function
3506 static int iScriptLogic = INVALID_ENT_REFERENCE;
3507 if(iScriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(iScriptLogic))
3508 {
3509 iScriptLogic = EntIndexToEntRef(CreateEntityByName("logic_script"));
3510 if(iScriptLogic == INVALID_ENT_REFERENCE || !IsValidEntity(iScriptLogic))
3511 LogError("Could not create 'logic_script");
3512
3513 DispatchSpawn(iScriptLogic);
3514 }
3515
3516 static char sBuffer[96];
3517 Format(sBuffer, sizeof(sBuffer), "GetPlayerFromUserID(%d).Stagger(Vector(%d,%d,%d))", userid, RoundFloat(vPos[0]), RoundFloat(vPos[1]), RoundFloat(vPos[2]));
3518 SetVariantString(sBuffer);
3519 AcceptEntityInput(iScriptLogic, "RunScriptCode");
3520 AcceptEntityInput(iScriptLogic, "Kill");
3521 } else {
3522 userid = GetClientOfUserId(userid);
3523 SDKCall(sdkStaggerClient, userid, userid, vPos); // Stagger: SDKCall method
3524 }
3525}
3526
3527
3528
3529// ====================================================================================================
3530// STOCKS - EXPLOSION
3531// ====================================================================================================
3532void CreateExplosion(int client, int entity, int index, float range = 0.0, float vPos[3], int damagetype = DMG_GENERIC, bool ignorePhysics = false)
3533{
3534 int targ = g_GrenadeTarg[index - 1];
3535 if( targ == 0 ) return;
3536
3537
3538
3539 // Damage tick timeout. Tesla and Vaporizer have their own timeout.
3540 if( index -1 != INDEX_TESLA && index -1 != INDEX_VAPORIZER )
3541 {
3542 if( GetGameTime() - g_fLastTesla[entity] < g_GrenadeData[index - 1][CONFIG_DMG_TICK] ) return;
3543 g_fLastTesla[entity] = GetGameTime();
3544 }
3545
3546
3547
3548 // Range
3549 float range_damage;
3550 float range_stumble;
3551 if( range == 0.0 )
3552 {
3553 range_damage = g_GrenadeData[index - 1][CONFIG_RANGE];
3554 range_stumble = g_GrenadeData[index - 1][CONFIG_STUMBLE];
3555 } else {
3556 // From Cluster projectiles
3557 range_damage = range;
3558 range_stumble = range;
3559 }
3560
3561
3562
3563 // Vars
3564 ArrayList aGodMode = new ArrayList(); // GodMode list, prevent players/common/witch from taking env_explosion damage.
3565 float damage = g_GrenadeData[index - 1][CONFIG_DAMAGE];
3566 float fDamage, fDistance;
3567 static float vEnd[3];
3568 static char sTemp[16];
3569 int team;
3570 int i;
3571
3572
3573
3574 // Hurt survivors/special/common/witch with scaled damage
3575 vPos[2] -= 5.0;
3576 CreateHurtEntity();
3577
3578
3579
3580 // ==================================================
3581 // PLAYERS - SURVIVORS + SPECIAL INFECTED
3582 // ==================================================
3583 // Loop through players, workout range and scale damage according distance. Stumble if needed.
3584 // Enable godmode so the explosion below does not hurt, and only our scaled damage affects clients.
3585 // ==================================================
3586 if( (targ & (1<<TARGET_SURVIVOR) || targ & (1<<TARGET_SPECIAL)) )
3587 {
3588 int clients[MAXPLAYERS+1]; // Flashbang
3589 int flashcount;
3590
3591 for( i = 1; i <= MaxClients; i++ )
3592 {
3593 if( IsClientInGame(i) && IsPlayerAlive(i) )
3594 {
3595 team = GetClientTeam(i);
3596 if( team == 2 ? targ & (1<<TARGET_SURVIVOR) : targ & (1<<TARGET_SPECIAL) )
3597 {
3598 GetEntPropVector(i, Prop_Data, "m_vecOrigin", vEnd);
3599 fDistance = GetVectorDistance(vPos, vEnd);
3600
3601 // Stumble
3602 // In range, not Cluster projectiles, not Tesla, not BlackHole, not Anti-Gravity
3603 if( range == 0.0 && range_stumble && fDistance < range_stumble && index -1 != INDEX_TESLA && index -1 != INDEX_BLACKHOLE && index -1 != INDEX_ANTIGRAVITY )
3604 {
3605 StaggerClient(GetClientUserId(i), vPos);
3606 }
3607
3608 // Scale Damage to Range
3609 if( fDistance <= range_damage )
3610 {
3611 if( index -1 == INDEX_VAPORIZER )
3612 {
3613 fDamage = damage; // Full damage
3614 } else {
3615 fDamage = fDistance / (index -1 == INDEX_TESLA ? range_damage * 2 : range_damage); // Double Tesla range so damage is more when entering.
3616 fDamage = damage * fDamage;
3617 fDamage = damage - fDamage;
3618 }
3619
3620 if( team == 3 )
3621 {
3622 if( GetEntProp(i, Prop_Send, "m_zombieClass") == (g_bLeft4Dead2 ? 8 : 5) )
3623 {
3624 if( targ & (1<<TARGET_TANK) )
3625 fDamage = fDamage * g_fConfigTank * g_GrenadeData[index - 1][CONFIG_DMG_TANK];
3626 else
3627 fDamage = 0.0;
3628 }
3629 else
3630 fDamage = fDamage * g_fConfigSpecial * g_GrenadeData[index - 1][CONFIG_DMG_SPECIAL];
3631 } else {
3632 fDamage = fDamage * g_fConfigSurvivors * g_GrenadeData[index - 1][CONFIG_DMG_SURVIVORS];
3633 }
3634
3635 // Round to 1 because damage fall off scaling can set the value to above 0 and below 1. So affected guaranteed to lose 1 HP.
3636 if( fDamage > 0.0 && fDamage < 1.0 )
3637 fDamage = 1.0;
3638
3639 if( fDamage > 0.0 )
3640 {
3641 // ==================================================
3642 // Grenade mode specific things:
3643 // ==================================================
3644 if( GrenadeSpecificExplosion(i, client, entity, index, TARGET_SURVIVOR, 0.0, fDistance, vPos, vEnd) == false )
3645 fDamage = 0.0;
3646
3647 // Damage
3648 if( fDamage != 0.0 && (!g_bLeft4Dead2 || index -1 != INDEX_CHEMICAL) ) // Chemical mode in L4D2 only needs to damage non-survivor, the spit already damages them.
3649 {
3650 clients[flashcount++] = i;
3651
3652 // Hurt
3653 FloatToString(fDamage, sTemp, sizeof(sTemp));
3654 DispatchKeyValue(g_iEntityHurt, "Damage", sTemp);
3655 IntToString(damagetype, sTemp, sizeof(sTemp));
3656 DispatchKeyValue(g_iEntityHurt, "DamageType", sTemp);
3657 DispatchKeyValue(i, "targetname", "silvershot");
3658
3659 if( i == client ) // Otherwise can't hurt self. Also to avoid red flash on Flashbang
3660 AcceptEntityInput(g_iEntityHurt, "Hurt");
3661 else
3662 AcceptEntityInput(g_iEntityHurt, "Hurt", client, client);
3663
3664 DispatchKeyValue(i, "targetname", "");
3665 }
3666 }
3667
3668 // GodMode
3669 aGodMode.Push(i);
3670 SetEntProp(i, Prop_Data, "m_takedamage", 0); // Prevent taking damage from env_explosion
3671 }
3672 }
3673 }
3674 }
3675
3676 // Flashbang
3677 if( flashcount && index -1 == INDEX_FLASHBANG )
3678 {
3679 // Blind
3680 Handle message = StartMessageEx(g_FadeUserMsgId, clients, flashcount);
3681 BfWrite bf = UserMessageToBfWrite(message);
3682 bf.WriteShort(RoundFloat(g_GrenadeData[index - 1][CONFIG_TIME]) * 1000);
3683 bf.WriteShort(100);
3684 bf.WriteShort(0x0001);
3685 bf.WriteByte(255);
3686 bf.WriteByte(255);
3687 bf.WriteByte(255);
3688 bf.WriteByte(240);
3689 EndMessage();
3690
3691 L4D_TE_Create_Particle(_, _, g_iParticleBashed, _, _, false);
3692 TE_Send(clients, flashcount);
3693 }
3694 }
3695
3696
3697
3698 // ==================================================
3699 // COMMON INFECTED - Loop
3700 // ==================================================
3701 if( targ & (1 << TARGET_COMMON) )
3702 {
3703 int numTesla;
3704 int numVapo;
3705
3706 i = -1;
3707 while( (i = FindEntityByClassname(i, "infected")) != INVALID_ENT_REFERENCE && GetEntProp(i, Prop_Data, "m_iHealth") > 0 )
3708 {
3709 GetEntPropVector(i, Prop_Data, "m_vecOrigin", vEnd);
3710 fDistance = GetVectorDistance(vPos, vEnd);
3711 if( fDistance <= range_damage )
3712 {
3713 if( index -1 == INDEX_VAPORIZER )
3714 {
3715 fDamage = damage;
3716 } else {
3717 fDamage = fDistance / (index -1 == INDEX_TESLA ? range_damage * 2 : range_damage);
3718 fDamage = damage * fDamage;
3719 fDamage = damage - fDamage;
3720 }
3721
3722
3723
3724 if( fDamage != 0.0 )
3725 {
3726 // ==================================================
3727 // Grenade mode specific things:
3728 // ==================================================
3729 if( GrenadeSpecificExplosion(i, client, entity, index, TARGET_COMMON, fDamage, fDistance, vPos, vEnd) == false )
3730 fDamage = 0.0;
3731
3732
3733
3734 // Damage
3735 if( fDamage != 0.0 )
3736 {
3737 FloatToString(fDamage, sTemp, sizeof(sTemp));
3738 DispatchKeyValue(g_iEntityHurt, "Damage", sTemp);
3739
3740 if( range_stumble && fDistance <= range_stumble )
3741 {
3742 if( g_bLeft4Dead2 ) DispatchKeyValue(g_iEntityHurt, "DamageType", "33554432"); // DMG_AIRBOAT (1<<25) // Common L4D2
3743 else DispatchKeyValue(g_iEntityHurt, "DamageType", "536870912"); // DMG_BUCKSHOT (1<<29) // Common L4D1
3744 }
3745 else
3746 DispatchKeyValue(g_iEntityHurt, "DamageType", "0");
3747
3748 DispatchKeyValue(i, "targetname", "silvershot");
3749
3750 if( index -1 == INDEX_BLACKHOLE )
3751 {
3752 static float vAng[3];
3753 MakeVectorFromPoints(vPos, vEnd, vAng);
3754 NormalizeVector(vAng, vAng);
3755 vEnd[0] += vAng[0] * 10;
3756 vEnd[1] += vAng[1] * 10;
3757 vEnd[2] += vAng[2] * 10;
3758 TeleportEntity(g_iEntityHurt, vEnd, NULL_VECTOR, NULL_VECTOR);
3759 } else {
3760 TeleportEntity(g_iEntityHurt, vPos, NULL_VECTOR, NULL_VECTOR);
3761 }
3762
3763 AcceptEntityInput(g_iEntityHurt, "Hurt", client, client);
3764 DispatchKeyValue(i, "targetname", "");
3765 }
3766 }
3767
3768
3769
3770 // GodMode
3771 aGodMode.Push(i);
3772 SetEntProp(i, Prop_Data, "m_takedamage", 0); // Prevent taking damage from env_explosion
3773
3774
3775
3776 if( fDamage != 0.0 )
3777 {
3778 if( index -1 == INDEX_TESLA )
3779 {
3780 if( numTesla++ >= 3 )
3781 {
3782 numTesla = 0;
3783 break;
3784 }
3785 }
3786 else if( index -1 == INDEX_VAPORIZER )
3787 {
3788 if( numVapo++ >= 2 )
3789 {
3790 numVapo = 0;
3791 break;
3792 }
3793 }
3794 }
3795 }
3796 }
3797 }
3798
3799
3800
3801 // ==================================================
3802 // WITCH - Loop
3803 // ==================================================
3804 if( targ & (1 << TARGET_WITCH) )
3805 {
3806 i = -1;
3807 while( (i = FindEntityByClassname(i, "witch")) != INVALID_ENT_REFERENCE )
3808 {
3809 GetEntPropVector(i, Prop_Data, "m_vecOrigin", vEnd);
3810 fDistance = GetVectorDistance(vPos, vEnd);
3811 if( fDistance <= range_damage )
3812 {
3813 if( index -1 == INDEX_VAPORIZER )
3814 {
3815 fDamage = damage;
3816 } else {
3817 fDamage = fDistance / (index -1 == INDEX_TESLA ? range_damage * 2 : range_damage);
3818 fDamage = damage * fDamage;
3819 fDamage = damage - fDamage;
3820 fDamage = fDamage * g_fConfigWitch * g_GrenadeData[index - 1][CONFIG_DMG_WITCH];
3821 }
3822
3823
3824
3825 if( fDamage != 0.0 )
3826 {
3827 // ==================================================
3828 // Grenade mode specific things:
3829 // ==================================================
3830 if( GrenadeSpecificExplosion(i, client, entity, index, TARGET_WITCH, 0.0, fDistance, vPos, vEnd) == false )
3831 fDamage = 0.0;
3832
3833
3834
3835 // Damage
3836 if( fDamage != 0.0 )
3837 {
3838 FloatToString(fDamage, sTemp, sizeof(sTemp));
3839 DispatchKeyValue(g_iEntityHurt, "Damage", sTemp);
3840
3841 if( range_stumble && fDistance <= range_stumble )
3842 DispatchKeyValue(g_iEntityHurt, "DamageType", "64"); // DMG_BLAST (1<<6) // Witch
3843 else
3844 DispatchKeyValue(g_iEntityHurt, "DamageType", "0");
3845
3846 DispatchKeyValue(i, "targetname", "silvershot");
3847 TeleportEntity(g_iEntityHurt, index -1 == INDEX_BLACKHOLE ? vEnd : vPos, NULL_VECTOR, NULL_VECTOR);
3848 AcceptEntityInput(g_iEntityHurt, "Hurt", client, client);
3849 DispatchKeyValue(i, "targetname", "");
3850 }
3851 }
3852
3853
3854
3855 // GodMode
3856 aGodMode.Push(i);
3857 SetEntProp(i, Prop_Data, "m_takedamage", 0); // Prevent taking damage from env_explosion
3858 }
3859 }
3860 }
3861
3862
3863
3864 // ==================================================
3865 // PHYSICS EXPLOSION
3866 // ==================================================
3867 if( ignorePhysics == false && targ & (1 << TARGET_PHYSICS) )
3868 {
3869 if( g_GrenadeData[index - 1][CONFIG_DMG_PHYSICS] )
3870 {
3871 int explo = CreateEntityByName("env_explosion");
3872 fDamage = damage * g_fConfigPhysics * g_GrenadeData[index - 1][CONFIG_DMG_PHYSICS];
3873 // FloatToString(fDamage, sTemp, sizeof(sTemp));
3874 // DispatchKeyValue(explo, "iMagnitude", sTemp);
3875 // FloatToString(range_damage, sTemp, sizeof(sTemp));
3876 // DispatchKeyValue(explo, "iRadiusOverride", sTemp);
3877 DispatchKeyValueFloat(explo, "iMagnitude", fDamage * 2);
3878 DispatchKeyValue(explo, "spawnflags", "18301");
3879 SetEntPropEnt(explo, Prop_Send, "m_hOwnerEntity", client);
3880 DispatchSpawn(explo);
3881 TeleportEntity(explo, vPos, NULL_VECTOR, NULL_VECTOR);
3882 AcceptEntityInput(explo, "Explode");
3883
3884 InputKill(explo, 0.3);
3885 }
3886 }
3887
3888
3889
3890 // Reset GodMode
3891 for( i = 0; i < aGodMode.Length; i++ )
3892 {
3893 SetEntProp(aGodMode.Get(i), Prop_Data, "m_takedamage", 2);
3894 }
3895
3896 delete aGodMode;
3897}
3898
3899
3900
3901// ====================================================================================================
3902// STOCKS - GRENADE SPECIFIC EXPLOSION
3903// ====================================================================================================
3904bool GrenadeSpecificExplosion(int target, int client, int entity, int index, int type, float fDamage, float fDistance, float vPos[3], float vEnd[3])
3905{
3906 // ==================================================
3907 // BLACKHOLE - Pull into center
3908 // ==================================================
3909 if( index -1 == INDEX_BLACKHOLE )
3910 {
3911 if( type == TARGET_SURVIVOR )
3912 {
3913 MakeVectorFromPoints(vEnd, vPos, vEnd);
3914 NormalizeVector(vEnd, vEnd);
3915 ScaleVector(vEnd, fDistance);
3916
3917 if( fDistance < 150 && GetEntProp(target, Prop_Send, "m_fFlags") & FL_ONGROUND == 0 ) // Reduce height when in air near center
3918 vEnd[2] = 100.0;
3919 else
3920 vEnd[2] = 300.0;
3921
3922 TeleportEntity(target, NULL_VECTOR, NULL_VECTOR, vEnd);
3923 }
3924 else
3925 {
3926 if( type == TARGET_WITCH )
3927 {
3928 static float vAng[3];
3929 MakeVectorFromPoints(vPos, vEnd, vAng);
3930 NormalizeVector(vAng, vAng);
3931 vEnd[0] += vAng[0] * 10;
3932 vEnd[1] += vAng[1] * 10;
3933 vEnd[2] += vAng[2] * 10;
3934
3935 PushCommon(client, target, vEnd, false);
3936 }
3937 }
3938 }
3939
3940 // ==================================================
3941 // TESLA - Push away
3942 // ==================================================
3943 else if( index -1 == INDEX_TESLA )
3944 {
3945 // Check duration
3946 if( GetGameTime() - g_fLastTesla[target] >= g_GrenadeData[INDEX_TESLA][CONFIG_DMG_TICK] )
3947 {
3948 g_fLastTesla[entity] = GetGameTime();
3949 g_fLastTesla[target] = GetGameTime();
3950
3951 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vEnd);
3952 vPos[2] += 50.0;
3953 vEnd[2] += 50.0;
3954 if( IsVisibleTo(vPos, vEnd) )
3955 {
3956 if( type == TARGET_SURVIVOR )
3957 {
3958 MakeVectorFromPoints(vPos, vEnd, vEnd);
3959 NormalizeVector(vEnd, vEnd);
3960 ScaleVector(vEnd, 400.0);
3961 vEnd[2] = 300.0;
3962 TeleportEntity(target, NULL_VECTOR, NULL_VECTOR, vEnd);
3963 }
3964
3965 TeslaShock(entity, target);
3966 vPos[2] -= 50.0;
3967 } else {
3968 return false;
3969 }
3970 } else {
3971 return false;
3972 }
3973 }
3974
3975 // ==================================================
3976 // FLASHBANG - Blind
3977 // ==================================================
3978 else if( index -1 == INDEX_FLASHBANG )
3979 {
3980 if( type == TARGET_SURVIVOR )
3981 {
3982 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vEnd);
3983 vPos[2] += 50.0;
3984 vEnd[2] += 50.0;
3985 if( IsVisibleTo(vPos, vEnd) )
3986 {
3987 SDKCall(sdkDeafenClient, target, 1.0, 0.0, 0.01 );
3988 }
3989 }
3990 }
3991
3992 // ==================================================
3993 // VAPORIZER - Dissolve
3994 // ==================================================
3995 else if( index -1 == INDEX_VAPORIZER )
3996 {
3997 if( GetGameTime() - GetEntPropFloat(target, Prop_Data, "m_flCreateTime") > g_GrenadeData[index - 1][CONFIG_DMG_TICK] )
3998 {
3999 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vEnd);
4000 vEnd[2] += 50.0;
4001 if( IsVisibleTo(vPos, vEnd) )
4002 {
4003 SetEntPropFloat(target, Prop_Data, "m_flCreateTime", GetGameTime());
4004
4005 // Does not happen here?
4006 // Prevent error msg: "Entity 157 (class 'pipe_bomb_projectile') reported ENTITY_CHANGE_NONE but 'm_flCreateTime' changed."
4007 // int offset = FindDataMapInfo(target, "m_flCreateTime");
4008 // ChangeEdictState(target, offset);
4009
4010 DissolveCommon(client, entity, target, fDamage);
4011 return true;
4012 }
4013 }
4014
4015 return false;
4016 }
4017
4018 // ==================================================
4019 // GLOW
4020 // ==================================================
4021 else if( index -1 == INDEX_GLOW )
4022 {
4023 if( GetEntProp(target, Prop_Data, "m_iHammerID") == 0 && GetEntProp(target, Prop_Send, "m_glowColorOverride") == 0 ) // Avoid conflict with Mutant Zombies and already glowing.
4024 {
4025 SetEntProp(target, Prop_Send, "m_nGlowRange", RoundFloat(g_GrenadeData[index - 1][CONFIG_RANGE] * 4));
4026 SetEntProp(target, Prop_Send, "m_iGlowType", 3); // 2 = Requires line of sight. 3 = Glow through walls.
4027 SetEntProp(target, Prop_Send, "m_glowColorOverride", 38655); // GetColor("255 150 0");
4028
4029 CreateTimer(g_GrenadeData[index - 1][CONFIG_TIME], tmrResetGlow, target <= MaxClients ? GetClientUserId(target) : EntIndexToEntRef(target));
4030 }
4031 return false;
4032 }
4033
4034 // ==================================================
4035 // ANTI-GRAVITY - Teleport up
4036 // ==================================================
4037 else if( index -1 == INDEX_ANTIGRAVITY )
4038 {
4039 if( type == TARGET_SURVIVOR )
4040 {
4041 static float vVel[3];
4042 GetEntPropVector(target, Prop_Data, "m_vecAbsVelocity", vVel);
4043 if( GetEntProp(target, Prop_Send, "m_fFlags") & FL_ONGROUND )
4044 vVel[2] = 350.0;
4045 else
4046 vVel[2] = 100.0;
4047 TeleportEntity(target, NULL_VECTOR, NULL_VECTOR, vVel);
4048
4049 AcceptEntityInput(client, "DisableLedgeHang");
4050 SetEntityGravity(target, 0.4);
4051 CreateTimer(0.1, tmrResetGravity, GetClientUserId(target), TIMER_REPEAT);
4052 }
4053 }
4054
4055 // ==================================================
4056 // EXTINGUISHER
4057 // ==================================================
4058 else if( index -1 == INDEX_EXTINGUISHER )
4059 {
4060 ExtinguishEntity(target);
4061 return false;
4062 }
4063
4064 return true;
4065}
4066
4067public Action tmrResetGravity(Handle timer, any target)
4068{
4069 if( (target = GetClientOfUserId(target)) )
4070 if( target )
4071 {
4072 if( GetEntProp(target, Prop_Send, "m_fFlags") & FL_ONGROUND )
4073 {
4074 AcceptEntityInput(target, "EnableLedgeHang");
4075 SetEntityGravity(target, 1.0);
4076 } else {
4077 return Plugin_Continue;
4078 }
4079 }
4080
4081 return Plugin_Stop;
4082}
4083
4084public Action tmrResetGlow(Handle timer, any target)
4085{
4086 target = ValidTargetRef(target);
4087 if( target && GetEntProp(target, Prop_Send, "m_glowColorOverride") == 38655 ) // GetColor("255 150 0");
4088 {
4089 SetEntProp(target, Prop_Send, "m_nGlowRange", 0);
4090 SetEntProp(target, Prop_Send, "m_iGlowType", 0);
4091 SetEntProp(target, Prop_Send, "m_glowColorOverride", 0);
4092 }
4093}
4094
4095int ValidTargetRef(int target)
4096{
4097 if( target < 0 )
4098 {
4099 if( (target = EntRefToEntIndex(target)) != INVALID_ENT_REFERENCE )
4100 return target;
4101 } else {
4102 if( (target = GetClientOfUserId(target)) )
4103 return target;
4104 }
4105
4106 return 0;
4107}
4108
4109/*
4110int GetColor(char sTemp[32])
4111{
4112 static char sColors[3][4];
4113 ExplodeString(sTemp, " ", sColors, 3, 4);
4114
4115 int color;
4116 color = StringToInt(sColors[0]);
4117 color += 256 * StringToInt(sColors[1]);
4118 color += 65536 * StringToInt(sColors[2]);
4119 return color;
4120}
4121// */
4122
4123
4124
4125// ====================================================================================================
4126// STOCKS - SHAKE
4127// ====================================================================================================
4128void CreateShake(float intensity, float range, float vPos[3])
4129{
4130 if( intensity == 0.0 ) return;
4131
4132 int entity = CreateEntityByName("env_shake");
4133 if( entity == -1 )
4134 {
4135 LogError("Failed to create 'env_shake'");
4136 return;
4137 }
4138
4139 static char sTemp[8];
4140 FloatToString(intensity, sTemp, sizeof sTemp);
4141 DispatchKeyValue(entity, "amplitude", sTemp);
4142 DispatchKeyValue(entity, "frequency", "1.5");
4143 DispatchKeyValue(entity, "duration", "0.9");
4144 FloatToString(range, sTemp, sizeof sTemp);
4145 DispatchKeyValue(entity, "radius", sTemp);
4146 DispatchKeyValue(entity, "spawnflags", "8");
4147 DispatchSpawn(entity);
4148 ActivateEntity(entity);
4149 AcceptEntityInput(entity, "Enable");
4150
4151 TeleportEntity(entity, vPos, NULL_VECTOR, NULL_VECTOR);
4152 AcceptEntityInput(entity, "StartShake");
4153 RemoveEdict(entity);
4154}
4155
4156
4157
4158// ====================================================================================================
4159// STOCKS - HEALTH
4160// ====================================================================================================
4161float GetTempHealth(int client)
4162{
4163 float fGameTime = GetGameTime();
4164 float fHealthTime = GetEntPropFloat(client, Prop_Send, "m_healthBufferTime");
4165 float fHealth = GetEntPropFloat(client, Prop_Send, "m_healthBuffer");
4166 fHealth -= (fGameTime - fHealthTime) * g_hDecayDecay.FloatValue;
4167 return fHealth < 0.0 ? 0.0 : fHealth;
4168}
4169
4170void SetTempHealth(int client, float fHealth)
4171{
4172 SetEntPropFloat(client, Prop_Send, "m_healthBuffer", fHealth < 0.0 ? 0.0 : fHealth );
4173 SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", GetGameTime());
4174}
4175
4176
4177
4178// ====================================================================================================
4179// STOCKS - HURT
4180// ====================================================================================================
4181void CreateHurtEntity()
4182{
4183 if( !g_iEntityHurt || EntRefToEntIndex(g_iEntityHurt) == INVALID_ENT_REFERENCE )
4184 {
4185 g_iEntityHurt = CreateEntityByName("point_hurt");
4186 DispatchKeyValue(g_iEntityHurt, "DamageTarget", "silvershot");
4187 DispatchSpawn(g_iEntityHurt);
4188 g_iEntityHurt = EntIndexToEntRef(g_iEntityHurt);
4189 }
4190}
4191
4192void CreateFires(int target, int client, bool gascan)
4193{
4194 int entity = CreateEntityByName("prop_physics");
4195 if( entity != -1 )
4196 {
4197 if( gascan ) SetEntityModel(entity, MODEL_GASCAN);
4198 else SetEntityModel(entity, MODEL_CRATE);
4199
4200 static float vPos[3];
4201 GetEntPropVector(target, Prop_Data, "m_vecOrigin", vPos);
4202 vPos[2] += 10.0;
4203 TeleportEntity(entity, vPos, NULL_VECTOR, NULL_VECTOR);
4204 DispatchSpawn(entity);
4205
4206 SetEntData(entity, GetEntSendPropOffs(entity, "m_CollisionGroup"), 1, 1, true);
4207 SetEntPropEnt(entity, Prop_Data, "m_hPhysicsAttacker", client);
4208 SetEntPropFloat(entity, Prop_Data, "m_flLastPhysicsInfluenceTime", GetGameTime());
4209 SetEntityRenderMode(entity, RENDER_TRANSCOLOR);
4210 SetEntityRenderColor(entity, 0, 0, 0, 0);
4211 AcceptEntityInput(entity, "Break");
4212 }
4213}
4214
4215void InputKill(int entity, float time)
4216{
4217 static char temp[40];
4218 Format(temp, sizeof temp, "OnUser4 !self:Kill::%f:-1", time);
4219 SetVariantString(temp);
4220 AcceptEntityInput(entity, "AddOutput");
4221 AcceptEntityInput(entity, "FireUser4");
4222}
4223
4224
4225
4226// ====================================================================================================
4227// STOCKS - TRIGGER
4228// ====================================================================================================
4229void TriggerMultipleDamage(int entity, int index, float range, float vPos[3])
4230{
4231 int trigger = CreateEntityByName("trigger_multiple");
4232 DispatchKeyValue(trigger, "spawnflags", "1");
4233 DispatchKeyValue(trigger, "entireteam", "0");
4234 DispatchKeyValue(trigger, "allowincap", "1");
4235 DispatchKeyValue(trigger, "allowghost", "0");
4236 SetEntityModel(trigger, MODEL_BOUNDING);
4237 DispatchSpawn(trigger);
4238
4239 SetEntProp(trigger, Prop_Data, "m_iHammerID", index);
4240 SetEntProp(trigger, Prop_Send, "m_nSolidType", 2);
4241
4242 // Box size
4243 range /= 2;
4244 static float vMins[3];
4245 vMins[0] = -range;
4246 vMins[1] = -range;
4247 static float vMaxs[3];
4248 vMaxs[0] = range;
4249 vMaxs[1] = range;
4250 vMaxs[2] = 70.0;
4251
4252 SetEntPropVector(trigger, Prop_Send, "m_vecMins", vMins);
4253 SetEntPropVector(trigger, Prop_Send, "m_vecMaxs", vMaxs);
4254
4255 TeleportEntity(trigger, vPos, NULL_VECTOR, NULL_VECTOR);
4256 SetVariantString("!activator");
4257 AcceptEntityInput(trigger, "SetParent", entity);
4258
4259 // Collision hooks
4260 SDKHook(trigger, SDKHook_Touch, OnTouchTriggerMultple);
4261
4262 // Freezer collision
4263 if( index - 1 == INDEX_FREEZER && (g_GrenadeTarg[INDEX_FREEZER] & (1 << TARGET_COMMON) || g_GrenadeTarg[INDEX_FREEZER] & (1 << TARGET_WITCH)) ) // Freezer and common only
4264 SDKHook(trigger, SDKHook_StartTouch, OnTouchTriggerFreezer2);
4265}
4266
4267public void OnTouchTriggerMultple(int trigger, int target)
4268{
4269 // Check duration
4270 int index = GetEntProp(trigger, Prop_Data, "m_iHammerID") - 1;
4271
4272 // Check classname
4273 static char classname[12];
4274 GetEdictClassname(target, classname, sizeof classname);
4275 if(
4276 (index == INDEX_SHIELD && strcmp(classname, "player") == 0) ||
4277 (index != INDEX_SHIELD && strcmp(classname, "player") == 0 || strcmp(classname, "infected") == 0 || strcmp(classname, "witch") == 0)
4278 )
4279 {
4280 switch( index )
4281 {
4282 case INDEX_FREEZER: OnTouchTriggerFreezer (trigger, target);
4283 case INDEX_SHIELD: OnTouchTriggerShield (trigger, target);
4284 }
4285 }
4286}
4287
4288
4289
4290// ====================================================================================================
4291// STOCKS - FX
4292// ====================================================================================================
4293int MakeLightDynamic(int target, const float vPos[3])
4294{
4295 int entity = CreateEntityByName("light_dynamic");
4296 if( entity == -1 )
4297 {
4298 LogError("Failed to create 'light_dynamic'");
4299 return 0;
4300 }
4301
4302 DispatchKeyValue(entity, "_light", "0 255 0 0");
4303 DispatchKeyValue(entity, "brightness", "0.1");
4304 DispatchKeyValueFloat(entity, "spotlight_radius", 32.0);
4305 DispatchKeyValueFloat(entity, "distance", 600.0);
4306 DispatchKeyValue(entity, "style", "6");
4307 DispatchSpawn(entity);
4308 AcceptEntityInput(entity, "TurnOff");
4309
4310 TeleportEntity(entity, vPos, NULL_VECTOR, NULL_VECTOR);
4311
4312 // Attach
4313 if( target )
4314 {
4315 SetVariantString("!activator");
4316 AcceptEntityInput(entity, "SetParent", target);
4317 }
4318
4319 return entity;
4320}
4321
4322void MakeEnvSteam(int target, const float vPos[3], const float vAng[3], const char[] sColor)
4323{
4324 int entity = CreateEntityByName("env_steam");
4325 if( entity == -1 )
4326 {
4327 LogError("Failed to create 'env_steam'");
4328 return;
4329 }
4330
4331 static char sTemp[32];
4332 Format(sTemp, sizeof sTemp, "silv_steam_%d", target);
4333 DispatchKeyValue(entity, "targetname", sTemp);
4334 DispatchKeyValue(entity, "SpawnFlags", "1");
4335 DispatchKeyValue(entity, "rendercolor", sColor);
4336 DispatchKeyValue(entity, "SpreadSpeed", "10");
4337 DispatchKeyValue(entity, "Speed", "100");
4338 DispatchKeyValue(entity, "StartSize", "5");
4339 DispatchKeyValue(entity, "EndSize", "10");
4340 DispatchKeyValue(entity, "Rate", "50");
4341 DispatchKeyValue(entity, "JetLength", "100");
4342 DispatchKeyValue(entity, "renderamt", "150");
4343 DispatchKeyValue(entity, "InitialState", "1");
4344 DispatchSpawn(entity);
4345 AcceptEntityInput(entity, "TurnOn");
4346 TeleportEntity(entity, vPos, vAng, NULL_VECTOR);
4347
4348 // Attach
4349 if( target )
4350 {
4351 SetVariantString("!activator");
4352 AcceptEntityInput(entity, "SetParent", target);
4353 }
4354
4355 return;
4356}
4357
4358void CreateEnvSprite(int target, const char[] sColor)
4359{
4360 int entity = CreateEntityByName("env_sprite");
4361 if( entity == -1)
4362 {
4363 LogError("Failed to create 'env_sprite'");
4364 return;
4365 }
4366
4367 DispatchKeyValue(entity, "rendercolor", sColor);
4368 DispatchKeyValue(entity, "model", MODEL_SPRITE);
4369 DispatchKeyValue(entity, "spawnflags", "3");
4370 DispatchKeyValue(entity, "rendermode", "9");
4371 DispatchKeyValue(entity, "GlowProxySize", "0.1");
4372 DispatchKeyValue(entity, "renderamt", "175");
4373 DispatchKeyValue(entity, "scale", "0.1");
4374 DispatchSpawn(entity);
4375
4376 // Attach
4377 if( target )
4378 {
4379 SetVariantString("!activator");
4380 AcceptEntityInput(entity, "SetParent", target);
4381 }
4382}
4383
4384void CreateBeamRing(int entity, int iColor[4], float min, float max)
4385{
4386 // Grenade Pos
4387 static float vPos[3];
4388 GetEntPropVector(entity, Prop_Data, "m_vecAbsOrigin", vPos);
4389
4390 // Make beam rings
4391 for( int i = 1; i <= BEAM_RINGS; i++ )
4392 {
4393 vPos[2] += 20;
4394 TE_SetupBeamRingPoint(vPos, min, max, g_BeamSprite, g_HaloSprite, 0, 15, 1.0, 1.0, 2.0, iColor, 20, 0);
4395 TE_SendToAll();
4396 }
4397}
4398
4399
4400
4401// ====================================================================================================
4402// STOCKS - PARTICLES
4403// ====================================================================================================
4404int DisplayParticle(int target, const char[] sParticle, const float vPos[3], const float vAng[3], float refire = 0.0)
4405{
4406 int entity = CreateEntityByName("info_particle_system");
4407 if( entity == -1)
4408 {
4409 LogError("Failed to create 'info_particle_system'");
4410 return 0;
4411 }
4412
4413 DispatchKeyValue(entity, "effect_name", sParticle);
4414 DispatchSpawn(entity);
4415 ActivateEntity(entity);
4416 AcceptEntityInput(entity, "start");
4417 TeleportEntity(entity, vPos, vAng, NULL_VECTOR);
4418
4419 // Refire
4420 if( refire )
4421 {
4422 static char sTemp[64];
4423 Format(sTemp, sizeof sTemp, "OnUser1 !self:Stop::%f:-1", refire - 0.05);
4424 SetVariantString(sTemp);
4425 AcceptEntityInput(entity, "AddOutput");
4426 Format(sTemp, sizeof sTemp, "OnUser1 !self:FireUser2::%f:-1", refire);
4427 SetVariantString(sTemp);
4428 AcceptEntityInput(entity, "AddOutput");
4429 AcceptEntityInput(entity, "FireUser1");
4430
4431 SetVariantString("OnUser2 !self:Start::0:-1");
4432 AcceptEntityInput(entity, "AddOutput");
4433 SetVariantString("OnUser2 !self:FireUser1::0:-1");
4434 AcceptEntityInput(entity, "AddOutput");
4435 }
4436
4437 // Attach
4438 if( target )
4439 {
4440 SetVariantString("!activator");
4441 AcceptEntityInput(entity, "SetParent", target);
4442 }
4443
4444 return entity;
4445}
4446
4447int PrecacheParticle(const char[] sEffectName)
4448{
4449 static int table = INVALID_STRING_TABLE;
4450
4451 if( table == INVALID_STRING_TABLE )
4452 {
4453 table = FindStringTable("ParticleEffectNames");
4454 }
4455
4456 int index = FindStringIndex(table, sEffectName);
4457 if( index == INVALID_STRING_INDEX )
4458 {
4459 bool save = LockStringTables(false);
4460 AddToStringTable(table, sEffectName);
4461 LockStringTables(save);
4462 index = FindStringIndex(table, sEffectName);
4463 }
4464
4465 return index;
4466}
4467
4468
4469
4470// ====================================================================================================
4471// STOCKS - TRACERAY
4472// ====================================================================================================
4473stock bool IsVisibleTo(float position[3], float targetposition[3])
4474{
4475 static float vAngles[3], vLookAt[3];
4476 position[2] += 50.0;
4477
4478 MakeVectorFromPoints(position, targetposition, vLookAt); // compute vector from start to target
4479 GetVectorAngles(vLookAt, vAngles); // get angles from vector for trace
4480
4481 // execute Trace
4482 static Handle trace;
4483 trace = TR_TraceRayFilterEx(position, vAngles, MASK_ALL, RayType_Infinite, _TraceFilter);
4484
4485 static bool isVisible;
4486 isVisible = false;
4487
4488 if( TR_DidHit(trace) )
4489 {
4490 static float vStart[3];
4491 TR_GetEndPosition(vStart, trace); // retrieve our trace endpoint
4492
4493 if( GetVectorDistance(position, vStart) + 25.0 >= GetVectorDistance(position, targetposition) )
4494 isVisible = true; // if trace ray length plus tolerance equal or bigger absolute distance, you hit the target
4495 }
4496 else
4497 isVisible = false;
4498
4499 position[2] -= 50.0;
4500 delete trace;
4501 return isVisible;
4502}
4503
4504public bool _TraceFilter(int entity, int contentsMask)
4505{
4506 if( !entity || !IsValidEntity(entity) ) // dont let WORLD, or invalid ents be hit
4507 return false;
4508
4509 // Don't hit triggers
4510 static char classname[12];
4511 GetEdictClassname(entity, classname, sizeof classname);
4512 if( strncmp(classname, "trigger_", 8) == 0 ) return false;
4513
4514 return true;
4515}
4516
4517bool SetTeleportEndPoint(int client, float vPos[3])
4518{
4519 GetClientEyePosition(client, vPos);
4520 static float vAng[3];
4521 GetClientEyeAngles(client, vAng);
4522
4523 static Handle trace;
4524 trace = TR_TraceRayFilterEx(vPos, vAng, MASK_SHOT, RayType_Infinite, ExcludeSelf_Filter, client);
4525
4526 if(TR_DidHit(trace))
4527 {
4528 TR_GetEndPosition(vPos, trace);
4529
4530 static float vDir[3];
4531 GetAngleVectors(vAng, vDir, NULL_VECTOR, NULL_VECTOR);
4532 vPos[0] -= vDir[0] * 10;
4533 vPos[1] -= vDir[1] * 10;
4534 vPos[2] -= vDir[2] * 10;
4535 }
4536 else
4537 {
4538 delete trace;
4539 return false;
4540 }
4541 delete trace;
4542 return true;
4543}
4544
4545public bool ExcludeSelf_Filter(int entity, int contentsMask, any client)
4546{
4547 if( entity == client )
4548 return false;
4549 return true;
4550}
4551
4552
4553
4554// ====================================================================================================
4555// STOCKS - TEMPENT PARTICLE - By Lux
4556// ====================================================================================================
4557/*
4558* iParticleIndex = "ParticleString" index location in String table "ParticleEffectNames"
4559* iEntIndex = entity index usually used for attachpoints
4560* fDelay = delay for TE_SendToAll
4561* SendToAll = if send to all false call send to clients your self
4562* sParticleName = particle name only used if iParticleIndex -1 it will find the index for you
4563* iAttachmentIndex = attachpoint index there is no way to get this currently with sm, gotta decompile the model :p
4564* ParticleAngles = angles usually effects particles that have no gravity
4565* iFlags = 1 required for attachpoints as well as damage type ^^
4566* iDamageType = saw it being used in impact effect dispatch and attachpoints need to be set to use (maybe)
4567* fMagnitude = no idea saw being used with pipebomb blast (needs testing)
4568* fScale = guess its particle scale but most dont scale (needs testing)
4569*/
4570stock bool L4D_TE_Create_Particle(float fParticleStartPos[3]={0.0, 0.0, 0.0},
4571 float fParticleEndPos[3]={0.0, 0.0, 0.0},
4572 int iParticleIndex=-1,
4573 int iEntIndex=0,
4574 float fDelay=0.0,
4575 bool SendToAll=true,
4576 char sParticleName[64]="",
4577 int iAttachmentIndex=0,
4578 float fParticleAngles[3]={0.0, 0.0, 0.0},
4579 int iFlags=0,
4580 int iDamageType=0,
4581 float fMagnitude=0.0,
4582 float fScale=1.0,
4583 float fRadius=0.0)
4584{
4585 TE_Start("EffectDispatch");
4586 TE_WriteFloat(g_bLeft4Dead2 ? "m_vOrigin.x" : "m_vStart[0]", fParticleStartPos[0]);
4587 TE_WriteFloat(g_bLeft4Dead2 ? "m_vOrigin.y" : "m_vStart[1]", fParticleStartPos[1]);
4588 TE_WriteFloat(g_bLeft4Dead2 ? "m_vOrigin.z" : "m_vStart[2]", fParticleStartPos[2]);
4589 TE_WriteFloat(g_bLeft4Dead2 ? "m_vStart.x" : "m_vOrigin[0]", fParticleEndPos[0]);//end point usually for bulletparticles or ropes
4590 TE_WriteFloat(g_bLeft4Dead2 ? "m_vStart.y" : "m_vOrigin[1]", fParticleEndPos[1]);
4591 TE_WriteFloat(g_bLeft4Dead2 ? "m_vStart.z" : "m_vOrigin[2]", fParticleEndPos[2]);
4592
4593 static int iEffectIndex = INVALID_STRING_INDEX;
4594 if(iEffectIndex < 0)
4595 {
4596 iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffect");
4597 if(iEffectIndex == INVALID_STRING_INDEX)
4598 SetFailState("Unable to find EffectDispatch/ParticleEffect indexes");
4599 }
4600
4601 TE_WriteNum("m_iEffectName", iEffectIndex);
4602
4603 if(iParticleIndex < 0)
4604 {
4605 static int iParticleStringIndex = INVALID_STRING_INDEX;
4606 iParticleStringIndex = __FindStringIndex2(iEffectIndex, sParticleName);
4607 if(iParticleStringIndex == INVALID_STRING_INDEX)
4608 return false;
4609
4610 TE_WriteNum("m_nHitBox", iParticleStringIndex);
4611 }
4612 else
4613 TE_WriteNum("m_nHitBox", iParticleIndex);
4614
4615 TE_WriteNum("entindex", iEntIndex);
4616 TE_WriteNum("m_nAttachmentIndex", iAttachmentIndex);
4617
4618 TE_WriteVector("m_vAngles", fParticleAngles);
4619
4620 TE_WriteNum("m_fFlags", iFlags);
4621 TE_WriteFloat("m_flMagnitude", fMagnitude);// saw this being used in pipebomb needs testing what it does probs shaking screen?
4622 TE_WriteFloat("m_flScale", fScale);
4623 TE_WriteFloat("m_flRadius", fRadius);// saw this being used in pipebomb needs testing what it does probs shaking screen?
4624 TE_WriteNum("m_nDamageType", iDamageType);// this shit is required dunno why for attachpoint emitting valve probs named it wrong
4625
4626 if(SendToAll)
4627 TE_SendToAll(fDelay);
4628
4629 return true;
4630}
4631
4632// Credit smlib https://github.com/bcserv/smlib
4633/*
4634 * Rewrite of FindStringIndex, because in my tests
4635 * FindStringIndex failed to work correctly.
4636 * Searches for the index of a given string in a string table.
4637 *
4638 * @param tableidx A string table index.
4639 * @param str String to find.
4640 * @return String index if found, INVALID_STRING_INDEX otherwise.
4641 */
4642stock int __FindStringIndex2(int tableidx, const char[] str)
4643{
4644 static char buf[1024];
4645
4646 int numStrings = GetStringTableNumStrings(tableidx);
4647 for (int i=0; i < numStrings; i++) {
4648 ReadStringTable(tableidx, i, buf, sizeof(buf));
4649
4650 if (StrEqual(buf, str)) {
4651 return i;
4652 }
4653 }
4654
4655 return INVALID_STRING_INDEX;
4656}