/** * Copyright (C) 2024 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 #pragma newdecls required #include #include #tryinclude #tryinclude #tryinclude #define LUX_LIBRARY_VERSION "0.5.8" #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; } if(GetEntProp(iClient, Prop_Send, "m_bAdrenalineActive", 1) < 1) return -1.0; //timerAddress + 8 = TimeStamp float flGameTime = GetGameTime(); float flTime = GetEntDataFloat(iClient, timerAddress + 8); if(flTime <= flGameTime) return -1.0; return flTime - flGameTime; } /** * Calls CTerrorPlayer::OnRevivedByDefibrillator() * * @param iRevivee Client index be revived. * @param iReviver Client index who revived can be same as revivee. * @param iDeathModel Death model index, dead survivor model (survivor_death_model). * * @return True if revive was successful false otherwise. * @error Invalid entity index or invalid attachment name, * signature for function not found, or SDKCall failed. **/ stock bool Terror_ReviveDeathModel(int iRevivee, int iReviver, int iDeathModel) { static Handle hSDKCall; if(hSDKCall == null) { Handle hGamedata; GetGameData(hGamedata); StartPrepSDKCall(SDKCall_Player); if(PrepSDKCall_SetFromConf(hGamedata, SDKConf_Signature, "CTerrorPlayer::OnRevivedByDefibrillator")) { PrepSDKCall_AddParameter(SDKType_CBasePlayer, SDKPass_Pointer); PrepSDKCall_AddParameter(SDKType_CBaseEntity, SDKPass_Pointer); hSDKCall = EndPrepSDKCall(); if(hSDKCall == null) LogError("Unable to prep 'CTerrorPlayer::OnRevivedByDefibrillator'"); } else { LogError("Error finding the 'CTerrorPlayer::OnRevivedByDefibrillator' signature."); } delete hGamedata; if(hSDKCall == null) return false; } SDKCall(hSDKCall, iRevivee, iReviver, iDeathModel); return true; } /** * Create a physics explosion that does not affect players. * * @param vecOrigin Origin of the explosion. * @param iMagnitude Magnitude of the explosion limit of 100, explode more than once for more power. * @param flRadius Radius of the explosion. * @param bDamage True to damage props, false otherwise. * @param flInnerRadius If not zero, the LOS is calculated from a point intersecting this sphere. * * @error Failed to create explosion. **/ stock void PhysicsExplode(float vecOrigin[3], int iMagnitude, float flRadius, bool bDamage=false, float flInnerRadius=0.0) { 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", "8"); } else { DispatchKeyValue(iBoom, "spawnflags", "9"); } 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", flInnerRadius); 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 void TE_SetupDynamicLight(float vecOrigin[3], int RGB[3], float flRadius, float flTime, float flDecay=0.0, int exponent=0) { 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); } /** * 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; } } ///////////////////////////////////////Sound /** * level boost in some games maybe clamed, e.g. l4d max seems to be 150 * pitch is clamed between 1-200, this maybe different between games. * sndChannel static sound wont be replaced, however if overflowed it wont play any sound until static sounds have finished. * * @param sample sound file path. * @param origin origin to emit sound from. * @param entity entity to emit sound from. * @param level sound level attenuation, the wav sound it's self matters. * @param pitch sound pitch. * @param sndChannel sound channel. * @param rangeMin players within the min range sound wont be mixed. * @param rangeCurve range curve until max mix can be achieved. * @param levelBoost add level boost to mixed sounds max rangeCurve will apply levelBoost, half will apply half levelBoost. * @param exponent exponent value to multiply, logarithmic. * * @error Invalid client index. **/ stock void EmitMixedAmbientSoundToAll(const char[] sample, const float origin[3] = NULL_VECTOR, int entity = SOUND_FROM_PLAYER, int level = SNDLEVEL_NORMAL, int pitch = SNDPITCH_NORMAL, int sndChannel = SNDCHAN_AUTO, float rangeMin, float rangeCurve=1500.0, int levelBoost=0, float exponent=1.0) { for(int i = 1; i <= MaxClients; ++i) { if(!IsClientInGame(i) || IsFakeClient(i)) continue; EmitMixedAmbientSound(i, sample, origin, entity, level, pitch, sndChannel, rangeMin, rangeCurve, levelBoost, exponent); } } /** * level boost in some games maybe clamed, e.g. l4d max seems to be 150 * pitch is clamed between 1-200, this maybe different between games. * sndChannel static sound wont be replaced, however if overflowed it wont play any sound until static sounds have finished. * * @param sample sound file path. * @param origin origin to emit sound from. * @param entity entity to emit sound from. * @param level sound level attenuation, the wav sound it's self matters. * @param pitch sound pitch. * @param sndChannel sound channel. * @param rangeMin players within the min range sound wont be mixed. * @param rangeCurve range curve until max mix can be achieved, sample2 is played when 2x this value. * @param levelBoost add level boost to mixed sounds max rangeCurve will apply levelBoost, half will apply half levelBoost. * @param exponent exponent value to multiply, logarithmic. * @param sample2 sound file path. * @param level2 sound level attenuation. * @param pitch2 sound pitch. * * @error Invalid client index. **/ stock void EmitMixedAmbientSoundToAll_FallBack(const char[] sample, const float origin[3] = NULL_VECTOR, int entity = SOUND_FROM_PLAYER, int level = SNDLEVEL_NORMAL, int pitch = SNDPITCH_NORMAL, int sndChannel = SNDCHAN_AUTO, float rangeMin, float rangeCurve=1500.0, int levelBoost=0, float exponent=1.0, const char[] sample2, int level2 = SNDLEVEL_NORMAL, int pitch2 = SNDPITCH_NORMAL) { for(int i = 1; i <= MaxClients; ++i) { if(!IsClientInGame(i) || IsFakeClient(i)) continue; EmitMixedAmbientSound_FallBack(i, sample, origin, entity, level, pitch, sndChannel, rangeMin, rangeCurve, levelBoost, exponent, sample2, level2, pitch2); } } /** * level boost in some games maybe clamed, e.g. l4d max seems to be 150 * pitch is clamed between 1-200, this maybe different between games. * sndChannel static sound wont be replaced, however if overflowed it wont play any sound until static sounds have finished. * * @param client client index. * @param sample sound file path. * @param origin origin to emit sound from. * @param entity entity to emit sound from. * @param level sound level attenuation, the wav sound it's self matters. * @param pitch sound pitch. * @param sndChannel sound channel. * @param rangeMin players within the min range sound wont be mixed. * @param rangeCurve range curve until max mix can be achieved. * @param levelBoost add level boost to mixed sounds max rangeCurve will apply levelBoost, half will apply half levelBoost. * @param exponent exponent value to multiply, logarithmic. * * @error Invalid client index. **/ stock void EmitMixedAmbientSound(int client, const char[] sample, const float origin[3] = NULL_VECTOR, int entity = SOUND_FROM_PLAYER, int level = SNDLEVEL_NORMAL, int pitch = SNDPITCH_NORMAL, int sndChannel = SNDCHAN_AUTO, float rangeMin, float rangeCurve=1500.0, int levelBoost=0, float exponent=1.0) { static float vecEyePos[3]; int newPitch; float flDist; float flDistPercent; float flDistMulti; int DistLevelBoost; int viewEnt = -1; viewEnt = GetEntPropEnt(client, Prop_Send, "m_hViewEntity"); if(viewEnt > 0) { GetAbsOrigin(viewEnt, vecEyePos); } else { GetClientEyePosition(client, vecEyePos); } flDist = GetVectorDistance(origin, vecEyePos); if(rangeCurve == 0.0) { LogError("RangeCurve == 0.0"); return; } flDist = (flDist - rangeMin < 0.0 ? 0.0 : flDist - rangeMin); flDistPercent = (flDist / rangeCurve); flDistMulti = (flDistPercent * 2) * exponent; newPitch = pitch; if(flDistMulti > 1.0) { newPitch = RoundToNearest(newPitch / (flDistMulti > 2.0 ? 2.0 : flDistMulti)); } DistLevelBoost = RoundToNearest((flDistPercent * exponent) * levelBoost); if(DistLevelBoost > levelBoost) DistLevelBoost = levelBoost; EmitSoundToClient(client, sample, entity, sndChannel, level + DistLevelBoost, _, _, newPitch, _, origin); } /** * level boost in some games maybe clamed, e.g. l4d max seems to be 150 * pitch is clamed between 1-200, this maybe different between games. * sndChannel static sound wont be replaced, however if overflowed it wont play any sound until static sounds have finished. * * @param client client index. * @param sample sound file path. * @param origin origin to emit sound from. * @param entity entity to emit sound from. * @param level sound level attenuation, the wav sound it's self matters. * @param pitch sound pitch. * @param sndChannel sound channel. * @param rangeMin players within the min range sound wont be mixed. * @param rangeCurve range curve until max mix can be achieved, sample2 is played when 2x this value. * @param levelBoost add level boost to mixed sounds max rangeCurve will apply levelBoost, half will apply half levelBoost. * @param exponent exponent value to multiply, logarithmic. * @param sample2 sound file path. * @param level2 sound level attenuation. * @param pitch2 sound pitch. * * @error Invalid client index. **/ stock void EmitMixedAmbientSound_FallBack(int client, const char[] sample, const float origin[3] = NULL_VECTOR, int entity = SOUND_FROM_PLAYER, int level = SNDLEVEL_NORMAL, int pitch = SNDPITCH_NORMAL, int sndChannel = SNDCHAN_AUTO, float rangeMin, float rangeCurve=1500.0, int levelBoost=0, float exponent=1.0, const char[] sample2, int level2 = SNDLEVEL_NORMAL, int pitch2 = SNDPITCH_NORMAL) { static float vecEyePos[3]; int newPitch; float flDist; float flDistPercent; float flDistMulti; int DistLevelBoost; int viewEnt = -1; viewEnt = GetEntPropEnt(client, Prop_Send, "m_hViewEntity"); if(viewEnt > 0) { GetAbsOrigin(viewEnt, vecEyePos); } else { GetClientEyePosition(client, vecEyePos); } flDist = GetVectorDistance(origin, vecEyePos); if(rangeCurve == 0.0) { LogError("RangeCurve == 0.0"); return; } flDist = (flDist - rangeMin < 0.0 ? 0.0 : flDist - rangeMin); flDistPercent = (flDist / rangeCurve); flDistMulti = (flDistPercent * 2) * exponent; if(flDistMulti >= 2.0) { EmitSoundToClient(client, sample2, entity, sndChannel, level2, _, _, pitch2, _, origin); return; } newPitch = pitch; if(flDistMulti > 1.0) { newPitch = RoundToNearest(newPitch / (flDistMulti > 2.0 ? 2.0 : flDistMulti)); } DistLevelBoost = RoundToNearest((flDistPercent * exponent) * levelBoost); if(DistLevelBoost > levelBoost) DistLevelBoost = levelBoost; EmitSoundToClient(client, sample, entity, sndChannel, level + DistLevelBoost, _, _, newPitch, _, origin); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////Legacy Particle Stock/////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Creates a tempent particle. * * @param iParticleIndex Particle index location in the "ParticleEffectNames" stringtable. * @param iEntIndex Entity index to attach particle to. * @param fDelay Delay for the TE_SendToAll function. * @param SendToAll True to send to all clients, false otherwise. You must call the send function yourself if sending to specific clients. * @param sParticleName Name of the particle to find the index with. Only used if the particle index is invalid. * @param iAttachmentIndex Attachment index of the particle. Decompile the model to retrieve this value. * @param fParticleAngles Angles of the particle. Usually effects particles that have no gravity. * @param iFlags Flags of the particle. Note: A value of "1" is required for attachment points and damage types. * @param iDamageType The damage type of the particle. (Used in impact effect dispatches and attachment points need to be set to use.) * @param fMagnitude The magnitude of the particle. (Needs testing; used in pipe bomb blast.) * @param fScale The scale of the particle (doesn't apply to most particles). (Needs testing.) * * @return True on success, false on failure. * @error Invalid effect index or invalid particle stringtable index. **/ #pragma deprecated Used for backwards compatibility. stock bool L4D_TE_Create_Particle(float fParticleStartPos[3]={0.0, 0.0, 0.0}, float fParticleEndPos[3]={0.0, 0.0, 0.0}, int iParticleIndex=-1, int iEntIndex=0, float fDelay=0.0, bool SendToAll=true, char sParticleName[64]="", int iAttachmentIndex=0, float fParticleAngles[3]={0.0, 0.0, 0.0}, int iFlags=0, int iDamageType=0, float fMagnitude=0.0, float fScale=1.0, float fRadius=0.0) { TE_Start("EffectDispatch"); static EngineVersion IsEngine; if(IsEngine == Engine_Unknown) IsEngine = GetEngineVersion(); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vOrigin[0]", fParticleStartPos[0]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vOrigin[1]", fParticleStartPos[1]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vOrigin[2]", fParticleStartPos[2]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vStart[0]", fParticleEndPos[0]);//end point usually for bulletparticles or ropes TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vStart[1]", fParticleEndPos[1]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vStart[2]", fParticleEndPos[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); if(iParticleIndex < 0) { static int iParticleStringIndex = INVALID_STRING_INDEX; iParticleStringIndex = __FindStringIndex2(FindStringTable("ParticleEffectNames"), sParticleName); if(iParticleStringIndex == INVALID_STRING_INDEX) return false; TE_WriteNum("m_nHitBox", iParticleStringIndex); } else TE_WriteNum("m_nHitBox", iParticleIndex); TE_WriteNum("entindex", iEntIndex); TE_WriteNum("m_nAttachmentIndex", iAttachmentIndex); TE_WriteVector("m_vAngles", fParticleAngles); TE_WriteNum("m_fFlags", iFlags); TE_WriteFloat("m_flMagnitude", fMagnitude);// saw this being used in pipebomb needs testing what it does probs shaking screen? TE_WriteFloat("m_flScale", fScale); TE_WriteFloat("m_flRadius", fRadius);// saw this being used in pipebomb needs testing what it does probs shaking screen? TE_WriteNum("m_nDamageType", iDamageType);// this shit is required dunno why for attachpoint emitting valve probs named it wrong if(SendToAll) TE_SendToAll(fDelay); return true; } /** * Stops a tempent particle. * * @param fParticleStartPos Starting position of the particle. * @param fParticleEndPos Ending position of the particle. * @param iParticleIndex Particle index location in the "ParticleEffectNames" stringtable. * @param iEntIndex Entity index to attach particle to. * @param fDelay Delay for the TE_SendToAll function. * @param SendToAll True to send to all clients, false otherwise. You must call the send function yourself if sending to specific clients. * @param sParticleName Name of the particle to find the index with. Only used if the particle index is invalid. * @param iAttachmentIndex Attachment index of the particle. Decompile the model to retrieve this value. * @param fParticleAngles Angles of the particle. Usually effects particles that have no gravity. * @param iFlags Flags of the particle. * @param iDamageType The damage type of the particle. (Used in impact effect dispatches and attachment points need to be set to use.) * @param fMagnitude The magnitude of the particle. (Needs testing; used in pipe bomb blast.) * @param fScale The scale of the particle (doesn't apply to most particles). (Needs testing.) * @param fRadius The radius of the particle. * * @return True on success, false on failure. * @error Invalid effect index or invalid particle stringtable index. **/ #pragma deprecated Used only for backwards compatibility. stock bool L4D_TE_Stop_Particle(float fParticleStartPos[3]={0.0, 0.0, 0.0}, float fParticleEndPos[3]={0.0, 0.0, 0.0}, int iParticleIndex=-1, int iEntIndex=0, float fDelay=0.0, bool SendToAll=true, char sParticleName[64]="", int iAttachmentIndex=0, float fParticleAngles[3]={0.0, 0.0, 0.0}, int iFlags=0, int iDamageType=0, float fMagnitude=0.0, float fScale=1.0, float fRadius=0.0) { TE_Start("EffectDispatch"); static EngineVersion IsEngine; if(IsEngine == Engine_Unknown) IsEngine = GetEngineVersion(); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.x" :"m_vStart[0]", fParticleStartPos[0]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.y" :"m_vStart[1]", fParticleStartPos[1]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vOrigin.z" :"m_vStart[2]", fParticleStartPos[2]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.x" :"m_vOrigin[0]", fParticleEndPos[0]);//end point usually for bulletparticles or ropes TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.y" :"m_vOrigin[1]", fParticleEndPos[1]); TE_WriteFloat(IsEngine == Engine_Left4Dead2 ? "m_vStart.z" :"m_vOrigin[2]", fParticleEndPos[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); if(iParticleIndex < 0) { static int iParticleStringIndex = INVALID_STRING_INDEX; iParticleStringIndex = __FindStringIndex2(FindStringTable("ParticleEffectNames"), sParticleName); if(iParticleStringIndex == INVALID_STRING_INDEX) return false; TE_WriteNum("m_nHitBox", iParticleStringIndex); } else TE_WriteNum("m_nHitBox", iParticleIndex); TE_WriteNum("entindex", iEntIndex); TE_WriteNum("m_nAttachmentIndex", iAttachmentIndex); TE_WriteVector("m_vAngles", fParticleAngles); TE_WriteNum("m_fFlags", iFlags); TE_WriteFloat("m_flMagnitude", fMagnitude);// saw this being used in pipebomb needs testing what it does probs shaking screen? TE_WriteFloat("m_flScale", fScale); TE_WriteFloat("m_flRadius", fRadius);// saw this being used in pipebomb needs testing what it does probs shaking screen? TE_WriteNum("m_nDamageType", iDamageType);// this shit is required dunno why for attachpoint emitting valve probs named it wrong if(SendToAll) TE_SendToAll(fDelay); return true; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////