Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

603 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "cbase.h"
  7. #include "filesystem.h"
  8. #include <KeyValues.h>
  9. #include "particle_parse.h"
  10. #include "particles/particles.h"
  11. #ifdef GAME_DLL
  12. #include "te_effect_dispatch.h"
  13. #include "networkstringtable_gamedll.h"
  14. #else
  15. #include "c_te_effect_dispatch.h"
  16. #include "networkstringtable_clientdll.h"
  17. #endif
  18. // memdbgon must be the last include file in a .cpp file!!!
  19. #include "tier0/memdbgon.h"
  20. #define PARTICLES_MANIFEST_FILE "particles/particles_manifest.txt"
  21. //-----------------------------------------------------------------------------
  22. // Purpose:
  23. //-----------------------------------------------------------------------------
  24. int GetAttachTypeFromString( const char *pszString )
  25. {
  26. if ( !pszString || !pszString[0] )
  27. return -1;
  28. // If you add new attach types, you need to add them to this list
  29. static const char *pAttachmentNames[MAX_PATTACH_TYPES] =
  30. {
  31. "start_at_origin", // PATTACH_ABSORIGIN = 0,
  32. "follow_origin", // PATTACH_ABSORIGIN_FOLLOW,
  33. "start_at_customorigin",// PATTACH_CUSTOMORIGIN,
  34. "start_at_attachment", // PATTACH_POINT,
  35. "follow_attachment", // PATTACH_POINT_FOLLOW,
  36. "follow_rootbone", // PATTACH_ROOTBONE_FOLLOW
  37. };
  38. for ( int i = 0; i < MAX_PATTACH_TYPES; i++ )
  39. {
  40. if ( FStrEq( pAttachmentNames[i], pszString ) )
  41. return i;
  42. }
  43. return -1;
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Purpose:
  47. // Input : list -
  48. //-----------------------------------------------------------------------------
  49. void GetParticleManifest( CUtlVector<CUtlString>& list )
  50. {
  51. // Open the manifest file, and read the particles specified inside it
  52. KeyValues *manifest = new KeyValues( PARTICLES_MANIFEST_FILE );
  53. if ( manifest->LoadFromFile( filesystem, PARTICLES_MANIFEST_FILE, "GAME" ) )
  54. {
  55. for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
  56. {
  57. if ( !Q_stricmp( sub->GetName(), "file" ) )
  58. {
  59. list.AddToTail( sub->GetString() );
  60. continue;
  61. }
  62. Warning( "CParticleMgr::Init: Manifest '%s' with bogus file type '%s', expecting 'file'\n", PARTICLES_MANIFEST_FILE, sub->GetName() );
  63. }
  64. }
  65. else
  66. {
  67. Warning( "PARTICLE SYSTEM: Unable to load manifest file '%s'\n", PARTICLES_MANIFEST_FILE );
  68. }
  69. manifest->deleteThis();
  70. }
  71. //-----------------------------------------------------------------------------
  72. // Purpose:
  73. //-----------------------------------------------------------------------------
  74. void ParseParticleEffects( bool bLoadSheets, bool bPrecache )
  75. {
  76. MEM_ALLOC_CREDIT();
  77. g_pParticleSystemMgr->ShouldLoadSheets( bLoadSheets );
  78. CUtlVector<CUtlString> files;
  79. GetParticleManifest( files );
  80. int nCount = files.Count();
  81. for ( int i = 0; i < nCount; ++i )
  82. {
  83. g_pParticleSystemMgr->ReadParticleConfigFile( files[i], bPrecache, false );
  84. }
  85. g_pParticleSystemMgr->DecommitTempMemory();
  86. }
  87. //-----------------------------------------------------------------------------
  88. // Purpose:
  89. //-----------------------------------------------------------------------------
  90. void ReloadParticleEffectsInList( IFileList *pFilesToReload )
  91. {
  92. MEM_ALLOC_CREDIT();
  93. CUtlVector<CUtlString> files;
  94. GetParticleManifest( files );
  95. // CAB 2/17/11 Reload all the particles regardless (Fixes filename change exploits).
  96. bool bReloadAll = true;
  97. //int nCount = files.Count();
  98. //for ( int i = 0; i < nCount; ++i )
  99. //{
  100. // // Skip the precache marker
  101. // const char *pFile = files[i];
  102. // if ( pFile[0] == '!' )
  103. // {
  104. // pFile++;
  105. // }
  106. // char szDX80Filename[MAX_PATH];
  107. // V_strncpy( szDX80Filename, pFile, sizeof( szDX80Filename ) );
  108. // V_StripExtension( pFile, szDX80Filename, sizeof( szDX80Filename ) );
  109. // V_strncat( szDX80Filename, "_dx80.", sizeof( szDX80Filename ) );
  110. // V_strncat( szDX80Filename, V_GetFileExtension( pFile ), sizeof( szDX80Filename ) );
  111. // if ( pFilesToReload->IsFileInList( pFile ) || pFilesToReload->IsFileInList( szDX80Filename ) )
  112. // {
  113. // Msg( "Reloading all particle files due to pure settings.\n" );
  114. // bReloadAll = true;
  115. // break;
  116. // }
  117. //}
  118. // Then check to see if we need to reload the map's particles
  119. const char *pszMapName = NULL;
  120. #ifdef CLIENT_DLL
  121. pszMapName = engine->GetLevelName();
  122. #else
  123. pszMapName = STRING( gpGlobals->mapname );
  124. #endif
  125. if ( pszMapName && pszMapName[0] )
  126. {
  127. char mapname[MAX_MAP_NAME];
  128. Q_FileBase( pszMapName, mapname, sizeof( mapname ) );
  129. Q_strlower( mapname );
  130. ParseParticleEffectsMap( mapname, true, pFilesToReload );
  131. }
  132. if ( bReloadAll )
  133. {
  134. ParseParticleEffects( true, true );
  135. }
  136. g_pParticleSystemMgr->DecommitTempMemory();
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose: loads per-map manifest!
  140. //-----------------------------------------------------------------------------
  141. void ParseParticleEffectsMap( const char *pMapName, bool bLoadSheets, IFileList *pFilesToReload )
  142. {
  143. MEM_ALLOC_CREDIT();
  144. CUtlVector<CUtlString> files;
  145. char szMapManifestFilename[MAX_PATH];
  146. szMapManifestFilename[0] = NULL;
  147. if ( pMapName && *pMapName )
  148. {
  149. V_snprintf( szMapManifestFilename, sizeof( szMapManifestFilename ), "maps/%s_particles.txt", pMapName );
  150. }
  151. // Open the manifest file, and read the particles specified inside it
  152. KeyValues *manifest = new KeyValues( szMapManifestFilename );
  153. if ( manifest->LoadFromFile( filesystem, szMapManifestFilename, "GAME" ) )
  154. {
  155. DevMsg( "Successfully loaded particle effects manifest '%s' for map '%s'\n", szMapManifestFilename, pMapName );
  156. for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
  157. {
  158. if ( !Q_stricmp( sub->GetName(), "file" ) )
  159. {
  160. // Ensure the particles are in the particles directory
  161. char szPath[ 512 ];
  162. Q_strncpy( szPath, sub->GetString(), sizeof( szPath ) );
  163. Q_StripFilename( szPath );
  164. char *pszPath = (szPath[0] == '!') ? &szPath[1] : &szPath[0];
  165. if ( pszPath && pszPath[0] && !Q_stricmp( pszPath, "particles" ) )
  166. {
  167. files.AddToTail( sub->GetString() );
  168. continue;
  169. }
  170. else
  171. {
  172. 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() );
  173. }
  174. }
  175. else
  176. {
  177. Warning( "CParticleMgr::LevelInit: Manifest '%s' with bogus file type '%s', expecting 'file'\n", szMapManifestFilename, sub->GetName() );
  178. }
  179. }
  180. }
  181. else
  182. {
  183. // Don't print a warning, and don't proceed any further if the file doesn't exist!
  184. return;
  185. }
  186. int nCount = files.Count();
  187. if ( !nCount )
  188. {
  189. return;
  190. }
  191. g_pParticleSystemMgr->ShouldLoadSheets( bLoadSheets );
  192. for ( int i = 0; i < nCount; ++i )
  193. {
  194. // If we've been given a list of particles to reload, only reload those.
  195. if ( !pFilesToReload || (pFilesToReload && pFilesToReload->IsFileInList( files[i] )) )
  196. {
  197. g_pParticleSystemMgr->ReadParticleConfigFile( files[i], true, true );
  198. }
  199. }
  200. g_pParticleSystemMgr->DecommitTempMemory();
  201. }
  202. //-----------------------------------------------------------------------------
  203. // Purpose:
  204. //-----------------------------------------------------------------------------
  205. void PrecacheStandardParticleSystems( )
  206. {
  207. #ifdef GAME_DLL
  208. // Now add each particle system name to the network string pool, so we can send string_t's
  209. // down to the client instead of full particle system names.
  210. for ( int i = 0; i < g_pParticleSystemMgr->GetParticleSystemCount(); i++ )
  211. {
  212. const char *pParticleSystemName = g_pParticleSystemMgr->GetParticleSystemNameFromIndex(i);
  213. CParticleSystemDefinition *pParticleSystem = g_pParticleSystemMgr->FindParticleSystem( pParticleSystemName );
  214. if ( pParticleSystem->ShouldAlwaysPrecache() )
  215. {
  216. PrecacheParticleSystem( pParticleSystemName );
  217. }
  218. }
  219. #endif
  220. }
  221. //-----------------------------------------------------------------------------
  222. // Purpose:
  223. //-----------------------------------------------------------------------------
  224. void DispatchParticleEffect( const char *pszParticleName, ParticleAttachment_t iAttachType, CBaseEntity *pEntity, const char *pszAttachmentName, bool bResetAllParticlesOnEntity )
  225. {
  226. int iAttachment = -1;
  227. if ( pEntity && pEntity->GetBaseAnimating() )
  228. {
  229. // Find the attachment point index
  230. iAttachment = pEntity->GetBaseAnimating()->LookupAttachment( pszAttachmentName );
  231. if ( iAttachment <= 0 )
  232. {
  233. Warning("Model '%s' doesn't have attachment '%s' to attach particle system '%s' to.\n", STRING(pEntity->GetBaseAnimating()->GetModelName()), pszAttachmentName, pszParticleName );
  234. return;
  235. }
  236. }
  237. DispatchParticleEffect( pszParticleName, iAttachType, pEntity, iAttachment, bResetAllParticlesOnEntity );
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Purpose:
  241. //-----------------------------------------------------------------------------
  242. void DispatchParticleEffect( const char *pszParticleName, ParticleAttachment_t iAttachType, CBaseEntity *pEntity, int iAttachmentPoint, bool bResetAllParticlesOnEntity )
  243. {
  244. CEffectData data;
  245. data.m_nHitBox = GetParticleSystemIndex( pszParticleName );
  246. if ( pEntity )
  247. {
  248. #ifdef CLIENT_DLL
  249. data.m_hEntity = pEntity;
  250. #else
  251. data.m_nEntIndex = pEntity->entindex();
  252. #endif
  253. data.m_fFlags |= PARTICLE_DISPATCH_FROM_ENTITY;
  254. data.m_vOrigin = pEntity->GetAbsOrigin();
  255. }
  256. data.m_nDamageType = iAttachType;
  257. data.m_nAttachmentIndex = iAttachmentPoint;
  258. if ( bResetAllParticlesOnEntity )
  259. {
  260. data.m_fFlags |= PARTICLE_DISPATCH_RESET_PARTICLES;
  261. }
  262. #ifdef GAME_DLL
  263. if ( ( data.m_fFlags & PARTICLE_DISPATCH_FROM_ENTITY ) != 0 &&
  264. ( iAttachType == PATTACH_ABSORIGIN_FOLLOW || iAttachType == PATTACH_POINT_FOLLOW || iAttachType == PATTACH_ROOTBONE_FOLLOW ) )
  265. {
  266. CBroadcastRecipientFilter filter;
  267. DispatchEffect( "ParticleEffect", data, filter );
  268. }
  269. else
  270. #endif
  271. {
  272. DispatchEffect( "ParticleEffect", data );
  273. }
  274. }
  275. //-----------------------------------------------------------------------------
  276. // Purpose:
  277. //-----------------------------------------------------------------------------
  278. void DispatchParticleEffect( const char *pszParticleName, ParticleAttachment_t iAttachType, CBaseEntity *pEntity, const char *pszAttachmentName, Vector vecColor1, Vector vecColor2, bool bUseColors, bool bResetAllParticlesOnEntity )
  279. {
  280. int iAttachment = -1;
  281. if ( pEntity && pEntity->GetBaseAnimating() )
  282. {
  283. // Find the attachment point index
  284. iAttachment = pEntity->GetBaseAnimating()->LookupAttachment( pszAttachmentName );
  285. if ( iAttachment <= 0 )
  286. {
  287. Warning("Model '%s' doesn't have attachment '%s' to attach particle system '%s' to.\n", STRING(pEntity->GetBaseAnimating()->GetModelName()), pszAttachmentName, pszParticleName );
  288. return;
  289. }
  290. }
  291. CEffectData data;
  292. data.m_nHitBox = GetParticleSystemIndex( pszParticleName );
  293. if ( pEntity )
  294. {
  295. #ifdef CLIENT_DLL
  296. data.m_hEntity = pEntity;
  297. #else
  298. data.m_nEntIndex = pEntity->entindex();
  299. #endif
  300. data.m_fFlags |= PARTICLE_DISPATCH_FROM_ENTITY;
  301. data.m_vOrigin = pEntity->GetAbsOrigin();
  302. }
  303. data.m_nDamageType = iAttachType;
  304. data.m_nAttachmentIndex = iAttachment;
  305. if ( bResetAllParticlesOnEntity )
  306. {
  307. data.m_fFlags |= PARTICLE_DISPATCH_RESET_PARTICLES;
  308. }
  309. if ( bUseColors )
  310. {
  311. data.m_bCustomColors = true;
  312. data.m_CustomColors.m_vecColor1 = vecColor1;
  313. data.m_CustomColors.m_vecColor2 = vecColor2;
  314. }
  315. #ifdef GAME_DLL
  316. if ( ( data.m_fFlags & PARTICLE_DISPATCH_FROM_ENTITY ) != 0 &&
  317. ( iAttachType == PATTACH_ABSORIGIN_FOLLOW || iAttachType == PATTACH_POINT_FOLLOW || iAttachType == PATTACH_ROOTBONE_FOLLOW ) )
  318. {
  319. CReliableBroadcastRecipientFilter filter;
  320. DispatchEffect( "ParticleEffect", data, filter );
  321. }
  322. else
  323. #endif
  324. {
  325. DispatchEffect( "ParticleEffect", data );
  326. }
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose:
  330. //-----------------------------------------------------------------------------
  331. void DispatchParticleEffect( int iEffectIndex, Vector vecOrigin, Vector vecStart, QAngle vecAngles, CBaseEntity *pEntity )
  332. {
  333. CEffectData data;
  334. data.m_nHitBox = iEffectIndex;
  335. data.m_vOrigin = vecOrigin;
  336. data.m_vStart = vecStart;
  337. data.m_vAngles = vecAngles;
  338. if ( pEntity )
  339. {
  340. #ifdef CLIENT_DLL
  341. data.m_hEntity = pEntity;
  342. #else
  343. data.m_nEntIndex = pEntity->entindex();
  344. #endif
  345. data.m_fFlags |= PARTICLE_DISPATCH_FROM_ENTITY;
  346. data.m_nDamageType = PATTACH_CUSTOMORIGIN;
  347. }
  348. else
  349. {
  350. #ifdef CLIENT_DLL
  351. data.m_hEntity = NULL;
  352. #else
  353. data.m_nEntIndex = 0;
  354. #endif
  355. }
  356. DispatchEffect( "ParticleEffect", data );
  357. }
  358. //-----------------------------------------------------------------------------
  359. // Purpose:
  360. //-----------------------------------------------------------------------------
  361. void DispatchParticleEffect( const char *pszParticleName, Vector vecOrigin, QAngle vecAngles, Vector vecColor1, Vector vecColor2, bool bUseColors, CBaseEntity *pEntity, int iAttachType )
  362. {
  363. int iEffectIndex = GetParticleSystemIndex( pszParticleName );
  364. CEffectData data;
  365. data.m_nHitBox = iEffectIndex;
  366. data.m_vOrigin = vecOrigin;
  367. data.m_vAngles = vecAngles;
  368. if ( pEntity )
  369. {
  370. #ifdef CLIENT_DLL
  371. data.m_hEntity = pEntity;
  372. #else
  373. data.m_nEntIndex = pEntity->entindex();
  374. #endif
  375. data.m_fFlags |= PARTICLE_DISPATCH_FROM_ENTITY;
  376. data.m_nDamageType = PATTACH_CUSTOMORIGIN;
  377. }
  378. else
  379. {
  380. #ifdef CLIENT_DLL
  381. data.m_hEntity = NULL;
  382. #else
  383. data.m_nEntIndex = 0;
  384. #endif
  385. }
  386. if ( bUseColors )
  387. {
  388. data.m_bCustomColors = true;
  389. data.m_CustomColors.m_vecColor1 = vecColor1;
  390. data.m_CustomColors.m_vecColor2 = vecColor2;
  391. }
  392. DispatchEffect( "ParticleEffect", data );
  393. }
  394. //-----------------------------------------------------------------------------
  395. // Purpose:
  396. //-----------------------------------------------------------------------------
  397. void DispatchParticleEffect( const char *pszParticleName, Vector vecOrigin, QAngle vecAngles, CBaseEntity *pEntity )
  398. {
  399. int iIndex = GetParticleSystemIndex( pszParticleName );
  400. DispatchParticleEffect( iIndex, vecOrigin, vecOrigin, vecAngles, pEntity );
  401. }
  402. //-----------------------------------------------------------------------------
  403. // Purpose: Yet another overload, lets us supply vecStart
  404. //-----------------------------------------------------------------------------
  405. void DispatchParticleEffect( const char *pszParticleName, Vector vecOrigin, Vector vecStart, QAngle vecAngles, CBaseEntity *pEntity )
  406. {
  407. int iIndex = GetParticleSystemIndex( pszParticleName );
  408. DispatchParticleEffect( iIndex, vecOrigin, vecStart, vecAngles, pEntity );
  409. }
  410. //-----------------------------------------------------------------------------
  411. // Purpose:
  412. //-----------------------------------------------------------------------------
  413. void StopParticleEffects( CBaseEntity *pEntity )
  414. {
  415. CEffectData data;
  416. if ( pEntity )
  417. {
  418. #ifdef CLIENT_DLL
  419. data.m_hEntity = pEntity;
  420. #else
  421. data.m_nEntIndex = pEntity->entindex();
  422. #endif
  423. }
  424. #ifdef GAME_DLL
  425. CReliableBroadcastRecipientFilter filter;
  426. DispatchEffect( "ParticleEffectStop", data, filter );
  427. #else
  428. DispatchEffect( "ParticleEffectStop", data );
  429. #endif
  430. }
  431. #ifndef CLIENT_DLL
  432. extern CBaseEntity *GetNextCommandEntity( CBasePlayer *pPlayer, const char *name, CBaseEntity *ent );
  433. ConVar particle_test_file( "particle_test_file", "", FCVAR_CHEAT, "Name of the particle system to dynamically spawn" );
  434. 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'" );
  435. ConVar particle_test_attach_attachment( "particle_test_attach_attachment", "0", FCVAR_CHEAT, "Attachment index for attachment mode" );
  436. void Particle_Test_Start( CBasePlayer* pPlayer, const char *name, bool bStart )
  437. {
  438. if ( !pPlayer )
  439. return;
  440. int iAttachType = GetAttachTypeFromString( particle_test_attach_mode.GetString() );
  441. if ( iAttachType < 0 )
  442. {
  443. Warning( "Invalid attach type specified for particle_test in cvar 'particle_test_attach_mode.\n" );
  444. return;
  445. }
  446. int iAttachmentIndex = particle_test_attach_attachment.GetInt();
  447. const char *pszParticleFile = particle_test_file.GetString();
  448. CBaseEntity *pEntity = NULL;
  449. while ( (pEntity = GetNextCommandEntity( pPlayer, name, pEntity )) != NULL )
  450. {
  451. /*
  452. Fire the test particle system on this entity
  453. */
  454. DispatchParticleEffect(
  455. pszParticleFile,
  456. (ParticleAttachment_t)iAttachType,
  457. pEntity,
  458. iAttachmentIndex,
  459. true ); // stops existing particle systems
  460. }
  461. }
  462. void CC_Particle_Test_Start( const CCommand& args )
  463. {
  464. Particle_Test_Start( UTIL_GetCommandClient(), args[1], true );
  465. }
  466. 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\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
  467. void Particle_Test_Stop( CBasePlayer* pPlayer, const char *name, bool bStart )
  468. {
  469. if ( !pPlayer )
  470. return;
  471. CBaseEntity *pEntity = NULL;
  472. while ( (pEntity = GetNextCommandEntity( pPlayer, name, pEntity )) != NULL )
  473. {
  474. //Stop all particle systems on the selected entity
  475. DispatchParticleEffect( "", PATTACH_ABSORIGIN, pEntity, 0, true );
  476. }
  477. }
  478. void CC_Particle_Test_Stop( const CCommand& args )
  479. {
  480. Particle_Test_Stop( UTIL_GetCommandClient(), args[1], false );
  481. }
  482. static ConCommand particle_test_stop("particle_test_stop", CC_Particle_Test_Stop, "Stops all particle systems on the selected entities.\n\tArguments: {entity_name} / {class_name} / no argument picks what player is looking at ", FCVAR_CHEAT);
  483. #endif //!CLIENT_DLL
  484. #if defined( CLIENT_DLL ) && defined( STAGING_ONLY )
  485. void CC_DispatchParticle( const CCommand& args )
  486. {
  487. C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();
  488. if ( !pLocalPlayer )
  489. return;
  490. if ( args.ArgC() < 2 )
  491. {
  492. DevMsg( "Use: dispatch_particle {particle_name} {surface_offset_distance}\n" );
  493. return;
  494. }
  495. float flSurfaceOffsetDistance = 0.f;
  496. if ( args.ArgC() == 3 )
  497. {
  498. flSurfaceOffsetDistance = atof( args[2] );
  499. }
  500. Vector vForward;
  501. pLocalPlayer->GetVectors( &vForward, NULL, NULL );
  502. trace_t tr;
  503. UTIL_TraceLine( pLocalPlayer->EyePosition(), pLocalPlayer->EyePosition() + vForward * 3000, MASK_SOLID_BRUSHONLY, NULL, &tr );
  504. Vector vTargetDeathPos = tr.endpos;
  505. DispatchParticleEffect( args[1], vTargetDeathPos + flSurfaceOffsetDistance * tr.plane.normal, vec3_angle );
  506. }
  507. static ConCommand dispatch_particle( "dispatch_particle", CC_DispatchParticle, "Dispatch specified particle effect 50 units away from the lookat surface normal.\n\tArguments: {particle_name} {surface_offset_distance}", FCVAR_CHEAT );
  508. #endif // CLIENT_DLL && STAGING_ONLY