//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
# include "cbase.h"
# include "filesystem.h"
# include <KeyValues.h>
# include "particle_parse.h"
# include "particles/particles.h"
# ifdef GAME_DLL
# include "te_effect_dispatch.h"
# include "networkstringtable_gamedll.h"
# else
# include "c_te_effect_dispatch.h"
# include "networkstringtable_clientdll.h"
# endif
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
# define PARTICLES_MANIFEST_FILE "particles / particles_manifest.txt"
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int GetAttachTypeFromString ( const char * pszString )
{
if ( ! pszString | | ! pszString [ 0 ] )
return - 1 ;
// If you add new attach types, you need to add them to this list
static const char * pAttachmentNames [ MAX_PATTACH_TYPES ] =
{
" start_at_origin " , // PATTACH_ABSORIGIN = 0,
" follow_origin " , // PATTACH_ABSORIGIN_FOLLOW,
" start_at_customorigin " , // PATTACH_CUSTOMORIGIN,
" start_at_attachment " , // PATTACH_POINT,
" follow_attachment " , // PATTACH_POINT_FOLLOW,
" follow_rootbone " , // PATTACH_ROOTBONE_FOLLOW
} ;
for ( int i = 0 ; i < MAX_PATTACH_TYPES ; i + + )
{
if ( FStrEq ( pAttachmentNames [ i ] , pszString ) )
return i ;
}
return - 1 ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : list -
//-----------------------------------------------------------------------------
void GetParticleManifest ( CUtlVector < CUtlString > & list )
{
// Open the manifest file, and read the particles specified inside it
KeyValues * manifest = new KeyValues ( PARTICLES_MANIFEST_FILE ) ;
if ( manifest - > LoadFromFile ( filesystem , PARTICLES_MANIFEST_FILE , " GAME " ) )
{
for ( KeyValues * sub = manifest - > GetFirstSubKey ( ) ; sub ! = NULL ; sub = sub - > GetNextKey ( ) )
{
if ( ! Q_stricmp ( sub - > GetName ( ) , " file " ) )
{
list . AddToTail ( sub - > GetString ( ) ) ;
continue ;
}
Warning ( " CParticleMgr::Init: Manifest '%s' with bogus file type '%s', expecting 'file' \n " , PARTICLES_MANIFEST_FILE , sub - > GetName ( ) ) ;
}
}
else
{
Warning ( " PARTICLE SYSTEM: Unable to load manifest file '%s' \n " , PARTICLES_MANIFEST_FILE ) ;
}
manifest - > deleteThis ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ParseParticleEffects ( bool bLoadSheets , bool bPrecache )
{
MEM_ALLOC_CREDIT ( ) ;
g_pParticleSystemMgr - > ShouldLoadSheets ( bLoadSheets ) ;
CUtlVector < CUtlString > files ;
GetParticleManifest ( files ) ;
int nCount = files . Count ( ) ;
for ( int i = 0 ; i < nCount ; + + i )
{
g_pParticleSystemMgr - > ReadParticleConfigFile ( files [ i ] , bPrecache , false ) ;
}
g_pParticleSystemMgr - > DecommitTempMemory ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void ReloadParticleEffectsInList ( IFileList * pFilesToReload )
{
MEM_ALLOC_CREDIT ( ) ;
CUtlVector < CUtlString > files ;
GetParticleManifest ( files ) ;
// CAB 2/17/11 Reload all the particles regardless (Fixes filename change exploits).
bool bReloadAll = true ;
//int nCount = files.Count();
//for ( int i = 0; i < nCount; ++i )
//{
// // Skip the precache marker
// const char *pFile = files[i];
// if ( pFile[0] == '!' )
// {
// pFile++;
// }
// char szDX80Filename[MAX_PATH];
// V_strncpy( szDX80Filename, pFile, sizeof( szDX80Filename ) );
// V_StripExtension( pFile, szDX80Filename, sizeof( szDX80Filename ) );
// V_strncat( szDX80Filename, "_dx80.", sizeof( szDX80Filename ) );
// V_strncat( szDX80Filename, V_GetFileExtension( pFile ), sizeof( szDX80Filename ) );
// if ( pFilesToReload->IsFileInList( pFile ) || pFilesToReload->IsFileInList( szDX80Filename ) )
// {
// Msg( "Reloading all particle files due to pure settings.\n" );
// bReloadAll = true;
// break;
// }
//}
// Then check to see if we need to reload the map's particles
const char * pszMapName = NULL ;
# ifdef CLIENT_DLL
pszMapName = engine - > GetLevelName ( ) ;
# else
pszMapName = STRING ( gpGlobals - > mapname ) ;
# endif
if ( pszMapName & & pszMapName [ 0 ] )
{
char mapname [ MAX_MAP_NAME ] ;
Q_FileBase ( pszMapName , mapname , sizeof ( mapname ) ) ;
Q_strlower ( mapname ) ;
ParseParticleEffectsMap ( mapname , true , pFilesToReload ) ;
}
if ( bReloadAll )
{
ParseParticleEffects ( true , true ) ;
}
g_pParticleSystemMgr - > DecommitTempMemory ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: loads per-map manifest!
//-----------------------------------------------------------------------------
void ParseParticleEffectsMap ( const char * pMapName , bool bLoadSheets , IFileList * pFilesToReload )
{
MEM_ALLOC_CREDIT ( ) ;
CUtlVector < CUtlString > files ;
char szMapManifestFilename [ MAX_PATH ] ;
szMapManifestFilename [ 0 ] = NULL ;
if ( pMapName & & * pMapName )
{
V_snprintf ( szMapManifestFilename , sizeof ( szMapManifestFilename ) , " maps/%s_particles.txt " , pMapName ) ;
}
// Open the manifest file, and read the particles specified inside it
KeyValues * manifest = new KeyValues ( szMapManifestFilename ) ;
if ( manifest - > LoadFromFile ( filesystem , szMapManifestFilename , " GAME " ) )
{
DevMsg ( " Successfully loaded particle effects manifest '%s' for map '%s' \n " , szMapManifestFilename , pMapName ) ;
for ( KeyValues * sub = manifest - > GetFirstSubKey ( ) ; sub ! = NULL ; sub = sub - > GetNextKey ( ) )
{
if ( ! Q_stricmp ( sub - > GetName ( ) , " file " ) )
{
// Ensure the particles are in the particles directory
char szPath [ 512 ] ;
Q_strncpy ( szPath , sub - > GetString ( ) , sizeof ( szPath ) ) ;
Q_StripFilename ( szPath ) ;
char * pszPath = ( szPath [ 0 ] = = ' ! ' ) ? & szPath [ 1 ] : & szPath [ 0 ] ;
if ( pszPath & & pszPath [ 0 ] & & ! Q_stricmp ( pszPath , " particles " ) )
{
files . AddToTail ( sub - > GetString ( ) ) ;
continue ;
}
else
{
Warning ( " CParticleMgr::LevelInit: Manifest '%s' contains a particle file '%s' that's not under the particles directory. Custom particles must be placed in the particles directory. \n " , szMapManifestFilename , sub - > GetString ( ) ) ;
}
}
else
{
Warning ( " CParticleMgr::LevelInit: Manifest '%s' with bogus file type '%s', expecting 'file' \n " , szMapManifestFilename , sub - > GetName ( ) ) ;
}
}
}
else
{
// Don't print a warning, and don't proceed any further if the file doesn't exist!
return ;
}
int nCount = files . Count ( ) ;
if ( ! nCount )
{
return ;
}
g_pParticleSystemMgr - > ShouldLoadSheets ( bLoadSheets ) ;
for ( int i = 0 ; i < nCount ; + + i )
{
// If we've been given a list of particles to reload, only reload those.
if ( ! pFilesToReload | | ( pFilesToReload & & pFilesToReload - > IsFileInList ( files [ i ] ) ) )
{
g_pParticleSystemMgr - > ReadParticleConfigFile ( files [ i ] , true , true ) ;
}
}
g_pParticleSystemMgr - > DecommitTempMemory ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void PrecacheStandardParticleSystems ( )
{
# ifdef GAME_DLL
// Now add each particle system name to the network string pool, so we can send string_t's
// down to the client instead of full particle system names.
for ( int i = 0 ; i < g_pParticleSystemMgr - > GetParticleSystemCount ( ) ; i + + )
{
const char * pParticleSystemName = g_pParticleSystemMgr - > GetParticleSystemNameFromIndex ( i ) ;
CParticleSystemDefinition * pParticleSystem = g_pParticleSystemMgr - > FindParticleSystem ( pParticleSystemName ) ;
if ( pParticleSystem - > ShouldAlwaysPrecache ( ) )
{
PrecacheParticleSystem ( pParticleSystemName ) ;
}
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( const char * pszParticleName , ParticleAttachment_t iAttachType , CBaseEntity * pEntity , const char * pszAttachmentName , bool bResetAllParticlesOnEntity )
{
int iAttachment = - 1 ;
if ( pEntity & & pEntity - > GetBaseAnimating ( ) )
{
// Find the attachment point index
iAttachment = pEntity - > GetBaseAnimating ( ) - > LookupAttachment ( pszAttachmentName ) ;
if ( iAttachment < = 0 )
{
Warning ( " Model '%s' doesn't have attachment '%s' to attach particle system '%s' to. \n " , STRING ( pEntity - > GetBaseAnimating ( ) - > GetModelName ( ) ) , pszAttachmentName , pszParticleName ) ;
return ;
}
}
DispatchParticleEffect ( pszParticleName , iAttachType , pEntity , iAttachment , bResetAllParticlesOnEntity ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( const char * pszParticleName , ParticleAttachment_t iAttachType , CBaseEntity * pEntity , int iAttachmentPoint , bool bResetAllParticlesOnEntity )
{
CEffectData data ;
data . m_nHitBox = GetParticleSystemIndex ( pszParticleName ) ;
if ( pEntity )
{
# ifdef CLIENT_DLL
data . m_hEntity = pEntity ;
# else
data . m_nEntIndex = pEntity - > entindex ( ) ;
# endif
data . m_fFlags | = PARTICLE_DISPATCH_FROM_ENTITY ;
data . m_vOrigin = pEntity - > GetAbsOrigin ( ) ;
}
data . m_nDamageType = iAttachType ;
data . m_nAttachmentIndex = iAttachmentPoint ;
if ( bResetAllParticlesOnEntity )
{
data . m_fFlags | = PARTICLE_DISPATCH_RESET_PARTICLES ;
}
# ifdef GAME_DLL
if ( ( data . m_fFlags & PARTICLE_DISPATCH_FROM_ENTITY ) ! = 0 & &
( iAttachType = = PATTACH_ABSORIGIN_FOLLOW | | iAttachType = = PATTACH_POINT_FOLLOW | | iAttachType = = PATTACH_ROOTBONE_FOLLOW ) )
{
CBroadcastRecipientFilter filter ;
DispatchEffect ( " ParticleEffect " , data , filter ) ;
}
else
# endif
{
DispatchEffect ( " ParticleEffect " , data ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( const char * pszParticleName , ParticleAttachment_t iAttachType , CBaseEntity * pEntity , const char * pszAttachmentName , Vector vecColor1 , Vector vecColor2 , bool bUseColors , bool bResetAllParticlesOnEntity )
{
int iAttachment = - 1 ;
if ( pEntity & & pEntity - > GetBaseAnimating ( ) )
{
// Find the attachment point index
iAttachment = pEntity - > GetBaseAnimating ( ) - > LookupAttachment ( pszAttachmentName ) ;
if ( iAttachment < = 0 )
{
Warning ( " Model '%s' doesn't have attachment '%s' to attach particle system '%s' to. \n " , STRING ( pEntity - > GetBaseAnimating ( ) - > GetModelName ( ) ) , pszAttachmentName , pszParticleName ) ;
return ;
}
}
CEffectData data ;
data . m_nHitBox = GetParticleSystemIndex ( pszParticleName ) ;
if ( pEntity )
{
# ifdef CLIENT_DLL
data . m_hEntity = pEntity ;
# else
data . m_nEntIndex = pEntity - > entindex ( ) ;
# endif
data . m_fFlags | = PARTICLE_DISPATCH_FROM_ENTITY ;
data . m_vOrigin = pEntity - > GetAbsOrigin ( ) ;
}
data . m_nDamageType = iAttachType ;
data . m_nAttachmentIndex = iAttachment ;
if ( bResetAllParticlesOnEntity )
{
data . m_fFlags | = PARTICLE_DISPATCH_RESET_PARTICLES ;
}
if ( bUseColors )
{
data . m_bCustomColors = true ;
data . m_CustomColors . m_vecColor1 = vecColor1 ;
data . m_CustomColors . m_vecColor2 = vecColor2 ;
}
# ifdef GAME_DLL
if ( ( data . m_fFlags & PARTICLE_DISPATCH_FROM_ENTITY ) ! = 0 & &
( iAttachType = = PATTACH_ABSORIGIN_FOLLOW | | iAttachType = = PATTACH_POINT_FOLLOW | | iAttachType = = PATTACH_ROOTBONE_FOLLOW ) )
{
CReliableBroadcastRecipientFilter filter ;
DispatchEffect ( " ParticleEffect " , data , filter ) ;
}
else
# endif
{
DispatchEffect ( " ParticleEffect " , data ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( int iEffectIndex , Vector vecOrigin , Vector vecStart , QAngle vecAngles , CBaseEntity * pEntity )
{
CEffectData data ;
data . m_nHitBox = iEffectIndex ;
data . m_vOrigin = vecOrigin ;
data . m_vStart = vecStart ;
data . m_vAngles = vecAngles ;
if ( pEntity )
{
# ifdef CLIENT_DLL
data . m_hEntity = pEntity ;
# else
data . m_nEntIndex = pEntity - > entindex ( ) ;
# endif
data . m_fFlags | = PARTICLE_DISPATCH_FROM_ENTITY ;
data . m_nDamageType = PATTACH_CUSTOMORIGIN ;
}
else
{
# ifdef CLIENT_DLL
data . m_hEntity = NULL ;
# else
data . m_nEntIndex = 0 ;
# endif
}
DispatchEffect ( " ParticleEffect " , data ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( const char * pszParticleName , Vector vecOrigin , QAngle vecAngles , Vector vecColor1 , Vector vecColor2 , bool bUseColors , CBaseEntity * pEntity , int iAttachType )
{
int iEffectIndex = GetParticleSystemIndex ( pszParticleName ) ;
CEffectData data ;
data . m_nHitBox = iEffectIndex ;
data . m_vOrigin = vecOrigin ;
data . m_vAngles = vecAngles ;
if ( pEntity )
{
# ifdef CLIENT_DLL
data . m_hEntity = pEntity ;
# else
data . m_nEntIndex = pEntity - > entindex ( ) ;
# endif
data . m_fFlags | = PARTICLE_DISPATCH_FROM_ENTITY ;
data . m_nDamageType = PATTACH_CUSTOMORIGIN ;
}
else
{
# ifdef CLIENT_DLL
data . m_hEntity = NULL ;
# else
data . m_nEntIndex = 0 ;
# endif
}
if ( bUseColors )
{
data . m_bCustomColors = true ;
data . m_CustomColors . m_vecColor1 = vecColor1 ;
data . m_CustomColors . m_vecColor2 = vecColor2 ;
}
DispatchEffect ( " ParticleEffect " , data ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( const char * pszParticleName , Vector vecOrigin , QAngle vecAngles , CBaseEntity * pEntity )
{
int iIndex = GetParticleSystemIndex ( pszParticleName ) ;
DispatchParticleEffect ( iIndex , vecOrigin , vecOrigin , vecAngles , pEntity ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Yet another overload, lets us supply vecStart
//-----------------------------------------------------------------------------
void DispatchParticleEffect ( const char * pszParticleName , Vector vecOrigin , Vector vecStart , QAngle vecAngles , CBaseEntity * pEntity )
{
int iIndex = GetParticleSystemIndex ( pszParticleName ) ;
DispatchParticleEffect ( iIndex , vecOrigin , vecStart , vecAngles , pEntity ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void StopParticleEffects ( CBaseEntity * pEntity )
{
CEffectData data ;
if ( pEntity )
{
# ifdef CLIENT_DLL
data . m_hEntity = pEntity ;
# else
data . m_nEntIndex = pEntity - > entindex ( ) ;
# endif
}
# ifdef GAME_DLL
CReliableBroadcastRecipientFilter filter ;
DispatchEffect ( " ParticleEffectStop " , data , filter ) ;
# else
DispatchEffect ( " ParticleEffectStop " , data ) ;
# endif
}
# ifndef CLIENT_DLL
extern CBaseEntity * GetNextCommandEntity ( CBasePlayer * pPlayer , const char * name , CBaseEntity * ent ) ;
ConVar particle_test_file ( " particle_test_file " , " " , FCVAR_CHEAT , " Name of the particle system to dynamically spawn " ) ;
ConVar particle_test_attach_mode ( " particle_test_attach_mode " , " follow_attachment " , FCVAR_CHEAT , " Possible Values: 'start_at_attachment', 'follow_attachment', 'start_at_origin', 'follow_origin' " ) ;
ConVar particle_test_attach_attachment ( " particle_test_attach_attachment " , " 0 " , FCVAR_CHEAT , " Attachment index for attachment mode " ) ;
void Particle_Test_Start ( CBasePlayer * pPlayer , const char * name , bool bStart )
{
if ( ! pPlayer )
return ;
int iAttachType = GetAttachTypeFromString ( particle_test_attach_mode . GetString ( ) ) ;
if ( iAttachType < 0 )
{
Warning ( " Invalid attach type specified for particle_test in cvar 'particle_test_attach_mode. \n " ) ;
return ;
}
int iAttachmentIndex = particle_test_attach_attachment . GetInt ( ) ;
const char * pszParticleFile = particle_test_file . GetString ( ) ;
CBaseEntity * pEntity = NULL ;
while ( ( pEntity = GetNextCommandEntity ( pPlayer , name , pEntity ) ) ! = NULL )
{
/*
Fire the test particle system on this entity
*/
DispatchParticleEffect (
pszParticleFile ,
( ParticleAttachment_t ) iAttachType ,
pEntity ,
iAttachmentIndex ,
true ) ; // stops existing particle systems
}
}
void CC_Particle_Test_Start ( const CCommand & args )
{
Particle_Test_Start ( UTIL_GetCommandClient ( ) , args [ 1 ] , true ) ;
}
static ConCommand particle_test_start ( " particle_test_start " , CC_Particle_Test_Start , " Dispatches the test particle system with the parameters specified in particle_test_file, \n particle_test_attach_mode and particle_test_attach_param on the entity the player is looking at. \n \t Arguments: {entity_name} / {class_name} / no argument picks what player is looking at " , FCVAR_CHEAT ) ;
void Particle_Test_Stop ( CBasePlayer * pPlayer , const char * name , bool bStart )
{
if ( ! pPlayer )
return ;
CBaseEntity * pEntity = NULL ;
while ( ( pEntity = GetNextCommandEntity ( pPlayer , name , pEntity ) ) ! = NULL )
{
//Stop all particle systems on the selected entity
DispatchParticleEffect ( " " , PATTACH_ABSORIGIN , pEntity , 0 , true ) ;
}
}
void CC_Particle_Test_Stop ( const CCommand & args )
{
Particle_Test_Stop ( UTIL_GetCommandClient ( ) , args [ 1 ] , false ) ;
}
static ConCommand particle_test_stop ( " particle_test_stop " , CC_Particle_Test_Stop , " Stops all particle systems on the selected entities. \n \t Arguments: {entity_name} / {class_name} / no argument picks what player is looking at " , FCVAR_CHEAT ) ;
# endif //CLIENT_DLL