diff --git a/scripting/include/feedthetrolls/misc.inc b/scripting/include/feedthetrolls/misc.inc index 8afc743..16a464c 100644 --- a/scripting/include/feedthetrolls/misc.inc +++ b/scripting/include/feedthetrolls/misc.inc @@ -212,6 +212,15 @@ bool IsAnyPlayerNear(int source, float range) { return false; } +void ThrowItemToPlayer(int victim, int target, int slot) { + int wpn = GetPlayerWeaponSlot(victim, slot); + if(wpn > 0 && (slot != 1 || DoesClientHaveMelee(victim))) { + static float pos[3]; + GetClientAbsOrigin(target, pos); + SDKHooks_DropWeapon(victim, wpn, pos); + } +} + void ThrowItemToClosestPlayer(int victim, int slot) { int wpn = GetPlayerWeaponSlot(victim, slot); if(wpn > 0 && (slot != 1 || DoesClientHaveMelee(victim))) { diff --git a/scripting/include/left4dhooks.inc b/scripting/include/left4dhooks.inc index 0810992..868c95f 100644 --- a/scripting/include/left4dhooks.inc +++ b/scripting/include/left4dhooks.inc @@ -17,8 +17,8 @@ * Left 4 Downtown 2 Extension updates * Copyright (C) 2017 "Accelerator74" * - * Left 4 DHooks SourceMod plugin - * Copyright (C) 2020 "SilverShot" / "Silvers" + * Left 4 DHooks Direct SourceMod plugin + * Copyright (C) 2021 "SilverShot" / "Silvers" * * ============================================================================= * @@ -52,6 +52,26 @@ #define _l4dh_included #include +#include +#include +#include + + + +// Natives: +// L4D1 = 24 [left4downtown] + 47 [l4d_direct] + 15 [l4d2addresses] + 44 [silvers - mine!] + 4 [anim] = 126 +// L4D2 = 53 [left4downtown] + 61 [l4d_direct] + 26 [l4d2addresses] + 79 [silvers - mine!] + 4 [anim] = 212 + +// Forwards: +// L4D1 = 61; +// L4D2 = 78; + +// Stocks: (L4D1 = 106. L4D2 = 149) +// left4dhooks_silver 37 stocks (L4D2 = 4, L4D1 = 0) +// left4dhooks_stocks 82 stocks (L4D2 = 39, L4D1 = 4) +// left4dhooks_lux_library 34 stocks (L4D2 = 4, L4D1 = 0) + + @@ -105,6 +125,11 @@ public void __pl_l4dh_SetNTVOptional() MarkNativeAsOptional("L4D2_VomitJarPrj"); MarkNativeAsOptional("L4D2_GrenadeLauncherPrj"); + MarkNativeAsOptional("L4D_GetPointer"); + MarkNativeAsOptional("L4D_GetClientFromAddress"); + MarkNativeAsOptional("L4D_GetEntityFromAddress"); + MarkNativeAsOptional("L4D_ReadMemoryString"); + MarkNativeAsOptional("L4D_GetServerOS"); MarkNativeAsOptional("L4D2_ExecVScriptCode"); MarkNativeAsOptional("L4D2_GetVScriptOutput"); MarkNativeAsOptional("L4D2_SpitterPrj"); @@ -335,6 +360,21 @@ enum GAMEMODE_SCAVENGE = 8 } +// For the "L4D_GetPointer" native +enum PointerType +{ + POINTER_DIRECTOR = 1, // @TheDirector + POINTER_SERVER = 2, // @sv + POINTER_GAMERULES = 3, // @g_pGameRules + POINTER_NAVMESH = 4, // @TheNavMesh + POINTER_ZOMBIEMANAGER = 5, // @TheZombieManager + POINTER_WEAPONINFO = 6, // @_ZL20m_WeaponInfoDatabase + POINTER_MELEEINFO = 7, // @g_MeleeWeaponInfoStore (L4D2 Only) + POINTER_EVENTMANAGER = 8, // pScriptedEventManager (L4D2 Only) + POINTER_SCAVENGEMODE = 9, // pScavengeMode (L4D2 Only) + POINTER_VERSUSMODE = 10 // pVersusMode +} + // Provided by "BHaType": // For the "L4D_State_Transition" native. // X -> Y (means X state will become Y state on next frame or some seconds later) @@ -466,6 +506,20 @@ native int AnimGetFromActivity(char[] activity); */ forward Action L4D_OnSpawnSpecial(int &zombieClass, const float vecPos[3], const float vecAng[3]); +/** + * @brief Called whenever ZombieManager::SpawnSpecial(ZombieClassType,Vector&,QAngle&) is invoked + * @remarks Only used for bot special spawns (not players) + * @remarks zombieClass: 1=Smoker, 2=Boomer, 3=Hunter, 4=Spitter, 5=Jockey, 6=Charger + * + * @param client The client index who spawned + * @param zombieClass Zombie class that will be spawned. + * @param vecPos Vector coordinate where special will be spawned + * @param vecAng QAngle where spcial will be facing + * + * @noreturn + */ +forward void L4D_OnSpawnSpecial_Post(int client, int zombieClass, const float vecPos[3], const float vecAng[3]); + /** * @brief Called whenever ZombieManager::SpawnTank(Vector&,QAngle&) is invoked * @remarks Not invoked if z_spawn tank is used and it gives a ghosted/dead player tank @@ -477,6 +531,18 @@ forward Action L4D_OnSpawnSpecial(int &zombieClass, const float vecPos[3], const */ forward Action L4D_OnSpawnTank(const float vecPos[3], const float vecAng[3]); +/** + * @brief Called whenever ZombieManager::SpawnTank(Vector&,QAngle&) is invoked + * @remarks Not invoked if z_spawn tank is used and it gives a ghosted/dead player tank + * + * @param client The client index who spawned + * @param vecPos Vector coordinate where tank is spawned + * @param vecAng QAngle where tank will be facing + * + * @noreturn + */ +forward void L4D_OnSpawnTank_Post(int client, const float vecPos[3], const float vecAng[3]); + /** * @brief Called whenever ZombieManager::SpawnWitch(Vector&,QAngle&) is invoked * @brief Called when a Witch spawns @@ -488,6 +554,18 @@ forward Action L4D_OnSpawnTank(const float vecPos[3], const float vecAng[3]); */ forward Action L4D_OnSpawnWitch(const float vecPos[3], const float vecAng[3]); +/** + * @brief Called whenever ZombieManager::SpawnWitch(Vector&,QAngle&) is invoked + * @brief Called when a Witch spawns + * + * @param entity Entity index that spawned + * @param vecPos Vector coordinate where witch is spawned + * @param vecAng QAngle where witch will be facing + * + * @noreturn + */ +forward void L4D_OnSpawnWitch_Post(int entity, const float vecPos[3], const float vecAng[3]); + /** * @brief Called whenever ZombieManager::SpawnWitchBride(Vector&,QAngle&) is invoked * @brief Called when a Witch Bride spawns @@ -500,6 +578,19 @@ forward Action L4D_OnSpawnWitch(const float vecPos[3], const float vecAng[3]); // L4D2 only. forward Action L4D2_OnSpawnWitchBride(const float vecPos[3], const float vecAng[3]); +/** + * @brief Called whenever ZombieManager::SpawnWitchBride(Vector&,QAngle&) is invoked + * @brief Called when a Witch Bride spawns + * + * @param entity Entity index that spawned + * @param vecPos Vector coordinate where witch is spawned + * @param vecAng QAngle where witch will be facing + * + * @noreturn + */ +// L4D2 only. +forward void L4D2_OnSpawnWitchBride_Post(int entity, const float vecPos[3], const float vecAng[3]); + /** * @brief Called whenever CDirector::OnMobRushStart(void) is invoked * @remarks called on random hordes, mini and finale hordes, and boomer hordes, causes Zombies to attack @@ -1242,7 +1333,7 @@ forward Action L4D2_OnStartCarryingVictim(int victim, int attacker); * @remarks Called when a Survivor player is covered in Boomer bile, or when using "Bile the World" plugin by "AtomicStryker" * * @param victim the client who's now it - * @param attacker the attacker who caused the vomit + * @param attacker the attacker who caused the vomit (can be 0) * * @return Plugin_Handled to block, Plugin_Changed to use overwritten values from plugin, Plugin_Continue otherwise */ @@ -1253,7 +1344,7 @@ forward Action L4D_OnVomitedUpon(int victim, int &attacker, bool &boomerExplosio * @remarks Called when a Special Infected is hit from a Bilejar explosion * * @param victim the client who's now it - * @param attacker the attacker who caused the vomit + * @param attacker the attacker who caused the vomit (can be 0) * @param boomerExplosion true if triggered by a boommer explosion * * @return Plugin_Handled to block, Plugin_Changed to use overwritten values from plugin, Plugin_Continue otherwise @@ -1377,6 +1468,49 @@ forward Action L4D_OnGetRandomPZSpawnPosition(int &client, int &zombieClass, int // ==================================================================================================== // NATIVES - Silvers // ==================================================================================================== +/** + * @brief Returns the address pointer to various internal game objects + * + * @param ptr_type Using the PointerType enum to select which pointer to return + * + * @return Address pointer or 0 on failure. + */ +native Address L4D_GetPointer(PointerType ptr_type); + +/** + * @brief Returns a client index from a memory address + * + * @param addy Address to check + * + * @return Client index or 0 on failure. + */ +native int L4D_GetClientFromAddress(Address addy); + +/** + * @brief Returns an entity index from a memory address + * + * @param addy Address to check + * + * @return Entity index or -1 on failure. + */ +native int L4D_GetEntityFromAddress(Address addy); + +/** + * @brief Returns an entity index from a memory address + * + * @param addy Address to check + * + * @return Entity index or -1 on failure. + */ +native int L4D_ReadMemoryString(Address addy, char[] buffer, int maxlength); + +/** + * @brief Returns the servers operating system. + * + * @return 0=Windows. 1=Linux. + */ +native int L4D_GetServerOS(); + /** * @brief Runs a specified VScript code. * @remarks Saves having to create an entity. The entity can remain alive and used again... @@ -2529,9 +2663,9 @@ native float L4D2_ITimerGetElapsedTime(L4D2IntervalTimer timer); * 2020 Update2: Now works in Left4DHooks. Glitchy animation bug when reloading an already full weapon. * 2021 Update3: Fix plugin for modified ammo clips found here: https://forums.alliedmods.net/showthread.php?t=327105 -A note regarding Clipsize: Any non-standard value will NOT be in effect at weapon pickup, which means the client -has to reload once to achieve the modified value. To fix this, add a weapon pickup hook in your plugin (eg "player_use") -and use something like this with a small timer delay of 0.1 seconds or more (dont you love this engine). + A note regarding Clipsize: Any non-standard value will NOT be in effect at weapon pickup, which means the client + has to reload once to achieve the modified value. To fix this, add a weapon pickup hook in your plugin (eg "player_use") + and use something like this with a small timer delay of 0.1 seconds or more (dont you love this engine). int weapon = GetPlayerWeaponSlot(client, 0); if( weapon == INVALID_ENT_REFERENCE ) return; @@ -2544,6 +2678,8 @@ enum L4D2IntWeaponAttributes L4D2IWA_Damage, L4D2IWA_Bullets, L4D2IWA_ClipSize, + L4D2IWA_Bucket, + L4D2IWA_Tier, // L4D2 only MAX_SIZE_L4D2IntWeaponAttributes }; @@ -2566,15 +2702,19 @@ enum L4D2FloatWeaponAttributes L4D2FWA_CycleTime, L4D2FWA_PelletScatterPitch, L4D2FWA_PelletScatterYaw, + L4D2FWA_VerticalPunch, + L4D2FWA_HorizontalPunch, // Requires "z_gun_horiz_punch" cvar changed to "1". MAX_SIZE_L4D2FloatWeaponAttributes }; +// L4D2 only enum L4D2BoolMeleeWeaponAttributes { L4D2BMWA_Decapitates, MAX_SIZE_L4D2BoolMeleeWeaponAttributes }; +// L4D2 only enum L4D2IntMeleeWeaponAttributes { L4D2IMWA_DamageFlags, @@ -2582,6 +2722,7 @@ enum L4D2IntMeleeWeaponAttributes MAX_SIZE_L4D2IntMeleeWeaponAttributes }; +// L4D2 only enum L4D2FloatMeleeWeaponAttributes { L4D2FMWA_Damage, diff --git a/scripting/include/left4dhooks_anim.inc b/scripting/include/left4dhooks_anim.inc index 6a05722..feea0c4 100644 --- a/scripting/include/left4dhooks_anim.inc +++ b/scripting/include/left4dhooks_anim.inc @@ -1,3 +1,37 @@ +/* +* Left 4 DHooks Direct +* Copyright (C) 2021 Silvers +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#if defined _l4d_anim_included + #endinput +#endif +#define _l4d_anim_included + +#include +#include + +#tryinclude +#tryinclude +#tryinclude + + + + + // ==================================================================================================== // ACT_* ANIMATION VALUES // ==================================================================================================== diff --git a/scripting/include/left4dhooks_lux_library.inc b/scripting/include/left4dhooks_lux_library.inc new file mode 100644 index 0000000..f6f8d91 --- /dev/null +++ b/scripting/include/left4dhooks_lux_library.inc @@ -0,0 +1,1252 @@ +/** +* Copyright (C) 2021 LuxLuma +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +**/ + +#if defined _lux_library_included +#endinput +#endif +#define _lux_library_included + +#include +#include + +#tryinclude +#tryinclude +#tryinclude + +#define LUX_LIBRARY_VERSION "0.5.5" + +#define GIMMEDATA "lux_library" + +//l4d2 +#define DMG_FULLGIB (1 << 24) + +enum OS_Type +{ + OS_Unknown = -2, + OS_invalid = -1, + OS_windows = 0, + OS_linux, + OS_mac +} + +static stock void GetGameData(Handle &hGamedata) +{ + hGamedata = LoadGameConfigFile(GIMMEDATA); + if(hGamedata == null) + LogError("Failed to load \"%s.txt\" gamedata.", GIMMEDATA); +} + +/** + * Returns the OS of the server. + * Note: Pass null to use gamedata from lux_library + * + * @param hGamedata Handle to the gamedata file to get the OS type from. + * + * @return Windows, Linux, or Mac + * @error Invalid or unknown OS type. + **/ +stock OS_Type GetOSType(Handle &hGamedata=null) +{ + static OS_Type os = OS_Unknown; + if(os == OS_Unknown) + { + if(hGamedata == null) + { + GetGameData(hGamedata); + } + + os = view_as(GameConfGetOffset(hGamedata, "OS")); + delete hGamedata; + } + return os; +} + +/** + * An alternative to TeleportEntity that maintains lerped movement client-side when setting an entity's new origin. + * Note: Does not account for parented entities, uses world space. + * + * @param iEntity Entity index to set new origin of. + * @param vec New origin of the entity. + * + * @return True on success, false on failure. + * @error Invalid entity index, signature for function not found, or SDKCall failed. + **/ +stock bool SetAbsOrigin(int iEntity, const float vec[3]) +{ + static Handle hSDKCall; + if(hSDKCall == null) + { + Handle hGamedata; + GetGameData(hGamedata); + + StartPrepSDKCall(SDKCall_Entity); + if(PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CBaseEntity::SetAbsOrigin")) + { + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_Pointer); + hSDKCall = EndPrepSDKCall(); + if(hSDKCall == null) + LogError("Unable to prep SDKCall 'CBaseEntity::SetAbsOrigin'"); + } + else + { + LogError("Error finding the 'CBaseEntity::SetAbsOrigin' signature."); + } + delete hGamedata; + if(hSDKCall == null) + return false; + } + SDKCall(hSDKCall, iEntity, vec); + return true; +} + +/** + * An alternative to TeleportEntity that maintains lerped movement client-side when setting an entity's new velocity. + * Note: Does not account for parented entities, uses world space. + * Note: Does not overwrite "m_vecBaseVelocity". + * + * @param iEntity Entity index to set new velocity of. + * @param vec Velocity to apply to entity. + * + * @return True on success, false on failure. + * @error Invalid entity index, signature for function not found, or SDKCall failed. + **/ +stock bool SetAbsVelocity(int iEntity, const float vec[3]) +{ + static Handle hSDKCall; + if(hSDKCall == null) + { + Handle hGamedata; + GetGameData(hGamedata); + + StartPrepSDKCall(SDKCall_Entity); + if(PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CBaseEntity::SetAbsVelocity")) + { + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_Pointer); + hSDKCall = EndPrepSDKCall(); + if(hSDKCall == null) + LogError("Unable to prep SDKCall 'CBaseEntity::SetAbsVelocity'"); + } + else + { + LogError("Error finding the 'CBaseEntity::SetAbsVelocity' signature."); + } + delete hGamedata; + if(hSDKCall == null) + return false; + } + SDKCall(hSDKCall, iEntity, vec); + return true; +} + +/** + * An alternative to TeleportEntity that maintains lerped movement client-side when setting an entity's new angles. + * Note: Does not account for parented entities, uses world space. + * + * @param iEntity Entity index to set new angles of. + * @param vec New angles of the entity. + * + * @return True on success, false on failure. + * @error Invalid entity index, signature for function not found, or SDKCall failed. + **/ +stock bool SetAbsAngles(int iEntity, const float vec[3]) +{ + static Handle hSDKCall; + if(hSDKCall == null) + { + Handle hGamedata; + GetGameData(hGamedata); + + StartPrepSDKCall(SDKCall_Entity); + if(PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CBaseEntity::SetAbsAngles")) + { + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_Pointer); + hSDKCall = EndPrepSDKCall(); + if(hSDKCall == null) + LogError("Unable to prep SDKCall 'CBaseEntity::SetAbsAngles'"); + } + else + { + LogError("Error finding the 'CBaseEntity::SetAbsAngles' signature."); + } + delete hGamedata; + if(hSDKCall == null) + return false; + } + SDKCall(hSDKCall, iEntity, vec); + return true; +} + +/** + * Copies the origin and angles vectors for world space attachment index location. + * + * @param iEntity Entity index to get origin and angles vectors of. + * @param attachmentName Name of the attachment. + * @param vecOrigin Vector to store origin in. + * @param vecAngles Vector to store angles in. + * + * @return True if attachment vectors were copied, false otherwise. + * @error Invalid entity index or invalid attachment name. + **/ +stock bool GetAttachmentVectors(int iEntity, char[] attachmentName, float vecOrigin[3], float vecAngles[3]) +{ + int iAttachmentName = LookupAttachment(iEntity, attachmentName); + if(iAttachmentName == 0) + return false; + GetAttachment(iEntity, iAttachmentName, vecOrigin, vecAngles); + return true; +} + +/** + * Looks up an entity's attachment index via its name. + * + * @param iEntity Entity index of the attachment. + * @param attachmentName Name of the attachment. + * + * @return Attachment index, 0 for not found. + * @error Invalid entity index, invalid attachment name, + * signature for function not found, or SDKCall failed. + **/ +stock int LookupAttachment(int iEntity, char[] attachmentName) +{ + static Handle hSDKCall; + if(hSDKCall == null) + { + Handle hGamedata; + GetGameData(hGamedata); + + StartPrepSDKCall(SDKCall_Entity); + if(PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CBaseAnimating::LookupAttachment")) + { + PrepSDKCall_AddParameter(SDKType_String, SDKPass_Pointer); + PrepSDKCall_SetReturnInfo(SDKType_PlainOldData, SDKPass_Plain); + hSDKCall = EndPrepSDKCall(); + if(hSDKCall == null) + LogError("Unable to prep 'CBaseAnimating::LookupAttachment'"); + } + else + { + LogError("Error finding the 'CBaseAnimating::LookupAttachment' signature."); + } + delete hGamedata; + if(hSDKCall == null) + return 0; + } + return SDKCall(hSDKCall, iEntity, attachmentName); +} + + +/** + * Overloaded SDKCall->CBaseAnimating::GetAttachment(int a1, int a2, float *a3, float *a4) + * Copies the origin and angles vectors for world space attachment index location. + * Note: Invalid attachments point to entitiy origin. + * + * @param iEntity Entity index to get attachment of. + * @param iAttachment Attachment index on model. + * @param vecOrigin Vector to store origin in. + * @param vecAngles Vector to store angles in. + * + * @return True if attachment vectors were copied, false otherwise. + * @error Invalid entity index or invalid attachment name, + * signature for function not found, or SDKCall failed. + **/ +stock bool GetAttachment(int iEntity, int iAttachment, float vecOrigin[3], float vecAngles[3]) +{ + static Handle hSDKCall; + if(hSDKCall == null) + { + Handle hGamedata; + GetGameData(hGamedata); + + StartPrepSDKCall(SDKCall_Entity); + if(PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CBaseAnimating::GetAttachment")) + { + PrepSDKCall_AddParameter(SDKType_PlainOldData, SDKPass_Plain); + PrepSDKCall_AddParameter(SDKType_Vector, SDKPass_ByRef, _, VENCODE_FLAG_COPYBACK); + PrepSDKCall_AddParameter(SDKType_QAngle, SDKPass_ByRef, _, VENCODE_FLAG_COPYBACK); + hSDKCall = EndPrepSDKCall(); + if(hSDKCall == null) + LogError("Unable to prep 'CBaseAnimating::GetAttachment'"); + } + else + { + LogError("Error finding the 'CBaseAnimating::GetAttachment' signature."); + } + delete hGamedata; + if(hSDKCall == null) + return false; + } + SDKCall(hSDKCall, iEntity, iAttachment, vecOrigin, vecAngles); + return true; +} + +/** + * Checks whether a given position is in water. + * Note: Mostly works, but some brushes seem to have water in their contents. + * Warning: Currently broken and unreliable. + * + * @param vecOrigin Position to check for. + * + * @return True if position is located in the water, false otherwise. + **/ +stock bool IsPositionInWater(float vecOrigin[3]) +{ + Handle hTrace = TR_TraceRayEx(vecOrigin, vecOrigin, CONTENTS_WATER, RayType_EndPoint); + bool bSolid = TR_StartSolid(hTrace); + delete hTrace; + return bSolid; +} + +/** + * Sets DSP effect post audio FX. + * + * @param iClient Client index of the player. + * @param flDelay Delay before sending effect. + * @param dspEffectType DSP effect type see "scripts/dsp_presets.txt" + * + * @error Invalid client index. + **/ +stock void Terror_SetPendingDspEffect(int iClient, float flDelay, int dspEffectType) +{ + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + // Get CountdownTimer address + static int timerAddress = -1; + if(timerAddress == -1) + { + if(IsEngine == Engine_Left4Dead2) + timerAddress = FindSendPropInfo("CTerrorPlayer", "m_iBloodyHandsLevel") + 8; + else + timerAddress = FindSendPropInfo("CTerrorPlayer", "m_bloodyHandsPercent") + 4; + } + + //timerAddress + 4 = Duration + //timerAddress + 8 = TimeStamp + SetEntDataFloat(iClient, timerAddress + 4, flDelay); + SetEntDataFloat(iClient, timerAddress + 8, GetGameTime() + flDelay); + SetEntData(iClient, timerAddress + 12, dspEffectType); +} + +/** + * Sets the adrenaline effect duration of a survivor. + * + * @param iClient Client index of the survivor. + * @param flDuration Duration of the adrenaline effect. + * + * @error Invalid client index. + **/ +// L4D2 only. +stock void Terror_SetAdrenalineTime(int iClient, float flDuration) +{ + // Get CountdownTimer address + static int timerAddress = -1; + if(timerAddress == -1) + { + timerAddress = FindSendPropInfo("CTerrorPlayer", "m_bAdrenalineActive") - 12; + } + + //timerAddress + 4 = Duration + //timerAddress + 8 = TimeStamp + SetEntDataFloat(iClient, timerAddress + 4, flDuration); + SetEntDataFloat(iClient, timerAddress + 8, GetGameTime() + flDuration); + SetEntProp(iClient, Prop_Send, "m_bAdrenalineActive", (flDuration <= 0.0 ? 0 : 1), 1); +} + +/** + * Returns the remaining duration of a survivor's adrenaline effect. + * + * @param iClient Client index of the survivor. + * + * @return Remaining duration or -1.0 if there's no effect. + * @error Invalid client index. + **/ +// L4D2 only. +stock float Terror_GetAdrenalineTime(int iClient) +{ + // Get CountdownTimer address + static int timerAddress = -1; + if(timerAddress == -1) + { + timerAddress = FindSendPropInfo("CTerrorPlayer", "m_bAdrenalineActive") - 12; + } + + //timerAddress + 8 = TimeStamp + float flGameTime = GetGameTime(); + float flTime = GetEntDataFloat(iClient, timerAddress + 8); + if(flTime <= flGameTime) + return -1.0; + + return flTime - flGameTime; +} + +/** + * Create a physics explosion that does not affect players and don't check for line of sight and no force fall-off. + * + * @param vecOrigin Origin of the explosion. + * @param iMagnitude Magnitude of the explosion. + * @param flRadius Radius of the explosion. + * @param bDamage True to damage props, false otherwise. + * + * @error Failed to create explosion. + **/ +stock void PhysicsExplode(float vecOrigin[3], int iMagnitude, float flRadius, bool bDamage=false) +{ + static int iBoom = INVALID_ENT_REFERENCE; + + if(iBoom == INVALID_ENT_REFERENCE || !IsValidEntity(iBoom)) + { + iBoom = EntIndexToEntRef(CreateEntityByName("env_physexplosion")); + if(iBoom == INVALID_ENT_REFERENCE) + return; + + DispatchKeyValue(iBoom, "spawnflags", "0"); + DispatchSpawn(iBoom); + } + + if(bDamage) + { + DispatchKeyValue(iBoom, "spawnflags", "0"); + } + else + { + DispatchKeyValue(iBoom, "spawnflags", "1"); + } + + char sBuf[32]; + IntToString(iMagnitude, sBuf, sizeof(sBuf)); + DispatchKeyValue(iBoom, "magnitude", sBuf); + + IntToString(RoundFloat(flRadius), sBuf, sizeof(sBuf)); + DispatchKeyValue(iBoom, "radius", sBuf); + DispatchKeyValueFloat(iBoom, "inner_radius", flRadius); + + TeleportEntity(iBoom, vecOrigin, NULL_VECTOR, NULL_VECTOR); + + AcceptEntityInput(iBoom, "Explode"); + RemoveEntity(iBoom); +} + +///////////////////////////////////////Temp ents + +/** + * Sets up an invisible explosion to push client-side physics. + * Note: The grenade launcher uses this to push bodies and other objects. + * Note: Left 4 Dead 2 only. + * + * @param vecOrigin Origin of the explosion in world space. + * @param flRadius Radius of the explosion. + * @param flMagnitude Magnitude of the explosion. + * + * @error Invalid effect index. + **/ +// L4D2 only. +stock void TE_SetupExplodeForce(float vecOrigin[3], float flRadius, float flMagnitude) +{ + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ExplosionForce"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ExplosionForce index"); + + } + + TE_Start("EffectDispatch"); + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteFloat("m_vOrigin.x", vecOrigin[0]); + TE_WriteFloat("m_vOrigin.y", vecOrigin[1]); + TE_WriteFloat("m_vOrigin.z", vecOrigin[2]); + TE_WriteFloat("m_flRadius", flRadius); + TE_WriteFloat("m_flMagnitude", flMagnitude); +} + +/** + * Creates a clientside physics prop. + * + * @param vecOrigin Origin of the prop in world space. + * @param iPrecacheModel Stringtable index of the prop. + * @param vecModelAngles Angles of the prop. + * @param vecVelocity Velocity used by explosion for pushing the prop. + * @param iFlags Flags to apply to the prop. Note: A value of "1" applies auto-despawn, unsupported models will despawn next client tick. + * @param iEffects Effects to apply to the prop. + * @param iSkin Skin of the prop's model. + * @param RGB Color of the prop's model. + **/ +stock void TE_SetupPhysicsProp(float vecModelOrigin[3], + int iPrecacheModel, + float vecModelAngles[3]={0.0, ...}, + float vecVelocity[3]={0.0, ...}, + int iFlags=0, + int iEffects=0, + int iSkin=0, + int RGB[3]={255, ...}) +{ + TE_Start("physicsprop"); + TE_WriteVector("m_vecOrigin", vecModelOrigin); + TE_WriteFloat("m_angRotation[0]", vecModelAngles[0]); + TE_WriteFloat("m_angRotation[1]", vecModelAngles[1]); + TE_WriteFloat("m_angRotation[2]", vecModelAngles[2]); + TE_WriteVector("m_vecVelocity", vecVelocity); + TE_WriteNum("m_nModelIndex", iPrecacheModel); + TE_WriteNum("m_nFlags", iFlags); + TE_WriteNum("m_nEffects", iEffects); + TE_WriteNum("r", RGB[0]); + TE_WriteNum("g", RGB[1]); + TE_WriteNum("b", RGB[2]); +} + +/** + * Sets up a dynamic light. + * Note: Only one can exist client-side per tick. New lights will replace old ones. + * + * @param vecOrigin Origin of the light in world space. + * @param RGB Color of the light. + * @param flRadius Radius of the light. + * @param flTime Time until the light despawns. + * @param flDecay Radius decay speed of the light. + * @param exponent Brightness of the light. + * + * @return True on success, false on failure. + **/ +stock bool TE_SetupDynamicLight(float vecOrigin[3], int RGB[3], float flRadius, float flTime, float flDecay=0.0, int exponent=0) +{ + static int iLastTick; + int iCurrentTick = GetGameTickCount(); + + if(iLastTick == iCurrentTick) + return false; + iLastTick = iCurrentTick; + + TE_Start("Dynamic Light"); + + TE_WriteVector("m_vecOrigin", vecOrigin); + TE_WriteNum("r", RGB[0]); + TE_WriteNum("g", RGB[1]); + TE_WriteNum("b", RGB[2]); + TE_WriteNum("exponent", exponent); + TE_WriteFloat("m_fRadius", flRadius); + TE_WriteFloat("m_fTime", flTime); + TE_WriteFloat("m_fDecay", flDecay); + + return true; +} + +/** + * Sets up a particle effect's attachment. + * + * @param iParticleIndex Particle index. + * @param sAttachmentName Name of attachment. + * @param iEntIndex Entity index of the particle. + * @param bFollow True to make the particle follow attachment points, false otherwise. + * + * @error Invalid effect index. + **/ +stock void TE_SetupParticleAttachment(int iParticleIndex, int iAttachmentIndex, int iEntIndex, bool bFollow=false) +{ + static float vecDummy[3]={0.0, 0.0, 0.0}; + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecDummy[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecDummy[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffect"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ParticleEffect indexes"); + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", iParticleIndex); + TE_WriteNum("entindex", iEntIndex); + TE_WriteNum("m_nAttachmentIndex", iAttachmentIndex); + TE_WriteNum("m_fFlags", 1); //needed for attachments to work + + + TE_WriteVector("m_vAngles", vecDummy); + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 1.0); + TE_WriteFloat("m_flRadius", 0.0); + + if(IsEngine == Engine_Left4Dead2) + { + TE_WriteNum("m_nDamageType", bFollow ? 5 : 4); + } + else + { + TE_WriteNum("m_nDamageType", bFollow ? 4 : 3); + } +} + +/** + * Sets up a particle effect. + * Note: Particles that need an ending point to show do not use particle angles usually. + * Warning: Looping particles will last forever with no known way of removing them. + * + * @param iParticleIndex Particle index. + * @param vecParticleStartPos Starting point of the particle in world space. + * @param vecParticleEndPos Ending point of the particle in world space. + * @param vecParticleAngles Angles of the particle. + * @param iEntity Entity owner if entity dies so does particle. + * + * @error Invalid effect index. + **/ +stock void TE_SetupParticle(int iParticleIndex, float vecParticleStartPos[3], float vecParticleEndPos[3]={0.0, 0.0, 0.0}, float vecParticleAngles[3]={0.0, 0.0, 0.0}, int iEntity=0) +{ + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecParticleStartPos[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecParticleStartPos[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecParticleStartPos[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecParticleEndPos[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecParticleEndPos[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecParticleEndPos[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffect"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ParticleEffect indexes"); + + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", iParticleIndex); + + TE_WriteNum("entindex", iEntity); + TE_WriteNum("m_nAttachmentIndex", 0); + TE_WriteNum("m_fFlags", 0); + + TE_WriteVector("m_vAngles", vecParticleAngles); + + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 1.0); + TE_WriteFloat("m_flRadius", 0.0); + TE_WriteNum("m_nDamageType", 0); +} + +/** + * Sets up a particle effect to follow an entity's origin (0, 0, 0). + * + * @param iParticleIndex Particle index. + * @param iEntIndex Entity index to follow. + * @param vecParticleAngles Angles of the particle. + * + * @error Invalid effect index. + **/ +stock void TE_SetupParticleFollowEntity(int iParticleIndex, int iEntIndex, float vecParticleAngles[3]={0.0, 0.0, 0.0}) +{ + static float vecDummy[3]={0.0, 0.0, 0.0}; + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecDummy[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecDummy[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffect"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ParticleEffect indexes"); + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", iParticleIndex); + TE_WriteNum("entindex", iEntIndex); + TE_WriteNum("m_nAttachmentIndex", 0); + TE_WriteNum("m_fFlags", 1); + + + TE_WriteVector("m_vAngles", vecParticleAngles); + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 1.0); + TE_WriteFloat("m_flRadius", 0.0); + + TE_WriteNum("m_nDamageType", 1); +} + +/** + * Sets up a particle effect to follow an entity's origin (0, 0, 0) while maintaining offset. + * Note: Left 4 Dead 2 only maintains offset from the world origin of the parent, which is + * not reliable since this is not a server side entity, so lag can miscalculate the offset. + * + * @param iParticleIndex Particle index. + * @param iEntIndex Entity index to follow. + * @param vecParticleStartPos Starting point to maintain offset from. + * @param vecParticleAngles Angles of the particle. + * + * @error Invalid effect index. + **/ +// L4D2 only. +stock void TE_SetupParticleFollowEntity_MaintainOffset(int iParticleIndex, int iEntIndex, float vecParticleStartPos[3], float vecParticleAngles[3]={0.0, 0.0, 0.0}) +{ + static float vecDummy[3]={0.0, 0.0, 0.0}; + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecParticleStartPos[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecParticleStartPos[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecParticleStartPos[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecDummy[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffect"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ParticleEffect indexes"); + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", iParticleIndex); + TE_WriteNum("entindex", iEntIndex); + TE_WriteNum("m_nAttachmentIndex", 0); + TE_WriteNum("m_fFlags", 1); + + + TE_WriteVector("m_vAngles", vecParticleAngles); + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 1.0); + TE_WriteFloat("m_flRadius", 0.0); + + TE_WriteNum("m_nDamageType", 3); +} + +/** + * Set up particle that attaches to points on a model that is predefined in particle i think. + * + * @param iParticleIndex Particle index. + * @param iEntIndex Entity index to attach. + * @param vecParticleStartPos Starting point valve uses it + * + * @error Invalid effect index. + **/ +stock void TE_SetupParticle_ControlPoints(int iParticleIndex, int iEntIndex, float vecParticleStartPos[3]) +{ + static float vecDummy[3]={0.0, 0.0, 0.0}; + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecParticleStartPos[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecParticleStartPos[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecParticleStartPos[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecDummy[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffect"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ParticleEffect indexes"); + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", iParticleIndex); + TE_WriteNum("entindex", iEntIndex); + TE_WriteNum("m_nAttachmentIndex", 255); + TE_WriteNum("m_fFlags", 1); + + + TE_WriteVector("m_vAngles", vecDummy); + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 1.0); + TE_WriteFloat("m_flRadius", 0.0); + + TE_WriteNum("m_nDamageType", 0); +} + +/** + * Sets up a particle effect via its name to follow an entity's origin (0, 0, 0). + * + * @param sParticleName Name of the particle. + * @param iEntIndex Entity index to follow. + * @param vecParticleAngles Angles of the particle. + * + * @return True on success, false on failure. + * @error Invalid particle stringtable index. + **/ +stock bool TE_SetupParticleFollowEntity_Name(char[] sParticleName, int iEntIndex, float vecParticleAngles[3]={0.0, 0.0, 0.0}) +{ + int iParticleStringIndex = GetParticleIndex(sParticleName); + if(iParticleStringIndex == INVALID_STRING_INDEX) + { + return false; + } + TE_SetupParticleFollowEntity(iParticleStringIndex, iEntIndex, vecParticleAngles); + return true; +} + +/** + * Sets up a particle effect via its name to follow an entity's origin (0, 0, 0) while maintaining offset. + * Note: Left 4 Dead 2 only maintains offset from the world origin of the parent, which is + * not reliable since this is not a server side entity, so lag can miscalculate the offset. + * + * @param sParticleName Name of the particle. + * @param iEntIndex Entity index to follow. + * @param vecParticleStartPos Starting point to maintain offset from. + * @param vecParticleAngles Angles of the particle. + * + * @return True on success, false on failure. + * @error Invalid particle stringtable index. + **/ +stock bool TE_SetupParticleFollowEntity_MaintainOffset_Name(char[] sParticleName, int iEntIndex, float vecParticleStartPos[3], float vecParticleAngles[3]={0.0, 0.0, 0.0}) +{ + int iParticleStringIndex = GetParticleIndex(sParticleName); + if(iParticleStringIndex == INVALID_STRING_INDEX) + { + return false; + } + TE_SetupParticleFollowEntity_MaintainOffset(iParticleStringIndex, iEntIndex, vecParticleStartPos, vecParticleAngles); + return true; +} + +/** + * Sets up a particle effect via a name. + * Note: Particles that need an ending point to show do not use particle angles usually. + * Warning: Looping particles will last forever with no known way of removing them. + * + * @param sParticleName Name of the particle. + * @param vecParticleStartPos Starting point of the particle in world space. + * @param vecParticleEndPos Ending point of the particle in world space. + * @param vecParticleAngles Angles of the particle. + * @param iEntity Entity owner if entity dies so does particle. + * + * @return True on success, false on failure. + * @error Invalid effect index. + **/ +stock bool TE_SetupParticle_Name(char[] sParticleName, float vecParticleStartPos[3], float vecParticleEndPos[3]={0.0, 0.0, 0.0}, float vecParticleAngles[3]={0.0, 0.0, 0.0}, int iEntity=0) +{ + int iParticleStringIndex = GetParticleIndex(sParticleName); + if(iParticleStringIndex == INVALID_STRING_INDEX) + { + return false; + } + TE_SetupParticle(iParticleStringIndex, vecParticleStartPos, vecParticleEndPos, vecParticleAngles, iEntity); + return true; +} + +/** + * Sets up a particle effect's attachment via a name. + * Note: Only follows entities that have attachment points. + * Warning: This function does not validate if the attachment is valid. + * + * @param sParticleName Name of the particle. + * @param sAttachmentName Name of the attachment. + * @param iEntIndex Entity index of the particle. + * @param bFollow True to make the particle follow attachment points, false otherwise. + * + * @return True on success, false on failure. + * @error Invalid particle stringtable index. + **/ +stock bool TE_SetupParticleAttachment_Names(char[] sParticleName, char[] sAttachmentName, int iEntIndex, bool bFollow=false) +{ + int iParticleStringIndex = GetParticleIndex(sParticleName); + if(iParticleStringIndex == INVALID_STRING_INDEX) + { + return false; + } + int iAttachmentIndex = LookupAttachment(iEntIndex, sAttachmentName); + TE_SetupParticleAttachment(iParticleStringIndex, iAttachmentIndex, iEntIndex, bFollow); + return true; +} + +/** + * Stops all particle effects emitted on an entity, such as attachment followers. + * Note: Currently no way to target particles. + * + * @param iEntIndex Entity index to stop all particles from emitting on. + * + * @error Invalid effect index. + **/ +stock void TE_SetupStopAllParticles(int iEntIndex) +{ + static float vecDummy[3]={0.0, 0.0, 0.0}; + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecDummy[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecDummy[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecDummy[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecDummy[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "ParticleEffectStop"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/ParticleEffectStop indexes"); + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", 0); + + TE_WriteNum("entindex", iEntIndex); + TE_WriteNum("m_nAttachmentIndex", 0); + TE_WriteNum("m_fFlags", 1); + TE_WriteVector("m_vAngles", vecDummy); + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 0.0); + TE_WriteFloat("m_flRadius", 0.0); + TE_WriteNum("m_nDamageType", 0); +} + +/** + * Sets up tracer sound for client-side. + * Note: Uses client-side ray from two points to play wizz sound. + * Note: Wizz sound is directional. + * + * @param vecParticleStartPos Starting position of the sound in world space. + * @param vecParticleEndPos Ending position of the sound in world space. + * + * @error Invalid effect index. + **/ +stock void TE_SetupTracerSound(float vecParticleStartPos[3], float vecParticleEndPos[3]) +{ + static float vecDummy[3]={0.0, 0.0, 0.0}; + static EngineVersion IsEngine; + if(IsEngine == Engine_Unknown) + IsEngine = GetEngineVersion(); + + TE_Start("EffectDispatch"); + + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", vecParticleStartPos[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", vecParticleStartPos[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", vecParticleStartPos[2]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", vecParticleEndPos[0]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", vecParticleEndPos[1]); + TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", vecParticleEndPos[2]); + + static int iEffectIndex = INVALID_STRING_INDEX; + if(iEffectIndex < 0) + { + iEffectIndex = __FindStringIndex2(FindStringTable("EffectDispatch"), "TracerSound"); + if(iEffectIndex == INVALID_STRING_INDEX) + SetFailState("Unable to find EffectDispatch/TracerSound indexes"); + + } + + TE_WriteNum("m_iEffectName", iEffectIndex); + TE_WriteNum("m_nHitBox", 0); + + TE_WriteNum("entindex", 0); + TE_WriteNum("m_nAttachmentIndex", 0); + + TE_WriteNum("m_fFlags", 1); + + TE_WriteVector("m_vAngles", vecDummy); + + TE_WriteFloat("m_flMagnitude", 0.0); + TE_WriteFloat("m_flScale", 0.0); + TE_WriteFloat("m_flRadius", 0.0); + TE_WriteNum("m_nDamageType", 0); +} + +/** + * Credit: Dysphie for decal example + * Note to apply to static props pass 0(world) to iTarget and iHitbox is used for static prop index TR_GetHitBoxIndex() to trace it. + * + * @param vecOrigin Origin of the decal in world space. + * @param vecStart Starting point of the decal's origin. + * @param iTarget Entity index of the target to splash the decal on. + * @param iHitbox Hitbox or bodygroup to apply decal on. (Only required for static props.) + * @param iDecalIndex Decal index in the decal stringtable. + **/ +stock void TE_SetupEntityDecal(float vecOrigin[3], float vecStart[3], int iTarget, int iHitbox=0, int iPrecacheDecal) +{ + TE_Start("Entity Decal"); + TE_WriteVector("m_vecOrigin", vecOrigin); + TE_WriteVector("m_vecStart", vecStart); + TE_WriteNum("m_nEntity", iTarget); + TE_WriteNum("m_nHitbox", iHitbox); + TE_WriteNum("m_nIndex", iPrecacheDecal); +} + +/** + * Credit: Dysphie for decal example + * Sets up a world decal. + * + * @param vecOrigin Origin of the decal in world space. + * @param iDecalIndex Decal index in the decal stringtable, + **/ +stock void TE_SetupWorldDecal(float vecOrigin[3], int iPrecacheDecal) +{ + TE_Start("World Decal"); + TE_WriteVector("m_vecOrigin", vecOrigin); + TE_WriteNum("m_nIndex", iPrecacheDecal); +} + +/** + * Sets up a decal from a traceray. + * + * @param hTR Pass traceray handle or INVALID_HANDLE to use global trace result. + * @param sDecalName Name of the decal. + * + * @return true if decal was setup false otherwise. + * @error Invaid Handle. + **/ +stock bool TE_SetupDecal_FromTrace(Handle hTR=INVALID_HANDLE, char[] sDecalName) +{ + if(!TR_DidHit(hTR)) + return false; + + int iSurf = TR_GetSurfaceFlags(hTR); + if((iSurf & SURF_NODECALS) || (iSurf & SURF_NODRAW) || (iSurf & SURF_SKY)) + { + return false; + } + + int iPrecacheDecal = GetDecalIndex(sDecalName); + if(iPrecacheDecal == INVALID_STRING_TABLE) + return false; + + float vecStart[3]; + float vecEnd[3]; + int iTarget = TR_GetEntityIndex(hTR); + int iHitbox = TR_GetHitBoxIndex(hTR); + TR_GetStartPosition(hTR, vecStart); + TR_GetEndPosition(vecEnd, hTR); + + if(iTarget == 0) + { + if(iHitbox) + { + TE_SetupEntityDecal(vecEnd, vecStart, iTarget, iHitbox, iPrecacheDecal); + } + else + { + TE_SetupWorldDecal(vecEnd, iPrecacheDecal); + } + } + else + { + TE_SetupEntityDecal(vecEnd, vecStart, iTarget, iHitbox, iPrecacheDecal); + } + return true; +} + +/** + * Gets a Decal index or late precaches it. + * Note: Cache decal in OnMapStart() with PrecacheDecal to avoid them spewing errors. + * + * @param sDecalName Name of the particle system. + * + * @return The decal index or INVALID_STRING_INDEX on error. + * @error Invalid decal stringtable index. + **/ +stock int GetDecalIndex(char[] sDecalName) +{ + int iDecalIndex; + if(!IsDecalPrecached(sDecalName)) + { + iDecalIndex = PrecacheDecal(sDecalName); + +#if !defined DISABLE_PRINT_PRECACHE_ERRORS + if(iDecalIndex == INVALID_STRING_INDEX) + { + LogError("Unable to late precache of decal '%s'", sDecalName); + } + else + { + LogError("Late precache of decal '%s'", sDecalName); + } +#endif + + } + else + { + iDecalIndex = PrecacheDecal(sDecalName); + } + return iDecalIndex; +} + + +/** + * Gets a particle system index or late precaches it. + * Note: Cache particle systems in OnMapStart() with Precache_Particle_System to avoid them spewing errors. + * + * @param sParticleName Name of the particle system. + * + * @return The particle system index or INVALID_STRING_INDEX on error. + * @error Invalid particle stringtable index. + **/ +stock int GetParticleIndex(char[] sParticleName) +{ + static int iParticleTableid = INVALID_STRING_TABLE; + if(iParticleTableid == INVALID_STRING_TABLE) + { + iParticleTableid = FindStringTable("ParticleEffectNames"); + if(iParticleTableid == INVALID_STRING_TABLE) + SetFailState("Failed to find 'ParticleEffectNames' stringtable."); + } + + int iParticleStringIndex = __FindStringIndex2(iParticleTableid, sParticleName); + if(iParticleStringIndex == INVALID_STRING_INDEX) + { + iParticleStringIndex = __PrecacheParticleSystem(sParticleName); + +#if !defined DISABLE_PRINT_PRECACHE_ERRORS + if(iParticleStringIndex == INVALID_STRING_INDEX) + { + LogError("Unable to late precache of particle '%s'", sParticleName); + } + else + { + LogError("Late precache of particle '%s'", sParticleName); + } +#endif + + } + return iParticleStringIndex; +} + +//Credit smlib https://github.com/bcserv/smlib +/** + * Rewrite of FindStringIndex, which failed to work correctly in previous tests. + * Searches for the index of a given string in a stringtable. + * + * @param tableidx Stringtable index. + * @param str String to find. + * @return The string index or INVALID_STRING_INDEX on error. + **/ +static stock int __FindStringIndex2(int tableidx, const char[] str) +{ + static char buf[1024]; + + int numStrings = GetStringTableNumStrings(tableidx); + for (int i=0; i < numStrings; i++) { + ReadStringTable(tableidx, i, buf, sizeof(buf)); + + if (StrEqual(buf, str)) { + return i; + } + } + + return INVALID_STRING_INDEX; +} + +//Credit smlib https://github.com/bcserv/smlib +/** + * Precaches the given particle system. + * Note: It's best to call this OnMapStart(). + * Note: Code based on Rochellecrab's, thanks. + * + * @param particleSystem Name of the particle system to precache. + * @return The particle system index or INVALID_STRING_INDEX on error. + **/ +static stock int __PrecacheParticleSystem(const char[] particleSystem) +{ + static int particleEffectNames = INVALID_STRING_TABLE; + + if (particleEffectNames == INVALID_STRING_TABLE) { + if ((particleEffectNames = FindStringTable("ParticleEffectNames")) == INVALID_STRING_TABLE) { + return INVALID_STRING_INDEX; + } + } + + int index = __FindStringIndex2(particleEffectNames, particleSystem); + if (index == INVALID_STRING_INDEX) + { + int numStrings = GetStringTableNumStrings(particleEffectNames); + if (numStrings >= GetStringTableMaxStrings(particleEffectNames)) + { + return INVALID_STRING_INDEX; + } + + AddToStringTable(particleEffectNames, particleSystem); + index = numStrings; + } + + return index; +} + +/** + * A wrapper function for SMLib's PrecacheParticleSystem to avoid include collisions. + * + * @param particlesystem Name of the particle system to precache. + * + * @return The particle system index or INVALID_STRING_INDEX on error. + **/ +stock int Precache_Particle_System(const char[] particleSystem) +{ + return __PrecacheParticleSystem(particleSystem); +} + +/** + * Get an entity's world space origin. + * Note: Not all entities may support "CollisionProperty" for getting the center. + * (https://github.com/LuxLuma/l4d2_structs/blob/master/collision_property.h) + * + * @param iEntity Entity index to get origin of. + * @param vecOrigin Vector to store origin in. + * @param bCenter True to get world space center, false otherwise. + * + * @error Invalid entity index. + **/ +stock void GetAbsOrigin(int iEntity, float vecOrigin[3], bool bCenter=false) +{ + GetEntPropVector(iEntity, Prop_Data, "m_vecAbsOrigin", vecOrigin); + + if(bCenter) + { + float vecMins[3]; + float vecMaxs[3]; + GetEntPropVector(iEntity, Prop_Send, "m_vecMins", vecMins); + GetEntPropVector(iEntity, Prop_Send, "m_vecMaxs", vecMaxs); + + vecOrigin[0] += (vecMins[0] + vecMaxs[0]) * 0.5; + vecOrigin[1] += (vecMins[1] + vecMaxs[1]) * 0.5; + vecOrigin[2] += (vecMins[2] + vecMaxs[2]) * 0.5; + } +} \ No newline at end of file diff --git a/scripting/include/left4dhooks_silver.inc b/scripting/include/left4dhooks_silver.inc new file mode 100644 index 0000000..071790c --- /dev/null +++ b/scripting/include/left4dhooks_silver.inc @@ -0,0 +1,883 @@ +/* +* Left 4 DHooks Direct - Stock Functions +* Copyright (C) 2021 Silvers +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#if defined _l4d_silver_included + #endinput +#endif +#define _l4d_silver_included + +#include +#include + +#tryinclude +#tryinclude +#tryinclude + + + + + +// ==================================================================================================== +// ENUMS +// ==================================================================================================== +enum +{ + L4D_TEAM_UNASSIGNED = 0, + L4D_TEAM_SPECTATOR = 1, + L4D_TEAM_SURVIVOR = 2, + L4D_TEAM_INFECTED = 3, + L4D_TEAM_FOUR = 4 +} + +enum +{ + L4D_WEAPON_SLOT_PRIMARY = 0, + L4D_WEAPON_SLOT_SECONDARY = 1, + L4D_WEAPON_SLOT_GRENADE = 2, + L4D_WEAPON_SLOT_MEDKIT = 3, // L4D2: Also upgrade ammo packs and defibrillator + L4D_WEAPON_SLOT_PILLS = 4, // L4D2: Also Adrenaline + L4D_WEAPON_SLOT_CARRIED = 5 // Physics props such as GasCan etc. +} + +enum +{ + L4D1_ZOMBIE_CLASS_SMOKER = 1, + L4D1_ZOMBIE_CLASS_BOOMER = 2, + L4D1_ZOMBIE_CLASS_HUNTER = 3, + L4D1_ZOMBIE_CLASS_WITCH = 4, + L4D1_ZOMBIE_CLASS_TANK = 5 +} + +enum +{ + L4D2_ZOMBIE_CLASS_SMOKER = 1, + L4D2_ZOMBIE_CLASS_BOOMER = 2, + L4D2_ZOMBIE_CLASS_HUNTER = 3, + L4D2_ZOMBIE_CLASS_SPITTER = 4, + L4D2_ZOMBIE_CLASS_JOCKEY = 5, + L4D2_ZOMBIE_CLASS_CHARGER = 6, + L4D2_ZOMBIE_CLASS_WITCH = 7, + L4D2_ZOMBIE_CLASS_TANK = 8 +} + +enum +{ + SERVER_OS_WINDOWS = 0, + SERVER_OS_LINUX = 1, +} + +// Thanks to "Dragokas": +enum // m_eDoorState +{ + DOOR_STATE_CLOSED, + DOOR_STATE_OPENING_IN_PROGRESS, + DOOR_STATE_OPENED, + DOOR_STATE_CLOSING_IN_PROGRESS +} + +// Thanks to "Dragokas": +enum // m_spawnflags +{ + DOOR_FLAG_STARTS_OPEN = 1, + DOOR_FLAG_STARTS_LOCKED = 2048, + DOOR_FLAG_SILENT = 4096, + DOOR_FLAG_USE_CLOSES = 8192, + DOOR_FLAG_SILENT_NPC = 16384, + DOOR_FLAG_IGNORE_USE = 32768, + DOOR_FLAG_UNBREAKABLE = 524288 +} + + + + + +// ==================================================================================================== +// STOCKS +// ==================================================================================================== +// ENGINE STOCKS +// ================================================== +static EngineVersion g_iEngine; + +/** + * @brief Returns if the server is running on the Left 4 Dead series engine + * + * @return True if the server is running on the Left 4 Dead series + */ +stock bool L4D_IsEngineLeft4Dead() +{ + if( g_iEngine == Engine_Unknown ) + { + g_iEngine = GetEngineVersion(); + } + + return (g_iEngine == Engine_Left4Dead || g_iEngine == Engine_Left4Dead2); +} + +/** + * @brief Returns if the server is running on Left 4 Dead 1 + * + * @return True if server is running on Left 4 Dead 1 + */ +stock bool L4D_IsEngineLeft4Dead1() +{ + if( g_iEngine == Engine_Unknown ) + { + g_iEngine = GetEngineVersion(); + } + + return g_iEngine == Engine_Left4Dead; +} + +/** + * @brief Returns if the server is running on Left 4 Dead 2 + * + * @return True if server is running on Left 4 Dead 2 + */ +stock bool L4D_IsEngineLeft4Dead2() +{ + if( g_iEngine == Engine_Unknown ) + { + g_iEngine = GetEngineVersion(); + } + + return g_iEngine == Engine_Left4Dead2; +} + + + +// ================================================== +// DOOR STOCKS +// ================================================== + +/** + * @brief Returns the specified door state. Uses the "DOOR_STATE_*" enum + * + * @param entity The "prop_door*" entity to check + * + * @return the "DOOR_STATE_*" value + */ +stock int L4D_GetDoorState(int entity) +{ + return GetEntProp(entity, Prop_Data, "m_eDoorState"); +} + +/** + * @brief Returns the specified door flags. Uses the "DOOR_FLAG_*" enum + * + * @param entity The "prop_door*" entity to check + * + * @return the "DOOR_FLAG_*" value + */ +stock int L4D_GetDoorFlag(int entity) +{ + return GetEntProp(entity, Prop_Data, "m_spawnflags"); +} + + + +// ================================================== +// ENTITY STOCKS +// ================================================== + +/** + * @brief Returns a players current weapon, or -1 if none. + * + * @param client Client ID of the player to check + * + * @return weapon entity index or -1 if none + */ +stock int L4D_GetPlayerCurrentWeapon(int client) +{ + return GetEntPropEnt(client, Prop_Send, "m_hActiveWeapon"); +} + +/** + * @brief Returns the Custom Ability entity of a Special Infected + * @remarks Returns the entity of "ability_vomit" (Boomer), "ability_lunge" (Hunter), "ability_tongue" (Smoker), "ability_charge" (Charger), "ability_ride" (Jockey), "ability_spit" (Spitter), "ability_throw" (Tank) + * + * @param client Client ID of the player to check + * + * @return entity index or -1 if none + */ +stock int L4D_GetPlayerCustomAbility(int client) +{ + return GetEntPropEnt(client, Prop_Send, "m_customAbility"); +} + +/** + * @brief Returns the players Use Action Target + * + * @param client Client ID of the player to check + * + * @return entity index or -1 if none + */ +stock int L4D_GetPlayerUseTarget(int client) +{ + return GetEntPropEnt(client, Prop_Send, "m_useActionTarget"); +} + +/** + * @brief Returns the parent of an entity + * + * @param client Entity index to check + * + * @return entity index or -1 if none + */ +stock int L4D_EntityParent(int entity) +{ + return GetEntPropEnt(entity, Prop_Data, "m_pParent"); +} + + + +// ================================================== +// COMMON INFECTED STOCKS +// ================================================== + +/** + * @brief Creates a panic event mob horde + * @remarks Subject to horde cooldown timer + * @remarks Can probably reset the timer with either "L4D_ResetMobTimer();" native or using "L4D2CT_MobSpawnTimer" with the timer natives. + * + * @noreturn + */ +stock void L4D_ForcePanicEvent() +{ + ServerCommand("director_force_panic_event"); +} + +/** + * @brief Returns the current number of common infected + * + * @return entity index or -1 if none + */ +stock int L4D_GetCommonsCount() +{ + int entity = -1; + int count; + while( (entity = FindEntityByClassname(entity, "infected")) != INVALID_ENT_REFERENCE ) + { + count++; + } + + return count; +} + +/** + * @brief Spawns a Common Infected at the given position + * + * @param vPos Origin vector to spawn at + * @param vAng Angles vector to spawn at (optional) + * + * @return entity index or -1 on failure + */ +stock int L4D_SpawnCommonInfected(float vPos[3], float vAng[3] = { 0.0, 0.0, 0.0 }) +{ + int entity = CreateEntityByName("infected"); + if( entity != -1 ) + { + DispatchSpawn(entity); + TeleportEntity(entity, vPos, vAng, NULL_VECTOR); + } + + return entity; +} + + + +// ================================================== +// INFECTED: GET VICTIM +// ================================================== + +/** + * @brief Returns the Survivor victim when pinned by a Hunter + * + * @param client Client ID of the Special Infected player to check + * + * @return Victim client index, or 0 if none + */ +stock int L4D_GetVictimHunter(int client) +{ + int attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_pounceVictim")) > 0 ) + return attacker; + + return 0; +} + +/** + * @brief Returns the Survivor victim when pinned by a Smoker + * + * @param client Client ID of the Special Infected player to check + * + * @return Victim client index, or 0 if none + */ +stock int L4D_GetVictimSmoker(int client) +{ + int attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_tongueVictim")) > 0 ) + return attacker; + + return 0; +} + +/** + * @brief Returns the Survivor victim when pinned by a Charger + * + * @param client Client ID of the Special Infected player to check + * + * @return Victim client index, or 0 if none + */ +// L4D2 only. +stock int L4D_GetVictimCharger(int client) +{ + int attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_pummelVictim")) > 0 ) + return attacker; + } + + return 0; +} + +/** + * @brief Returns the Survivor victim when carried by a Charger + * + * @param client Client ID of the Special Infected player to check + * + * @return Victim client index, or 0 if none + */ +// L4D2 only. +stock int L4D_GetVictimCarry(int client) +{ + int attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_carryVictim")) > 0 ) + return attacker; + } + + return 0; +} + +/** + * @brief Returns the Survivor victim when pinned by a Jockey + * + * @param client Client ID of the Special Infected player to check + * + * @return Victim client index, or 0 if none + */ +// L4D2 only. +stock int L4D_GetVictimJockey(int client) +{ + int attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_jockeyVictim")) > 0 ) + return attacker; + } + + return 0; +} + + + +// ================================================== +// SURVIVOR: GET ATTACKER +// ================================================== + +/** + * @brief Returns a Survivors attacker when pinned by a Hunter + * + * @param client Client ID of the Survivor player to check + * + * @return Attacker client index, or 0 if none + */ +stock int L4D_GetAttackerHunter(int client) +{ + int attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_pounceAttacker")) > 0 ) + return attacker; + + return 0; +} + +/** + * @brief Returns a Survivors attacker when pinned by a Smoker + * + * @param client Client ID of the Survivor player to check + * + * @return Attacker client index, or 0 if none + */ +stock int L4D_GetAttackerSmoker(int client) +{ + int attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_tongueOwner")) > 0 ) + return attacker; + + return 0; +} + +/** + * @brief Returns a Survivors attacker when pummelled by a Charger + * + * @param client Client ID of the Survivor player to check + * + * @return Attacker client index, or 0 if none + */ +// L4D2 only. +stock int L4D_GetAttackerCharger(int client) +{ + int attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_pummelAttacker")) > 0 ) + return attacker; + } + + return 0; +} + +/** + * @brief Returns a Survivors attacker when carried by a Charger + * + * @param client Client ID of the Survivor player to check + * + * @return Attacker client index, or 0 if none + */ +// L4D2 only. +stock int L4D_GetAttackerCarry(int client) +{ + int attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_carryAttacker")) > 0 ) + return attacker; + } + + return 0; +} + +/** + * @brief Returns a Survivors attacker when pinned by a Jockey + * + * @param client Client ID of the Survivor player to check + * + * @return Attacker client index, or 0 if none + */ +// L4D2 only. +stock int L4D_GetAttackerJockey(int client) +{ + int attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker")) > 0 ) + return attacker; + } + + return 0; +} + + + +// ================================================== +// PINNED CHECKS +// ================================================== + +/** + * @brief Returns the attacker when a Survivor is pinned by a Special Infected + * + * @param client Client ID of the player to check + * + * @return Attacker client index, or 0 if none + */ +stock int L4D_GetPinnedInfected(int client) +{ + int attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_pounceAttacker")) > 0 ) + return attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_tongueOwner")) > 0 ) + return attacker; + + if( L4D_IsEngineLeft4Dead2() ) + { + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker")) > 0 ) + return attacker; + + if( (attacker = GetEntPropEnt(client, Prop_Send, "m_pummelAttacker")) > 0 ) + return attacker; + } + + return 0; +} + +/** + * @brief Returns if a Survivor is pinned by a Special Infected + * + * @param client Client ID of the player to check + * + * @return True if pinned, false otherwise + */ +stock bool L4D_IsPlayerPinned(int client) +{ + return L4D_GetPinnedInfected(client) != 0; +} + + + +// ================================================== +// LEDGE HANG STOCKS +// ================================================== + +/** + * @brief Returns if a Survivor is hanging from a ledge + * + * @param client Client ID of the player to check + * + * @return True if hanging, false otherwise + */ +stock bool L4D_IsPlayerHangingFromLedge(int client) +{ + return GetEntProp(client, Prop_Send, "m_isHangingFromLedge", 1) == 1; +} + +/** + * @brief Returns if a Survivor can ledge hang + * + * @param client Client ID of the player to check + * + * @return True if can hang, false otherwise + */ +stock bool L4D_CanPlayerLedgeHang(int client) +{ + // Get address + static int addy = -1; + if( addy == -1 ) + { + /* + See the function "CTerrorPlayer::InputDisableLedgeHang" + On Windows: this[16417] = 0; + m_bWasPresentAtSurvivalStart offset == 16388 + 16388 + 29 = 16417 + Offset unlikely to change unless netprops are added/removed/changed - which is very unlikely + */ + addy = FindSendPropInfo("CTerrorPlayer", "m_bWasPresentAtSurvivalStart") + 29; + } + + return GetEntData(client, addy, 1) == 1; +} + +/** + * @brief Allow a Survivor to ledge hang + * + * @param client Client ID of the player to affect + * + * @noreturn + */ +stock void L4D_LedgeHangEnable(int client) +{ + AcceptEntityInput(client, "EnableLedgeHang"); +} + +/** + * @brief Disallow a Survivor to ledge hang + * + * @param client Client ID of the player to affect + * + * @noreturn + */ +stock void L4D_LedgeHangDisable(int client) +{ + AcceptEntityInput(client, "DisableLedgeHang"); +} + + + +// ================================================== +// INCAP and REVIVE STOCKS +// ================================================== +/** + * @brief Set a Survivors incapacitated netprop + * @remarks When setting to false can make a Survivor have 300 health (the incapped health value) + * + * @param client Client ID of the player to affect + * @param incap True to incap, false to remove incap (not the proper way of reviving from incap, probably bypasses revive event) + * + * @noreturn + */ +stock void L4D_SetPlayerIncapped(int client, bool incap) +{ + SetEntProp(client, Prop_Send, "m_isIncapacitated", incap ? 1 : 0); +} + +/** + * @brief Incap a Survivor by giving them 100.0 damage + * + * @param client Client ID of the player to affect + * @param attacker Optionally set the attacker to credit them for the incap + * + * @noreturn + */ +stock void L4D_SetPlayerIncappedDamage(int client, int attacker = 0) +{ + SDKHooks_TakeDamage(client, attacker, attacker, 100.0); +} + +/** + * @brief Returns a Survivors revive target + * + * @param client Client ID of the player to check + * + * @return Target client index, or 0 if none + */ +stock int L4D_GetPlayerReviveTarget(int client) +{ + int target = GetEntPropEnt(client, Prop_Send, "m_reviveTarget"); + if( target > 0 ) + return target; + + return 0; +} + +/** + * @brief Returns an incapacitated Survivor's reviver + * + * @param client Client ID of the player to check + * + * @return Reviver client index, or 0 if none + */ +stock int L4D_GetPlayerReviveOwner(int client) +{ + int target = GetEntPropEnt(client, Prop_Send, "m_reviveOwner"); + if( target > 0 ) + return target; + + return 0; +} + +/** + * @brief Stops a Survivor reviving someone + * @remarks Prevents accidental freezing of player who tried to revive you + * @remarks Thanks to "Dragokas" for the stock + * + * @param client Client ID of the player to affect + * + * @noreturn + */ +stock void L4D_StopReviveAction(int client) +{ + int owner_save = -1; + int target_save = -1; + int owner = GetEntPropEnt(client, Prop_Send, "m_reviveOwner"); // when you reviving somebody, this is -1. When somebody revive you, this is somebody's id + int target = GetEntPropEnt(client, Prop_Send, "m_reviveTarget"); // when you reviving somebody, this is somebody's id. When somebody revive you, this is -1 + + SetEntPropEnt(client, Prop_Send, "m_reviveOwner", -1); + SetEntPropEnt(client, Prop_Send, "m_reviveTarget", -1); + + if( owner != -1 ) // we must reset flag for both - for you, and who you revive + { + SetEntPropEnt(owner, Prop_Send, "m_reviveOwner", -1); + SetEntPropEnt(owner, Prop_Send, "m_reviveTarget", -1); + owner_save = owner; + } + + if( target != -1 ) + { + SetEntPropEnt(target, Prop_Send, "m_reviveOwner", -1); + SetEntPropEnt(target, Prop_Send, "m_reviveTarget", -1); + target_save = target; + } + + if( L4D_IsEngineLeft4Dead2() ) + { + owner = GetEntPropEnt(client, Prop_Send, "m_useActionOwner"); // used when healing e.t.c. + target = GetEntPropEnt(client, Prop_Send, "m_useActionTarget"); + SetEntPropEnt(client, Prop_Send, "m_useActionOwner", -1); + SetEntPropEnt(client, Prop_Send, "m_useActionTarget", -1); + + if( owner != -1 ) + { + SetEntPropEnt(owner, Prop_Send, "m_useActionOwner", -1); + SetEntPropEnt(owner, Prop_Send, "m_useActionTarget", -1); + owner_save = owner; + } + + if( target != -1 ) + { + SetEntPropEnt(target, Prop_Send, "m_useActionOwner", -1); + SetEntPropEnt(target, Prop_Send, "m_useActionTarget", -1); + target_save = target; + } + + SetEntProp(client, Prop_Send, "m_iCurrentUseAction", 0); + SetEntPropFloat(client, Prop_Send, "m_flProgressBarDuration", 0.0); + + if( owner_save != -1 ) + { + SetEntProp(owner_save, Prop_Send, "m_iCurrentUseAction", 0); + SetEntPropFloat(owner_save, Prop_Send, "m_flProgressBarDuration", 0.0); + } + + if( target_save != -1 ) + { + SetEntProp(target_save, Prop_Send, "m_iCurrentUseAction", 0); + SetEntPropFloat(target_save, Prop_Send, "m_flProgressBarDuration", 0.0); + } + } + else + { + owner = GetEntPropEnt(client, Prop_Send, "m_healOwner"); // used when healing + target = GetEntPropEnt(client, Prop_Send, "m_healTarget"); + SetEntPropEnt(client, Prop_Send, "m_healOwner", -1); + SetEntPropEnt(client, Prop_Send, "m_healTarget", -1); + + if( owner != -1 ) + { + SetEntPropEnt(owner, Prop_Send, "m_healOwner", -1); + SetEntPropEnt(owner, Prop_Send, "m_healTarget", -1); + owner_save = owner; + } + + if( target != -1 ) + { + SetEntPropEnt(target, Prop_Send, "m_healOwner", -1); + SetEntPropEnt(target, Prop_Send, "m_healTarget", -1); + target_save = target; + } + + SetEntProp(client, Prop_Send, "m_iProgressBarDuration", 0); + + if( owner_save != -1 ) + { + SetEntProp(owner_save, Prop_Send, "m_iProgressBarDuration", 0); + } + + if( target_save != -1 ) + { + SetEntProp(target_save, Prop_Send, "m_iProgressBarDuration", 0); + } + } +} + +/** + * @brief Returns if a Survivor is incapacitated + * + * @param client Client ID of the player to check + * + * @return True if incapacitated, false otherwise + */ +#pragma deprecated Use L4D_IsPlayerIncapacitated instead +stock bool L4D_IsPlayerIncapped(int client) +{ + return GetEntProp(client, Prop_Send, "m_isIncapacitated", 1) != 0; +} + + + + + +// ================================================== +// GET CLIENT STOCKS +// ================================================== + +/** + * @brief Returns a random client in-game + * + * @return Client index or 0 if none + */ +stock int GetAnyRandomClient() +{ + int client; + ArrayList aClients = new ArrayList(); + + for( int i = 1; i <= MaxClients; i++ ) + { + if( IsClientInGame(i) ) + { + aClients.Push(i); + } + } + + if( aClients.Length > 0 ) + client = aClients.Get(GetRandomInt(0, aClients.Length - 1)); + + delete aClients; + + return client; +} + +/** + * @brief Returns a random Survivor + * + * @param alive -1 = Any. 0 = Only dead players. 1 = Only alive players + * @param bots -1 = Any. 0 - Only real players. 1 = Only fake players + * + * @return Client index or 0 if none + */ +stock int GetRandomSurvivor(int alive = -1, int bots = -1) +{ + return Local_GetRandomClient(2, alive, bots); +} + +/** + * @brief Returns a random Special Infected + * + * @param alive -1 = Any. 0 = Only dead players. 1 = Only alive players + * @param bots -1 = Any. 0 - Only real players. 1 = Only fake players + * + * @return Client index or 0 if none + */ +stock int GetRandomInfected(int alive = -1, int bots = -1) +{ + return Local_GetRandomClient(3, alive, bots); +} + +stock int Local_GetRandomClient(int team, int alive = -1, int bots = -1) +{ + ArrayList aClients = new ArrayList(); + + for( int i = 1; i <= MaxClients; i++ ) + { + if( IsClientInGame(i) && GetClientTeam(i) == team && (alive == -1 || IsPlayerAlive(i) == view_as(alive)) && (bots == -1 || IsFakeClient(i) == view_as(alive)) ) + { + aClients.Push(i); + } + } + + int client; + + if( aClients.Length > 0 ) + client = aClients.Get(GetRandomInt(0, aClients.Length - 1)); + + delete aClients; + + return client; +} \ No newline at end of file diff --git a/scripting/include/left4dhooks_stocks.inc b/scripting/include/left4dhooks_stocks.inc new file mode 100644 index 0000000..3441b8b --- /dev/null +++ b/scripting/include/left4dhooks_stocks.inc @@ -0,0 +1,1835 @@ +/** + * ============================================================================= + * Left 4 Dead Stocks Library (C)2011-2012 Buster "Mr. Zero" Nielsen + * Syntax Update and merge into "Left 4 DHooks Direct" (C) 2021 "SilverShot" + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License, version 3.0, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," + * the "Source Engine," the "SourcePawn JIT," and any Game MODs that run on + * software by the Valve Corporation. You must obey the GNU General Public + * License in all respects for all other code used. Additionally, + * AlliedModders LLC grants this exception to all derivative works. + * AlliedModders LLC defines further exceptions, found in LICENSE.txt + * (as of this writing, version JULY-31-2007), or + * . + */ + +#if defined _l4d_stocks_included + #endinput +#endif +#define _l4d_stocks_included + +#tryinclude +#tryinclude +#tryinclude + + + + + +// ==================================================================================================== +// VARIOUS STOCKS: "l4d_stocks.inc" by "Mr. Zero" +// ==================================================================================================== + +#include + +/* Spawn state bit flags */ +#define L4D_SPAWNFLAG_CANSPAWN (0 << 0) +#define L4D_SPAWNFLAG_DISABLED (1 << 0) +#define L4D_SPAWNFLAG_WAITFORSURVIVORS (1 << 1) +#define L4D_SPAWNFLAG_WAITFORFINALE (1 << 2) +#define L4D_SPAWNFLAG_WAITFORTANKTODIE (1 << 3) +#define L4D_SPAWNFLAG_SURVIVORESCAPED (1 << 4) +#define L4D_SPAWNFLAG_DIRECTORTIMEOUT (1 << 5) +#define L4D_SPAWNFLAG_WAITFORNEXTWAVE (1 << 6) +#define L4D_SPAWNFLAG_CANBESEEN (1 << 7) +#define L4D_SPAWNFLAG_TOOCLOSE (1 << 8) +#define L4D_SPAWNFLAG_RESTRICTEDAREA (1 << 9) +#define L4D_SPAWNFLAG_BLOCKED (1 << 10) + +/* Weapon upgrade bit flags */ +#define L4D2_WEPUPGFLAG_NONE (0 << 0) +#define L4D2_WEPUPGFLAG_INCENDIARY (1 << 0) +#define L4D2_WEPUPGFLAG_EXPLOSIVE (1 << 1) +#define L4D2_WEPUPGFLAG_LASER (1 << 2) + +/* Instructor Hint bit flags */ +#define L4D2_IHFLAG_NONE (0 << 0) +#define L4D2_IHFLAG_PULSE_SLOW (1 << 0) +#define L4D2_IHFLAG_PULSE_FAST (1 << 1) +#define L4D2_IHFLAG_PULSE_URGENT (1 << 2) +#define L4D2_IHFLAG_ALPHA_SLOW (1 << 3) +#define L4D2_IHFLAG_ALPHA_FAST (1 << 4) +#define L4D2_IHFLAG_ALPHA_URGENT (1 << 5) +#define L4D2_IHFLAG_SHAKE_NARROW (1 << 6) +#define L4D2_IHFLAG_SHAKE_WIDE (1 << 7) +#define L4D2_IHFLAG_STATIC (1 << 8) + +/* Survivor intensity -- Equal or less */ +#define L4D_SURVIVORINTENSITY_MILD 0.25 +#define L4D_SURVIVORINTENSITY_MODERATE 0.50 +#define L4D_SURVIVORINTENSITY_HIGH 0.75 +#define L4D_SURVIVORINTENSITY_EXTREME 1.00 + +enum L4DTeam +{ + L4DTeam_Unassigned = 0, + L4DTeam_Spectator = 1, + L4DTeam_Survivor = 2, + L4DTeam_Infected = 3 +} + +enum L4DWeaponSlot +{ + L4DWeaponSlot_Primary = 0, + L4DWeaponSlot_Secondary = 1, + L4DWeaponSlot_Grenade = 2, + L4DWeaponSlot_FirstAid = 3, + L4DWeaponSlot_Pills = 4 +} + +enum L4D2GlowType +{ + L4D2Glow_None = 0, + L4D2Glow_OnUse = 1, + L4D2Glow_OnLookAt = 2, + L4D2Glow_Constant = 3 +} + +enum L4D1ZombieClassType +{ + L4D1ZombieClass_Smoker = 1, + L4D1ZombieClass_Boomer = 2, + L4D1ZombieClass_Hunter = 3, + L4D1ZombieClass_Witch = 4, + L4D1ZombieClass_Tank = 5, + L4D1ZombieClass_NotInfected = 6 +} + +enum L4D2ZombieClassType +{ + L4D2ZombieClass_Smoker = 1, + L4D2ZombieClass_Boomer = 2, + L4D2ZombieClass_Hunter = 3, + L4D2ZombieClass_Spitter = 4, + L4D2ZombieClass_Jockey = 5, + L4D2ZombieClass_Charger = 6, + L4D2ZombieClass_Witch = 7, + L4D2ZombieClass_Tank = 8, + L4D2ZombieClass_NotInfected = 9 +} + +enum L4D2UseAction +{ + L4D2UseAction_None = 0, // No use action active + L4D2UseAction_Healing = 1, // Includes healing yourself or a teammate. + L4D2UseAction_Defibing = 4, // When defib'ing a dead body. + L4D2UseAction_GettingDefibed = 5, // When comming back to life from a dead body. + L4D2UseAction_PouringGas = 8, // Pouring gas into a generator + L4D2UseAction_Cola = 9, // For Dead Center map 2 cola event, when handing over the cola to whitalker. + L4D2UseAction_Button = 10 // Such as buttons, timed buttons, generators, etc. + /* List is not fully done, these are just the ones I have found so far */ +} + +enum L4DResourceType +{ + L4DResource_Ping, + L4DResource_Score, + L4DResource_TankTickets, + L4DResource_Deaths, + L4DResource_MaxHealth, + L4DResource_WantsToPlay, + L4DResource_TankTickets2 +} + +static const char L4DResourceName[L4DResourceType][] = +{ + "m_iPing", + "m_iScore", + "m_iTankTickets", + "m_iDeaths", + "m_maxHealth", + "m_wantsToPlay", + "m_tankTickets" +}; + +/** + * Returns zombie player L4D1 zombie class. + * + * @param client Player's index. + * @return Current L4D1ZombieClassType of player. + * @error Invalid client index. + */ +// L4D1 only. +stock L4D1ZombieClassType L4D1_GetPlayerZombieClass(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_zombieClass")); +} + +/** + * Set zombie player L4D1 zombie class. + * + * @param client Player's index. + * @param class L4D1ZombieClassType class symbol. + * @noreturn + * @error Invalid client index. + */ +// L4D1 only. +stock void L4D1_SetPlayerZombieClass(int client, L4D1ZombieClassType class) +{ + SetEntProp(client, Prop_Send, "m_zombieClass", class); +} + +/** + * Returns zombie player L4D2 zombie class. + * + * @param client Player's index. + * @return Current L4D2ZombieClassType of player. + * @error Invalid client index. + */ +// L4D2 only. +stock L4D2ZombieClassType L4D2_GetPlayerZombieClass(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_zombieClass")); +} + +/** + * Set zombie player L4D2 zombie class. + * + * @param client Player's index. + * @param class L4D2ZombieClassType class symbol. + * @noreturn + * @error Invalid client index. + */ +// L4D2 only. +stock void L4D2_SetPlayerZombieClass(int client, L4D2ZombieClassType class) +{ + SetEntProp(client, Prop_Send, "m_zombieClass", class); +} + +/** + * Returns ghost state of zombie player. + * + * @param client Player index. + * @return True if player is ghost, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_IsPlayerGhost(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_isGhost", 1)); +} + +/** + * Sets ghost state of zombie player. + * + * @param client Player index. + * @param isGhost Sets ghost status. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerGhostState(int client, bool isGhost) +{ + SetEntProp(client, Prop_Send, "m_isGhost", isGhost, 1); +} + +/** + * Returns ghost spawn state of zombie player. + * + * @param client Player index. + * @return Player's spawn state bits. + * @error Invalid client index. + */ +stock int L4D_GetPlayerGhostSpawnState(int client) +{ + return GetEntProp(client, Prop_Send, "m_ghostSpawnState"); +} + +/** + * Set zombie player's ghost spawn state bits. + * + * Note: The game updates spawn state bits every frame. + * + * @param client Player index. + * @param bits Ghost spawn state bits. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerGhostSpawnState(int client, int bits) +{ + SetEntProp(client, Prop_Send, "m_ghostSpawnState", bits); +} + +/** + * Returns whether zombie player is culling. + * + * @param client Player index. + * @return True if player is culling, false otherwise. + */ +stock bool L4D_IsPlayerCulling(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_isCulling", 1)); +} + +/** + * Set culling state of zombie player. + * + * @param client Player index. + * @param isCulling Whether player is culling. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerCullingState(int client, bool isCulling) +{ + SetEntProp(client, Prop_Send, "m_isCulling", isCulling, 1); +} + +/** + * Returns whether player is incapacitated. + * + * Note: A tank player will return true when in dying animation. + * + * @param client Player index. + * @return True if incapacitated, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_IsPlayerIncapacitated(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_isIncapacitated", 1)); +} + +/** + * Set player's incapacitated state. + * + * @param client Player index. + * @param isIncapacitated Whether the player is incapacitated. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerIncapacitatedState(int client, bool isIncapacitated) +{ + SetEntProp(client, Prop_Send, "m_isIncapacitated", isIncapacitated, 1); +} + +/** + * Returns survivor player shove penalty. + * + * @param client Player index. + * @return Current shove penalty of player. + */ +stock int L4D_GetPlayerShovePenalty(int client) +{ + return GetEntProp(client, Prop_Send, "m_iShovePenalty"); +} + +/** + * Set survivor player shove penalty. + * + * @param client Player index. + * @param shovePenalty Shove penalty. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerShovePenalty(int client, int shovePenalty) +{ + SetEntProp(client, Prop_Send, "m_iShovePenalty", shovePenalty); +} + +/** + * Returns tank player's frustration. + * + * @param client Player index. + * @return How frustrated tank player is. + * @error Invalid client index. + */ +stock int L4D_GetTankFrustration(int client) +{ + return GetEntProp(client, Prop_Send, "m_frustration"); +} + +/** + * Set tank player's frustration. + * + * @param client Player index. + * @param frustration How frustrated tank player is. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetTankFrustration(int client, int frustration) +{ + SetEntProp(client, Prop_Send, "m_frustration", frustration); +} + +/** + * Returns whether survivor player is idle. + * + * @param Player index. + * @return True if idle, false otherwise. + */ +stock bool L4D_IsPlayerIdle(int client) +{ + return L4D_GetBotOfIdlePlayer(client) > -1; +} + +/** + * Returns survivor bot of idle survivor player. + * + * @param client Player index. + * @return Player index of the bot, -1 if not found. + */ +stock int L4D_GetBotOfIdlePlayer(int client) +{ + int idleClient; + int offset; + char netclass[128]; + + for (int bot = 1; bot <= MaxClients; bot++) + { + if (!IsClientInGame(bot) || + !IsFakeClient(bot) || + view_as(GetClientTeam(bot)) != L4DTeam_Survivor || + !IsPlayerAlive(bot) || + GetClientHealth(bot) < 1) + { + continue; + } + + GetEntityNetClass(bot, netclass, sizeof(netclass)); + offset = FindSendPropInfo(netclass, "m_humanSpectatorUserID"); + + if (offset < 1) + { + continue; + } + + idleClient = GetClientOfUserId(GetEntProp(bot, Prop_Send, "m_humanSpectatorUserID")); + + if (idleClient == client) + { + return bot; + } + } + + return -1; +} + +/** + * Returns idle survivor player of survivor bot. + * + * @param bot Player index of bot. + * @return Player index of idle client, -1 if not found. + * @error Invalid client index. + */ +stock int L4D_GetIdlePlayerOfBot(int bot) +{ + char netclass[128]; + GetEntityNetClass(bot, netclass, sizeof(netclass)); + int offset = FindSendPropInfo(netclass, "m_humanSpectatorUserID"); + + if (offset < 1) + { + return -1; + } + + return GetClientOfUserId(GetEntProp(bot, Prop_Send, "m_humanSpectatorUserID")); +} + +/** + * Returns resource entity. + * + * @return Entity index of resource entity, -1 if not found. + */ +stock int L4D_GetResourceEntity() +{ + return FindEntityByClassname(-1, "terror_player_manager"); +} + +/** + * Retrieves client data from the resource entity. + * + * @param client Player's index. + * @param type ResourceType constant + * @return Value or -1 on failure. + * @error Invalid client index, client not in game or failed to find resource entity. + */ +stock int L4D_GetPlayerResourceData(int client, L4DResourceType type) +{ + if (!IsClientConnected(client)) + { + return -1; + } + + int offset = FindSendPropInfo("CTerrorPlayerResource", L4DResourceName[type]); + + if (offset < 1) + { + return -1; + } + + int entity = L4D_GetResourceEntity(); + + if (entity == -1) + { + return -1; + } + + return GetEntData(entity, offset + (client * 4)); +} + +/** + * Sets client data in the resource entity. + * + * Note: The game overwrites these values every frame, so changing them will have very little effect. + * + * @param client Player's index. + * @param type ResourceType constant + * @param value Value to set. + * @return Value or -1 on failure. + * @error Invalid client index, client not in game or failed to find resource entity. + */ +stock bool L4D_SetPlayerResourceData(int client, L4DResourceType type, any value) +{ + if (!IsClientConnected(client)) + { + return false; + } + + int offset = FindSendPropInfo("CTerrorPlayerResource", L4DResourceName[type]); + + if (offset < 1) + { + return false; + } + + int entity = L4D_GetResourceEntity(); + + if (entity == -1) + { + return false; + } + + SetEntData(entity, offset + (client * 4), value); + + return true; +} + +/** + * Removes the weapon from a client's weapon slot + * + * @param client Player's index. + * @param slot Slot index. + * @noreturn + * @error Invalid client or lack of mod support. + */ +stock void L4D_RemoveWeaponSlot(int client, L4DWeaponSlot slot) +{ + int weaponIndex; + while ((weaponIndex = GetPlayerWeaponSlot(client, view_as(slot))) != -1) + { + RemovePlayerItem(client, weaponIndex); + RemoveEdict(weaponIndex); + } +} + +/** + * Removes all weapons from a client + * + * @param client Player's index. + * @noreturn + * @error Invalid client or lack of mod support. + */ +stock void L4D_RemoveAllWeapons(int client) +{ + for (int i = 0; i <= 4; i++) + { + L4D_RemoveWeaponSlot(client, view_as(i)); + } +} + +/** + * Returns whether the finale is active. + * + * Note: Finales can also be on other maps than just the finale map. A perfect + * example of this is the Swamp Fever map 1 crescendo event. This event is + * defined as a finale by Valve for some reason. + * + * @return True if finale is active, false otherwise. + */ +stock bool L4D_IsFinaleActive() +{ + int entity = L4D_GetResourceEntity(); + + if (entity == -1) + { + return false; + } + + return view_as(GetEntProp(entity, Prop_Send, "m_isFinale", 1)); +} + +/** + * Returns whether any survivor have left the safe area. + * + * @return True if any survivor have left safe area, false otherwise. + */ +stock bool L4D_HasAnySurvivorLeftSafeAreaStock() +{ + int entity = L4D_GetResourceEntity(); + + if (entity == -1) + { + return false; + } + + return view_as(GetEntProp(entity, Prop_Send, "m_hasAnySurvivorLeftSafeArea", 1)); +} + +/** + * Returns pending tank player. + * + * @return Player index of pending tank player, -1 if not found. + */ +stock int L4D_GetPendingTankPlayer() +{ + int entity = L4D_GetResourceEntity(); + + if (entity == -1) + { + return -1; + } + + return GetEntProp(entity, Prop_Send, "m_pendingTankPlayerIndex"); +} + +/** + * Set entity glow. This is consider safer and more robust over setting each glow property on their own because glow offset will be check first. + * + * @param entity Entity index. + * @parma type Glow type. + * @param range Glow max range, 0 for unlimited. + * @param minRange Glow min range. + * @param colorOverride Glow color, RGB. + * @param flashing Whether the glow will be flashing. + * @return True if glow was set, false if entity does not support glow. + */ +// L4D2 only. +stock bool L4D2_SetEntityGlow(int entity, L4D2GlowType type, int range, int minRange, colorOverride[3], bool flashing) +{ + if (!IsValidEntity(entity)) + { + return false; + } + + char netclass[128]; + GetEntityNetClass(entity, netclass, sizeof(netclass)); + + int offset = FindSendPropInfo(netclass, "m_iGlowType"); + + if (offset < 1) + { + return false; + } + + L4D2_SetEntityGlow_Type(entity, type); + L4D2_SetEntityGlow_Range(entity, range); + L4D2_SetEntityGlow_MinRange(entity, minRange); + L4D2_SetEntityGlow_Color(entity, colorOverride); + L4D2_SetEntityGlow_Flashing(entity, flashing); + return true; +} + +/** + * Set entity glow type. + * + * @param entity Entity index. + * @parma type Glow type. + * @noreturn + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock void L4D2_SetEntityGlow_Type(int entity, L4D2GlowType type) +{ + SetEntProp(entity, Prop_Send, "m_iGlowType", type); +} + +/** + * Set entity glow range. + * + * @param entity Entity index. + * @parma range Glow range. + * @noreturn + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock void L4D2_SetEntityGlow_Range(int entity, int range) +{ + SetEntProp(entity, Prop_Send, "m_nGlowRange", range); +} + +/** + * Set entity glow min range. + * + * @param entity Entity index. + * @parma minRange Glow min range. + * @noreturn + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock void L4D2_SetEntityGlow_MinRange(int entity, int minRange) +{ + SetEntProp(entity, Prop_Send, "m_nGlowRangeMin", minRange); +} + +/** + * Set entity glow color. + * + * @param entity Entity index. + * @parma colorOverride Glow color, RGB. + * @noreturn + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock void L4D2_SetEntityGlow_Color(int entity, int colorOverride[3]) +{ + SetEntProp(entity, Prop_Send, "m_glowColorOverride", colorOverride[0] + (colorOverride[1] * 256) + (colorOverride[2] * 65536)); +} + +/** + * Set entity glow flashing state. + * + * @param entity Entity index. + * @parma flashing Whether glow will be flashing. + * @noreturn + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock void L4D2_SetEntityGlow_Flashing(int entity, bool flashing) +{ + SetEntProp(entity, Prop_Send, "m_bFlashing", flashing); +} + +/** + * Returns entity glow type. + * + * @param entity Entity index. + * @return L4D2 glow type. + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock L4D2GlowType L4D2_GetEntityGlow_Type(int entity) +{ + return view_as(GetEntProp(entity, Prop_Send, "m_iGlowType")); +} + +/** + * Returns entity glow range. + * + * @param entity Entity index. + * @return Glow range. + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock int L4D2_GetEntityGlow_Range(int entity) +{ + return GetEntProp(entity, Prop_Send, "m_nGlowRange"); +} + +/** + * Returns entity glow min range. + * + * @param entity Entity index. + * @return Glow min range. + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock int L4D2_GetEntityGlow_MinRange(int entity) +{ + return GetEntProp(entity, Prop_Send, "m_nGlowRangeMin"); +} + +/** + * Returns entity glow flashing state. + * + * @param entity Entity index. + * @return Glow flashing state. + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock bool L4D2_GetEntityGlow_Flashing(int entity) +{ + return view_as(GetEntProp(entity, Prop_Send, "m_bFlashing")); +} + +/** + * Removes entity glow. + * + * @param entity Entity index. + * @return True if glow was removed, false if entity does not + * support glow. + */ +// L4D2 only. +stock bool L4D2_RemoveEntityGlow(int entity) +{ + return view_as(L4D2_SetEntityGlow(entity, L4D2Glow_None, 0, 0, { 0, 0, 0 }, false)); +} + +/** + * Removes entity glow override color. + * + * Note: This only removes the override color and reset it to the default glow + * color. + * + * @param entity Entity index. + * @noreturn + * @error Invalid entity index or entity does not support glow. + */ +// L4D2 only. +stock void L4D2_RemoveEntityGlow_Color(int entity) +{ + L4D2_SetEntityGlow_Color(entity, { 0, 0, 0 }); +} + +/** + * Whether survivor glow for player is enabled. + * + * @param client Client index. + * @return True if survivor glow is enabled, false otherwise. + * @error Invalid client index. + */ +// L4D2 only. +stock bool L4D2_IsPlayerSurvivorGlowEnable(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_bSurvivorGlowEnabled")); +} + +/** + * Set survivor glow state for player. + * + * @param client Client index. + * @param enabled Whether survivor glow is enabled. + * @noreturn + * @error Invalid client index. + */ +// L4D2 only. +stock void L4D2_SetPlayerSurvivorGlowState(int client, bool enabled) +{ + SetEntProp(client, Prop_Send, "m_bSurvivorGlowEnabled", enabled); +} + +/** + * Return player current revive count. + * + * @param client Client index. + * @return Survivor's current revive count. + * @error Invalid client index. + */ +stock int L4D_GetPlayerReviveCount(int client) +{ + return GetEntProp(client, Prop_Send, "m_currentReviveCount"); +} + +/** + * Set player revive count. + * + * @param client Client index. + * @param count Revive count. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerReviveCount(int client, int count) +{ + SetEntProp(client, Prop_Send, "m_currentReviveCount", count); +} + +/** + * Return player intensity. + * + * Note: Its percentage. 0.0 - Player is calm, 1.0 - Player is stressed. + * + * @param client Client index. + * @return Intensity. + * @error Invalid client index. + */ +stock float L4D_GetPlayerIntensity(int client) +{ + /* This format is used to keep consistency with the Director which also + * uses 0.0 for calm and 1.0 for stressed */ + return float(GetEntProp(client, Prop_Send, "m_clientIntensity")) / 100.0; +} + +/** + * Returns average survivor intensity. + * + * Note: Its percentage. 0.0 - All survivors is calm, 1.0 - All survivors is stressed. + * + * @return Average intensity level for survivors. + */ +stock float L4D_GetAvgSurvivorIntensity() +{ + int intensityTotal = 0; + int intensityMaxTotal = 0; + + for (int client = 1; client <= MaxClients; client++) + { + if (!IsClientInGame(client) || + view_as(GetClientTeam(client)) != L4DTeam_Survivor || + !IsPlayerAlive(client) || + GetClientHealth(client) < 1) + { + continue; + } + + intensityMaxTotal += 100; + intensityTotal += GetEntProp(client, Prop_Send, "m_clientIntensity"); + } + + /* This format is used to keep consistency with the Director which also uses 0.0 for calm and 1.0 for stressed */ + return float(intensityTotal) / float(intensityMaxTotal); +} + +/** + * Set player intensity. + * + * Note: Its percentage. 0.0 - Player is calm, 1.0 - Player is stressed. + * + * @param client Client index. + * @param intensity Intensity. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerIntensity(int client, float intensity) +{ + SetEntProp(client, Prop_Send, "m_clientIntensity", RoundToNearest(intensity * 100.0)); +} + +/** + * Returns whether player is calm. + * + * Note: Player is calm means that the player have not taken damage or + * fired their weapon for a while. Survivor bots always return false. + * + * @param client Client index. + * @return True if player is calm, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_IsPlayerCalm(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_isCalm")); +} + +/** + * Set player is calm state. + * + * Note: Player is calm means that the player have not taken damage or fired their weapon for a while. + * + * @param client Client index. + * @param isCalm Whether player is calm. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerCalmState(int client, bool isCalm) +{ + SetEntProp(client, Prop_Send, "m_isCalm", isCalm); +} + +/** + * Returns whether player has visible threats. + * + * Note: This function should work for all players. Survivors looking upon + * specials, witch or tank will be marked as has visible threats. However + * looking at commons will not be seen as has visible threats. The common has + * to be awaken from its slumber before beings seen as a threat. + * + * @parma client Client index. + * @return True if player has visible threats, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_HasVisibleThreats(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_hasVisibleThreats")); +} + +/** + * Returns whether player is on third strike. + * + * @param client Client index. + * @return True if on third strike, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_IsPlayerOnThirdStrike(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_bIsOnThirdStrike")); +} + +/** + * Set player third strike state. + * + * @param client Client index. + * @param onThirdStrike Whether survivor is on third strike. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerThirdStrikeState(int client, bool onThirdStrike) +{ + SetEntProp(client, Prop_Send, "m_bIsOnThirdStrike", onThirdStrike); +} + +/** + * Returns whether player is going to die. + * + * Note: This is not the same as is player on third strike. While on third + * strike defines whether player should die next time they get incapacitated, + * this defines whether the survivor should limp when they hit 1hp and make + * the character vocalize their "I dont think I'm gonna make it" lines. + * + * @param client Client index. + * @return True if player is going to die, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_IsPlayerGoingToDie(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_isGoingToDie")); +} + +/** + * Set player is going to die state. + * + * @param client Client index. + * @param isGoingToDie Whether player is going to die. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerIsGoingToDie(int client, bool isGoingToDie) +{ + SetEntProp(client, Prop_Send, "m_isGoingToDie", isGoingToDie); +} + +/** + * Returns whether weapon is upgrade compatible. + * + * @param weapon Weapon entity index. + * @return True if compatible with upgrades, false otherwise. + * @error Invalid entity index. + */ +stock bool L4D2_IsWeaponUpgradeCompatible(int weapon) +{ + char netclass[128]; + GetEntityNetClass(weapon, netclass, sizeof(netclass)); + return FindSendPropInfo(netclass, "m_upgradeBitVec") > 0; +} + +/** + * Returns upgraded ammo count for weapon. + * + * @param weapon Weapon entity index. + * @return Upgraded ammo count. + * @error Invalid entity index. + */ +// L4D2 only. +stock int L4D2_GetWeaponUpgradeAmmoCount(int weapon) +{ + return HasEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded") && GetEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded"); +} + +/** + * Set upgraded ammo count in weapon. + * + * @param weapon Weapon entity index. + * @param count Upgraded ammo count. + * @noreturn + * @error Invalid entity index. + */ +// L4D2 only. +stock void L4D2_SetWeaponUpgradeAmmoCount(int weapon, int count) +{ + if( HasEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded") ) + { + SetEntProp(weapon, Prop_Send, "m_nUpgradedPrimaryAmmoLoaded", count); + } +} + +/** + * Returns weapon upgrades of weapon. + * + * @param weapon Weapon entity index. + * @return Weapon upgrade bits. + * @error Invalid entity index. + */ +// L4D2 only. +stock int L4D2_GetWeaponUpgrades(int weapon) +{ + return HasEntProp(weapon, Prop_Send, "m_upgradeBitVec") && GetEntProp(weapon, Prop_Send, "m_upgradeBitVec"); +} + +/** + * Set weapon upgrades for weapon. + * + * @param weapon Weapon entity index. + * @param upgrades Weapon upgrade bits. + * @noreturn + * @error Invalid entity index. + */ +// L4D2 only. +stock void L4D2_SetWeaponUpgrades(int weapon, int upgrades) +{ + if( HasEntProp(weapon, Prop_Send, "m_upgradeBitVec") ) + { + SetEntProp(weapon, Prop_Send, "m_upgradeBitVec", upgrades); + } +} + +/** + * Returns infected attacker of survivor victim. + * + * Note: Infected attacker means the infected player that is currently + * pinning down the survivor. Such as hunter, smoker, charger and jockey. + * + * @param client Survivor client index. + * @return Infected attacker index, -1 if not found. + * @error Invalid client index. + */ +// L4D2 only. +stock int L4D2_GetInfectedAttacker(int client) +{ + int attacker; + + /* Charger */ + attacker = GetEntPropEnt(client, Prop_Send, "m_pummelAttacker"); + if (attacker > 0) + { + return attacker; + } + + attacker = GetEntPropEnt(client, Prop_Send, "m_carryAttacker"); + if (attacker > 0) + { + return attacker; + } + + /* Hunter */ + attacker = GetEntPropEnt(client, Prop_Send, "m_pounceAttacker"); + if (attacker > 0) + { + return attacker; + } + + /* Smoker */ + attacker = GetEntPropEnt(client, Prop_Send, "m_tongueOwner"); + if (attacker > 0) + { + return attacker; + } + + /* Jockey */ + attacker = GetEntPropEnt(client, Prop_Send, "m_jockeyAttacker"); + if (attacker > 0) + { + return attacker; + } + + return -1; +} + +/** + * Returns survivor victim of infected attacker. + * + * Note: Survivor victim means the survivor player that is currently pinned + * down by an attacker. Such as hunter, smoker, charger and jockey. + * + * @param client Infected client index. + * @return Survivor victim index, -1 if not found. + * @error Invalid client index. + */ +// L4D2 only. +stock int L4D2_GetSurvivorVictim(int client) +{ + int victim; + + /* Charger */ + victim = GetEntPropEnt(client, Prop_Send, "m_pummelVictim"); + if (victim > 0) + { + return victim; + } + + victim = GetEntPropEnt(client, Prop_Send, "m_carryVictim"); + if (victim > 0) + { + return victim; + } + + /* Hunter */ + victim = GetEntPropEnt(client, Prop_Send, "m_pounceVictim"); + if (victim > 0) + { + return victim; + } + + /* Smoker */ + victim = GetEntPropEnt(client, Prop_Send, "m_tongueVictim"); + if (victim > 0) + { + return victim; + } + + /* Jockey */ + victim = GetEntPropEnt(client, Prop_Send, "m_jockeyVictim"); + if (victim > 0) + { + return victim; + } + + return -1; +} + +/** + * Returns whether survivor client was present at survival start. + * + * @param client Client index. + * @return True if survivor was present at survival start, false otherwise. + * @error Invalid client index. + */ +// L4D2 only. +stock bool L4D2_WasPresentAtSurvivalStart(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_bWasPresentAtSurvivalStart")); +} + +/** + * Sets whether player was present at survival start. + * + * @param client Client index. + * @param wasPresent Whether survivor was present at survival start. + * @noreturn + * @error Invalid client index. + */ +// L4D2 only. +stock void L4D2_SetPresentAtSurvivalStart(int client, bool wasPresent) +{ + SetEntProp(client, Prop_Send, "m_bWasPresentAtSurvivalStart", wasPresent); +} + +/** + * Returns whether player is using a mounted weapon. + * + * @param client Client index. + * @return True if using a mounted weapon, false otherwise. + * @error Invalid client index. + */ +stock bool L4D_IsPlayerUsingMountedWeapon(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_usingMountedWeapon")); +} + +/** + * Returns player temporarily health. + * + * Note: This will not work with mutations or campaigns that alters the decay + * rate through vscript'ing. If you want to be sure that it works no matter + * the mutation, you will have to detour the OnGetScriptValueFloat function. + * Doing so you are able to capture the altered decay rate and calculate the + * temp health the same way as this function does. + * + * @param client Client index. + * @return Player's temporarily health, -1 if unable to get. + * @error Invalid client index or unable to find pain_pills_decay_rate cvar. + */ +stock int L4D_GetPlayerTempHealth(int client) +{ + static ConVar painPillsDecayCvar; + if (painPillsDecayCvar == null) + { + painPillsDecayCvar = FindConVar("pain_pills_decay_rate"); + if (painPillsDecayCvar == null) + { + return -1; + } + } + + int tempHealth = RoundToCeil(GetEntPropFloat(client, Prop_Send, "m_healthBuffer") - ((GetGameTime() - GetEntPropFloat(client, Prop_Send, "m_healthBufferTime")) * painPillsDecayCvar.FloatValue)) - 1; + return tempHealth < 0 ? 0 : tempHealth; +} + +/** + * Set players temporarily health. + * + * @param client Client index. + * @param tempHealth Temporarily health. + * @noreturn + * @error Invalid client index. + */ +stock void L4D_SetPlayerTempHealth(int client, int tempHealth) +{ + SetEntPropFloat(client, Prop_Send, "m_healthBuffer", float(tempHealth)); + SetEntPropFloat(client, Prop_Send, "m_healthBufferTime", GetGameTime()); +} + +/** + * Returns player use action. + * + * @param client Client index. + * @return Use action. + * @error Invalid client index. + */ +// L4D2 only. +stock L4D2UseAction L4D2_GetPlayerUseAction(int client) +{ + return view_as(GetEntProp(client, Prop_Send, "m_iCurrentUseAction")); +} + +/** + * Returns player use action target. + * + * @param client Client index. + * @return Entity index. + * @error Invalid client index. + */ +// L4D2 only. +stock int L4D2_GetPlayerUseActionTarget(int client) +{ + return GetEntPropEnt(client, Prop_Send, "m_useActionTarget"); +} + +/** + * Returns player use action owner. + * + * @param client Client index. + * @return Entity index. + * @error Invalid client index. + */ +// L4D2 only. +stock int L4D2_GetPlayerUseActionOwner(int client) +{ + return GetEntPropEnt(client, Prop_Send, "m_useActionOwner"); +} + +/** + * Creates an instructor hint. + * + * Note: Both infected and survivor players will see hint. No more than one at + * a time can be shown. The "newest" hint will override the old no matter the + * timeout and range. This instructor hint will not be shown if the given + * player is dead. + * + * @param name Instructor hint name. + * @param target Entity index of target. + * @param caption Caption of hint. + * @param color Color of the caption. RGB format. + * @param iconOnScreen Icon when hint is on screen. + * @param iconOffScreen Icon when hint is off screen. + * @param binding Key binding to show. + * @param iconOffset Height offset for icon from target entity's origin. + * @param range Display range of hint. 0 for unlimited range. + * @param timeout Timeout out for hint. 0 will persist until stopped with L4D2_EndInstructorHint. + * @param allowNoDrawTarget Whether hint will work with targets that have nodraw set. + * @param noOffScreen Whether when hint is off screen it will show an arrow pointing to target. + * @param forceCaption Whether the hint and icon will show even when occluded a wall. + * @param flags Instructor hint bit flags. See L4D2_IHFLAG_* defines. + * @return True if send, false otherwise. + */ +// L4D2 only. +stock bool L4D2_CreateInstructorHint(const char[] name, + int target = 0, + const char[] caption, + int color[3] = {255, 255, 255}, + const char[] iconOnScreen = "icon_tip", + const char[] iconOffScreen = "icon_tip", + const char[] binding = "", + float iconOffset = 0.0, + float range = 0.0, + int timeout = 0, + bool allowNoDrawTarget = true, + bool noOffScreen = false, + bool forceCaption = false, + int flags = L4D2_IHFLAG_STATIC) +{ + Event event = CreateEvent("instructor_server_hint_create", true); + if (event == null) + { + return false; + } + + char finalizedColor[16]; + Format(finalizedColor, 16, "%d,%d,%d", color[0], color[1], color[2]); + + event.SetString("hint_name", name); + event.SetInt("hint_target", target); + event.SetString("hint_caption", caption); + event.SetString("hint_color", finalizedColor); + event.SetString("hint_icon_onscreen", iconOnScreen); + event.SetString("hint_icon_offscreen", iconOffScreen); + event.SetString("hint_binding", binding); + event.SetFloat("hint_icon_offset", iconOffset); + event.SetFloat("hint_range", range); + event.SetInt("hint_timeout", timeout); + event.SetBool("hint_allow_nodraw_target", allowNoDrawTarget); + event.SetBool("hint_nooffscreen", noOffScreen); + event.SetBool("hint_forcecaption", forceCaption); + event.SetInt("hint_flags", flags); + event.Fire(); + return true; +} + +/** + * Stops all instructor hints with name. + * + * @param name Name of instructor hint to stop. + * @return True if send, false otherwise. + */ +// L4D2 only. +stock bool L4D2_StopInstructorHint(const char[] name) +{ + Event event = CreateEvent("instructor_server_hint_stop", true); + if (event == null) + { + return false; + } + + event.SetString("hint_name", name); + event.Fire(); + return true; +} + +/** + * Returns whether shotgun needs to be pumped. + * + * @parma weapon Weapon entity index. + * @return True if shotgun needs to be pumped, false otherwise. + */ +// L4D1 only. +stock int L4D1_GetShotgunNeedPump(int weapon) +{ + return HasEntProp(weapon, Prop_Send, "m_needPump") && GetEntProp(weapon, Prop_Send, "m_needPump"); +} + +/** + * Sets whether shotgun needs to be pumped. + * + * @parma weapon Weapon entity index. + * @parma needPump Whether shotgun needs to be pumped. + * @noreturn + */ +// L4D1 only. +stock void L4D1_SetShotgunNeedPump(int weapon, bool needPump) +{ + if( HasEntProp(weapon, Prop_Send, "m_needPump") ) + { + SetEntProp(weapon, Prop_Send, "m_needPump", view_as(needPump)); + } +} + +/** + * Sets custom ability cooldown of client. + * + * Note: Used for the Infected class abilities. + * + * @param client Client index. + * @param time How long before client can use their custom ability. + * @return True if set, false if no ability found. + */ +// L4D2 only. +stock bool L4D2_SetCustomAbilityCooldown(int client, float time) +{ + int ability = GetEntPropEnt(client, Prop_Send, "m_customAbility"); + if (ability > 0 && IsValidEdict(ability)) + { + SetEntPropFloat(ability, Prop_Send, "m_duration", time); + SetEntPropFloat(ability, Prop_Send, "m_timestamp", GetGameTime() + time); + return true; + } + return false; +} + + + + + +// ==================================================================================================== +// WEAPON STOCKS: "l4d_weapon_stocks.inc" by "Mr. Zero" +// ==================================================================================================== + +/* Credits to ProdigySim for the weapon details, models and original script */ + +#if defined _l4d_weapon_stocks_included + #endinput +#endif +#define _l4d_weapon_stocks_included + +#include +#include +#include + +enum L4D2WeaponId +{ + L4D2WeaponId_None, // 0 + L4D2WeaponId_Pistol, // 1 + L4D2WeaponId_Smg, // 2 + L4D2WeaponId_Pumpshotgun, // 3 + L4D2WeaponId_Autoshotgun, // 4 + L4D2WeaponId_Rifle, // 5 + L4D2WeaponId_HuntingRifle, // 6 + L4D2WeaponId_SmgSilenced, // 7 + L4D2WeaponId_ShotgunChrome, // 8 + L4D2WeaponId_RifleDesert, // 9 + L4D2WeaponId_SniperMilitary, // 10 + L4D2WeaponId_ShotgunSpas, // 11 + L4D2WeaponId_FirstAidKit, // 12 + L4D2WeaponId_Molotov, // 13 + L4D2WeaponId_PipeBomb, // 14 + L4D2WeaponId_PainPills, // 15 + L4D2WeaponId_Gascan, // 16 + L4D2WeaponId_PropaneTank, // 17 + L4D2WeaponId_OxygenTank, // 18 + L4D2WeaponId_Melee, // 19 + L4D2WeaponId_Chainsaw, // 20 + L4D2WeaponId_GrenadeLauncher, // 21 + L4D2WeaponId_AmmoPack, // 22 + L4D2WeaponId_Adrenaline, // 23 + L4D2WeaponId_Defibrillator, // 24 + L4D2WeaponId_Vomitjar, // 25 + L4D2WeaponId_RifleAK47, // 26 + L4D2WeaponId_GnomeChompski, // 27 + L4D2WeaponId_ColaBottles, // 28 + L4D2WeaponId_FireworksBox, // 29 + L4D2WeaponId_IncendiaryAmmo, // 30 + L4D2WeaponId_FragAmmo, // 31 + L4D2WeaponId_PistolMagnum, // 32 + L4D2WeaponId_SmgMP5, // 33 + L4D2WeaponId_RifleSG552, // 34 + L4D2WeaponId_SniperAWP, // 35 + L4D2WeaponId_SniperScout, // 36 + L4D2WeaponId_RifleM60, // 37 + L4D2WeaponId_TankClaw, // 38 + L4D2WeaponId_HunterClaw, // 39 + L4D2WeaponId_ChargerClaw, // 40 + L4D2WeaponId_BoomerClaw, // 41 + L4D2WeaponId_SmokerClaw, // 42 + L4D2WeaponId_SpitterClaw, // 43 + L4D2WeaponId_JockeyClaw, // 44 + L4D2WeaponId_Machinegun, // 45 + L4D2WeaponId_FatalVomit, // 46 + L4D2WeaponId_ExplodingSplat, // 47 + L4D2WeaponId_LungePounce, // 48 + L4D2WeaponId_Lounge, // 49 + L4D2WeaponId_FullPull, // 50 + L4D2WeaponId_Choke, // 51 + L4D2WeaponId_ThrowingRock, // 52 + L4D2WeaponId_TurboPhysics, // 53 + L4D2WeaponId_Ammo, // 54 + L4D2WeaponId_UpgradeItem, // 55 + L4D2WeaponId_MAX +}; + +static const char L4D2WeaponName[L4D2WeaponId][] = +{ + "weapon_none", // 0 + "weapon_pistol", // 1 + "weapon_smg", // 2 + "weapon_pumpshotgun", // 3 + "weapon_autoshotgun", // 4 + "weapon_rifle", // 5 + "weapon_hunting_rifle", // 6 + "weapon_smg_silenced", // 7 + "weapon_shotgun_chrome", // 8 + "weapon_rifle_desert", // 9 + "weapon_sniper_military", // 10 + "weapon_shotgun_spas", // 11 + "weapon_first_aid_kit", // 12 + "weapon_molotov", // 13 + "weapon_pipe_bomb", // 14 + "weapon_pain_pills", // 15 + "weapon_gascan", // 16 + "weapon_propanetank", // 17 + "weapon_oxygentank", // 18 + "weapon_melee", // 19 + "weapon_chainsaw", // 20 + "weapon_grenade_launcher", // 21 + "weapon_ammo_pack", // 22 + "weapon_adrenaline", // 23 + "weapon_defibrillator", // 24 + "weapon_vomitjar", // 25 + "weapon_rifle_ak47", // 26 + "weapon_gnome", // 27 + "weapon_cola_bottles", // 28 + "weapon_fireworkcrate", // 29 + "weapon_upgradepack_incendiary", // 30 + "weapon_upgradepack_explosive", // 31 + "weapon_pistol_magnum", // 32 + "weapon_smg_mp5", // 33 + "weapon_rifle_sg552", // 34 + "weapon_sniper_awp", // 35 + "weapon_sniper_scout", // 36 + "weapon_rifle_m60", // 37 + "weapon_tank_claw", // 38 + "weapon_hunter_claw", // 39 + "weapon_charger_claw", // 40 + "weapon_boomer_claw", // 41 + "weapon_smoker_claw", // 42 + "weapon_spitter_claw", // 43 + "weapon_jockey_claw", // 44 + "weapon_machinegun", // 45 + "vomit", // 46 + "splat", // 47 + "pounce", // 48 + "lounge", // 49 + "pull", // 50 + "choke", // 51 + "rock", // 52 + "physics", // 53 + "weapon_ammo", // 54 + "upgrade_item", // 55 + "" +}; + +static const char L4D2WeaponWorldModel[L4D2WeaponId][] = +{ + "", + "/w_models/weapons/w_pistol_b.mdl", + "/w_models/weapons/w_smg_uzi.mdl", + "/w_models/weapons/w_shotgun.mdl", + "/w_models/weapons/w_autoshot_m4super.mdl", + "/w_models/weapons/w_rifle_m16a2.mdl", + "/w_models/weapons/w_sniper_mini14.mdl", + "/w_models/weapons/w_smg_a.mdl", + "/w_models/weapons/w_pumpshotgun_a.mdl", + "/w_models/weapons/w_desert_rifle.mdl", // "/w_models/weapons/w_rifle_b.mdl" + "/w_models/weapons/w_sniper_military.mdl", + "/w_models/weapons/w_shotgun_spas.mdl", + "/w_models/weapons/w_eq_medkit.mdl", + "/w_models/weapons/w_eq_molotov.mdl", + "/w_models/weapons/w_eq_pipebomb.mdl", + "/w_models/weapons/w_eq_painpills.mdl", + "/props_junk/gascan001a.mdl", + "/props_junk/propanecanister001.mdl", + "/props_equipment/oxygentank01.mdl", + "", // "/weapons/w_knife_t.mdl", + // "/weapons/melee/w_bat.mdl", + // "/weapons/melee/w_chainsaw.mdl + // "/weapons/melee/w_cricket_bat.mdl", + // "/weapons/melee/w_crowbar.mdl", + // "/weapons/melee/w_didgeridoo.mdl", + // "/weapons/melee/w_electric_guitar.mdl", + // "/weapons/melee/w_fireaxe.mdl", + // "/weapons/melee/w_frying_pan.mdl", + // "/weapons/melee/w_golfclub.mdl", + // "/weapons/melee/w_katana.mdl", + // "/weapons/melee/w_machete.mdl", + // "/weapons/melee/w_riotshield.mdl", + // "/weapons/melee/w_tonfa.mdl" + "/weapons/melee/w_chainsaw.mdl", + "/w_models/weapons/w_grenade_launcher.mdl", + "", + "/w_models/weapons/w_eq_adrenaline.mdl", + "/w_models/weapons/w_eq_defibrillator.mdl", + "/w_models/weapons/w_eq_bile_flask.mdl", + "/w_models/weapons/w_rifle_ak47.mdl", + "/props_junk/gnome.mdl", + "/w_models/weapons/w_cola.mdl", + "/props_junk/explosive_box001.mdl", + "/w_models/weapons/w_eq_incendiary_ammopack.mdl", + "/w_models/weapons/w_eq_explosive_ammopack.mdl", + "/w_models/weapons/w_desert_eagle.mdl", + "/w_models/weapons/w_smg_mp5.mdl", + "/w_models/weapons/w_rifle_sg552.mdl", + "/w_models/weapons/w_sniper_awp.mdl", + "/w_models/weapons/w_sniper_scout.mdl", + "/w_models/weapons/w_m60.mdl", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" +}; + +StringMap g_hWeaponNameTrie; + +/** + * Returns whether weapon id is valid. + * + * @param weaponId Weapon id to check for validity. + * @return True if weapon id is valid, false otherwise. + */ +// L4D2 only. +stock bool L4D2_IsValidWeaponId(L4D2WeaponId weaponId) +{ + return weaponId >= L4D2WeaponId_None && weaponId < L4D2WeaponId_MAX; +} + +/** + * Returns whether weapon name is a valid weapon. + * + * @param weaponName Weapon name to check for validity. + * @return True if weapon name is valid, false otherwise. + */ +// L4D2 only. +stock bool L4D2_IsValidWeaponName(const char[] weaponName) +{ + return L4D2_GetWeaponIdByWeaponName(weaponName) != L4D2WeaponId_None; +} + +/** + * Checks to see if a given weapon id has a known WeaponModel in this file's model array. + * + * Note: The melee weapon have multiple valid models. This function will + * return false for melee weapon. + * + * @param weaponId Weapon id to check for a known weapon model for. + * @return True if a valid weapon model exists for weapon id, false otherwise. + */ +// L4D2 only. +stock bool L4D2_HasValidWeaponWorldModel(L4D2WeaponId weaponId) +{ + return L4D2WeaponWorldModel[weaponId][0] != '\0'; +} + +/** + * Returns weapon world model by weapon id. + * + * @note Does not work with melee weapons. + * + * @param weaponId Weapon id. + * @param dest Destination string buffer to copy to. + * @param destlen Destination buffer length (includes null terminator). + * @return Number of cells written. + */ +// L4D2 only. +stock int L4D2_GetWeaponModelByWeaponId(L4D2WeaponId weaponId, char[] dest, int destlen) +{ + if (!L4D2_IsValidWeaponId(weaponId)) + { + return 0; + } + + return strcopy(dest, destlen, L4D2WeaponWorldModel[weaponId]); +} + +/** + * Returns weapon id by weapon world model. + * + * @note Does not work with melee weapons. + * + * @param model Weapon world model. + * @return Weapon Id. + */ +// L4D2 only. +stock L4D2WeaponId L4D2_GetWeaponIdByWeaponModel(const char[] model) +{ + for (int i = 0; i < sizeof(L4D2WeaponWorldModel); i++) + { + if (strcmp(model, L4D2WeaponWorldModel[i], false) == 0) + { + return view_as(i); + } + } + + return L4D2WeaponId_None; +} + +/** + * Returns weapon id by weapon name. + * + * @param weaponName Weapon name to get id from. + * @return The corresponding weapon id if found, else L4D2WeaponId_None. + */ +// L4D2 only. +stock L4D2WeaponId L4D2_GetWeaponIdByWeaponName(const char[] weaponName) +{ + L4D2_InitWeaponNameTrie(); + L4D2WeaponId weaponId; + return g_hWeaponNameTrie.GetValue(weaponName, weaponId) ? weaponId : L4D2WeaponId_None; +} + +/** + * Returns weapon name by weapon id. + * + * @param weaponName Weapon id to get name from. + * @param dest Destination string buffer to copy to. + * @param destlen Destination buffer length (includes null terminator). + * @return Number of cells written. + */ +// L4D2 only. +stock int L4D2_GetWeaponNameByWeaponId(L4D2WeaponId weaponId, char[] dest, int destlen) +{ + if (!L4D2_IsValidWeaponId(weaponId)) + { + return 0; + } + + return strcopy(dest, destlen, L4D2WeaponName[weaponId]); +} + +/** + * Returns weapon id of entity. + * + * @param weapon Entity index of weapon. + * @return Weapon id if found, L4D2WeaponId_None otherwise. + * @error Invalid entity index. + */ +// L4D2 only. +stock L4D2WeaponId L4D2_GetWeaponId(int weapon) +{ + char classname[64]; + if (!GetEdictClassname(weapon, classname, sizeof(classname))) + { + return L4D2WeaponId_None; + } + + if (strcmp(classname, "weapon_spawn") == 0) + { + return view_as(GetEntProp(weapon, Prop_Send, "m_weaponID")); + } + + int len = strlen(classname); + if (len - 6 > 0 && strcmp(classname[len - 6], "_spawn") == 0) + { + classname[len - 6] = '\0'; + } + + return L4D2_GetWeaponIdByWeaponName(classname); +} + +/** + * Initialize the L4D2 weapon names trie. Not necessary to be executed, done by the functions that require the trie. + * + * @noreturn + */ +// L4D2 only. +stock void L4D2_InitWeaponNameTrie() +{ + if (g_hWeaponNameTrie != null) + { + return; + } + + g_hWeaponNameTrie = new StringMap(); + for (int i = 0; i < view_as(L4D2WeaponId_MAX); i++) + { + g_hWeaponNameTrie.SetValue(L4D2WeaponName[i], i); + } +} \ No newline at end of file