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.

656 lines
21 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "cbase.h"
  7. #include "item_dynamic_resupply.h"
  8. #include "props.h"
  9. #include "items.h"
  10. #include "ammodef.h"
  11. // memdbgon must be the last include file in a .cpp file!!!
  12. #include "tier0/memdbgon.h"
  13. ConVar sk_dynamic_resupply_modifier( "sk_dynamic_resupply_modifier","1.0" );
  14. extern ConVar sk_battery;
  15. extern ConVar sk_healthkit;
  16. ConVar g_debug_dynamicresupplies( "g_debug_dynamicresupplies", "0", FCVAR_NONE, "Debug item_dynamic_resupply spawning. Set to 1 to see text printouts of the spawning. Set to 2 to see lines drawn to other items factored into the spawning." );
  17. struct DynamicResupplyItems_t
  18. {
  19. const char *sEntityName;
  20. const char *sAmmoDef;
  21. int iAmmoCount;
  22. float flFullProbability; // Probability of spawning if the player meeds all goals
  23. };
  24. struct SpawnInfo_t
  25. {
  26. float m_flDesiredRatio;
  27. float m_flCurrentRatio;
  28. float m_flDelta;
  29. int m_iPotentialItems;
  30. };
  31. // Health types
  32. static DynamicResupplyItems_t g_DynamicResupplyHealthItems[] =
  33. {
  34. { "item_healthkit", "Health", 0, 0.0f, },
  35. { "item_battery", "Armor", 0, 0.0f },
  36. };
  37. // Ammo types
  38. static DynamicResupplyItems_t g_DynamicResupplyAmmoItems[] =
  39. {
  40. { "item_ammo_pistol", "Pistol", SIZE_AMMO_PISTOL, 0.5f },
  41. { "item_ammo_smg1", "SMG1", SIZE_AMMO_SMG1, 0.4f },
  42. { "item_ammo_smg1_grenade", "SMG1_Grenade", SIZE_AMMO_SMG1_GRENADE, 0.0f },
  43. { "item_ammo_ar2", "AR2", SIZE_AMMO_AR2, 0.0f },
  44. { "item_box_buckshot", "Buckshot", SIZE_AMMO_BUCKSHOT, 0.0f },
  45. { "item_rpg_round", "RPG_Round", SIZE_AMMO_RPG_ROUND, 0.0f },
  46. { "weapon_frag", "Grenade", 1, 0.1f },
  47. { "item_ammo_357", "357", SIZE_AMMO_357, 0.0f },
  48. { "item_ammo_crossbow", "XBowBolt", SIZE_AMMO_CROSSBOW, 0.0f },
  49. { "item_ammo_ar2_altfire", "AR2AltFire", SIZE_AMMO_AR2_ALTFIRE, 0.0f },
  50. };
  51. #define DS_HEALTH_INDEX 0
  52. #define DS_ARMOR_INDEX 1
  53. #define DS_GRENADE_INDEX 6
  54. #define NUM_HEALTH_ITEMS (ARRAYSIZE(g_DynamicResupplyHealthItems))
  55. #define NUM_AMMO_ITEMS (ARRAYSIZE(g_DynamicResupplyAmmoItems))
  56. #define DYNAMIC_ITEM_THINK 1.0
  57. #define POTENTIAL_ITEM_RADIUS 1024
  58. //-----------------------------------------------------------------------------
  59. // Purpose: An item that dynamically decides what the player needs most and spawns that.
  60. //-----------------------------------------------------------------------------
  61. class CItem_DynamicResupply : public CPointEntity
  62. {
  63. DECLARE_CLASS( CItem_DynamicResupply, CPointEntity );
  64. public:
  65. DECLARE_DATADESC();
  66. CItem_DynamicResupply();
  67. void Spawn( void );
  68. void Precache( void );
  69. void Activate( void );
  70. void CheckPVSThink( void );
  71. // Inputs
  72. void InputKill( inputdata_t &data );
  73. void InputCalculateType( inputdata_t &data );
  74. void InputBecomeMaster( inputdata_t &data );
  75. float GetDesiredHealthPercentage( void ) const { return m_flDesiredHealth[0]; }
  76. private:
  77. friend void DynamicResupply_InitFromAlternateMaster( CBaseEntity *pTargetEnt, string_t iszMaster );
  78. void FindPotentialItems( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo );
  79. void ComputeHealthRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo );
  80. void ComputeAmmoRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo );
  81. bool SpawnItemFromRatio( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo, Vector *pVecSpawnOrigin );
  82. // Spawns an item when the player is full
  83. void SpawnFullItem( CItem_DynamicResupply *pMaster, CBasePlayer *pPlayer, int iDebug );
  84. void SpawnDynamicItem( CBasePlayer *pPlayer );
  85. enum Versions
  86. {
  87. VERSION_0,
  88. VERSION_1_PERSISTENT_MASTER,
  89. VERSION_CURRENT = VERSION_1_PERSISTENT_MASTER,
  90. };
  91. int m_version;
  92. float m_flDesiredHealth[ NUM_HEALTH_ITEMS ];
  93. float m_flDesiredAmmo[ NUM_AMMO_ITEMS ];
  94. bool m_bIsMaster;
  95. };
  96. LINK_ENTITY_TO_CLASS(item_dynamic_resupply, CItem_DynamicResupply);
  97. // Master
  98. typedef CHandle<CItem_DynamicResupply> DynamicResupplyHandle_t;
  99. static DynamicResupplyHandle_t g_MasterResupply;
  100. //-----------------------------------------------------------------------------
  101. // Save/load:
  102. //-----------------------------------------------------------------------------
  103. BEGIN_DATADESC( CItem_DynamicResupply )
  104. DEFINE_THINKFUNC( CheckPVSThink ),
  105. DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ),
  106. DEFINE_INPUTFUNC( FIELD_VOID, "CalculateType", InputCalculateType ),
  107. DEFINE_INPUTFUNC( FIELD_VOID, "BecomeMaster", InputBecomeMaster ),
  108. DEFINE_KEYFIELD( m_flDesiredHealth[0], FIELD_FLOAT, "DesiredHealth" ),
  109. DEFINE_KEYFIELD( m_flDesiredHealth[1], FIELD_FLOAT, "DesiredArmor" ),
  110. DEFINE_KEYFIELD( m_flDesiredAmmo[0], FIELD_FLOAT, "DesiredAmmoPistol" ),
  111. DEFINE_KEYFIELD( m_flDesiredAmmo[1], FIELD_FLOAT, "DesiredAmmoSMG1" ),
  112. DEFINE_KEYFIELD( m_flDesiredAmmo[2], FIELD_FLOAT, "DesiredAmmoSMG1_Grenade" ),
  113. DEFINE_KEYFIELD( m_flDesiredAmmo[3], FIELD_FLOAT, "DesiredAmmoAR2" ),
  114. DEFINE_KEYFIELD( m_flDesiredAmmo[4], FIELD_FLOAT, "DesiredAmmoBuckshot" ),
  115. DEFINE_KEYFIELD( m_flDesiredAmmo[5], FIELD_FLOAT, "DesiredAmmoRPG_Round" ),
  116. DEFINE_KEYFIELD( m_flDesiredAmmo[6], FIELD_FLOAT, "DesiredAmmoGrenade" ),
  117. DEFINE_KEYFIELD( m_flDesiredAmmo[7], FIELD_FLOAT, "DesiredAmmo357" ),
  118. DEFINE_KEYFIELD( m_flDesiredAmmo[8], FIELD_FLOAT, "DesiredAmmoCrossbow" ),
  119. DEFINE_KEYFIELD( m_flDesiredAmmo[9], FIELD_FLOAT, "DesiredAmmoAR2_AltFire" ),
  120. DEFINE_FIELD( m_version, FIELD_INTEGER ),
  121. DEFINE_FIELD( m_bIsMaster, FIELD_BOOLEAN ),
  122. // Silence, Classcheck!
  123. // DEFINE_ARRAY( m_flDesiredHealth, FIELD_FLOAT, NUM_HEALTH_ITEMS ),
  124. // DEFINE_ARRAY( m_flDesiredAmmo, FIELD_FLOAT, NUM_AMMO_ITEMS ),
  125. END_DATADESC()
  126. //-----------------------------------------------------------------------------
  127. // Purpose:
  128. //-----------------------------------------------------------------------------
  129. CItem_DynamicResupply::CItem_DynamicResupply( void )
  130. {
  131. AddSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER );
  132. m_version = VERSION_CURRENT;
  133. // Setup default values
  134. m_flDesiredHealth[0] = 1.0; // Health
  135. m_flDesiredHealth[1] = 0.3; // Armor
  136. m_flDesiredAmmo[0] = 0.5; // Pistol
  137. m_flDesiredAmmo[1] = 0.5; // SMG1
  138. m_flDesiredAmmo[2] = 0.1; // SMG1 Grenade
  139. m_flDesiredAmmo[3] = 0.4; // AR2
  140. m_flDesiredAmmo[4] = 0.5; // Shotgun
  141. m_flDesiredAmmo[5] = 0.0; // RPG Round
  142. m_flDesiredAmmo[6] = 0.1; // Grenade
  143. m_flDesiredAmmo[7] = 0; // 357
  144. m_flDesiredAmmo[8] = 0; // Crossbow
  145. m_flDesiredAmmo[9] = 0; // AR2 alt-fire
  146. }
  147. //-----------------------------------------------------------------------------
  148. // Purpose:
  149. //-----------------------------------------------------------------------------
  150. void CItem_DynamicResupply::Spawn( void )
  151. {
  152. if ( g_pGameRules->IsAllowedToSpawn( this ) == false )
  153. {
  154. UTIL_Remove( this );
  155. return;
  156. }
  157. // Don't callback to spawn
  158. Precache();
  159. m_bIsMaster = HasSpawnFlags( SF_DYNAMICRESUPPLY_IS_MASTER );
  160. // Am I the master?
  161. if ( !HasSpawnFlags( SF_DYNAMICRESUPPLY_IS_MASTER | SF_DYNAMICRESUPPLY_ALTERNATE_MASTER ) )
  162. {
  163. // Stagger the thinks a bit so they don't all think at the same time
  164. SetNextThink( gpGlobals->curtime + RandomFloat(0.2f, 0.4f) );
  165. SetThink( &CItem_DynamicResupply::CheckPVSThink );
  166. }
  167. }
  168. //-----------------------------------------------------------------------------
  169. // Purpose:
  170. //-----------------------------------------------------------------------------
  171. void CItem_DynamicResupply::Activate( void )
  172. {
  173. BaseClass::Activate();
  174. if ( HasSpawnFlags( SF_DYNAMICRESUPPLY_IS_MASTER ) )
  175. {
  176. if ( !g_MasterResupply && ( m_bIsMaster || m_version < VERSION_1_PERSISTENT_MASTER ) )
  177. {
  178. g_MasterResupply = this;
  179. }
  180. else
  181. {
  182. m_bIsMaster = false;
  183. }
  184. }
  185. if ( !HasSpawnFlags( SF_DYNAMICRESUPPLY_ALTERNATE_MASTER ) && HasSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER ) && gpGlobals->curtime < 1.0 )
  186. {
  187. if ( !g_MasterResupply )
  188. {
  189. Warning( "item_dynamic_resupply set to 'Use Master', but no item_dynamic_resupply master exists.\n" );
  190. }
  191. }
  192. }
  193. //-----------------------------------------------------------------------------
  194. // Purpose:
  195. //-----------------------------------------------------------------------------
  196. void CItem_DynamicResupply::Precache( void )
  197. {
  198. // Precache all the items potentially spawned
  199. int i;
  200. for ( i = 0; i < NUM_HEALTH_ITEMS; i++ )
  201. {
  202. UTIL_PrecacheOther( g_DynamicResupplyHealthItems[i].sEntityName );
  203. }
  204. for ( i = 0; i < NUM_AMMO_ITEMS; i++ )
  205. {
  206. UTIL_PrecacheOther( g_DynamicResupplyAmmoItems[i].sEntityName );
  207. }
  208. }
  209. //-----------------------------------------------------------------------------
  210. // Purpose:
  211. //-----------------------------------------------------------------------------
  212. void CItem_DynamicResupply::CheckPVSThink( void )
  213. {
  214. edict_t *pentPlayer = UTIL_FindClientInPVS( edict() );
  215. if ( pentPlayer )
  216. {
  217. CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pentPlayer );
  218. if ( pPlayer )
  219. {
  220. SpawnDynamicItem( pPlayer );
  221. return;
  222. }
  223. }
  224. SetNextThink( gpGlobals->curtime + DYNAMIC_ITEM_THINK );
  225. }
  226. //-----------------------------------------------------------------------------
  227. // Purpose:
  228. // Input : &data -
  229. //-----------------------------------------------------------------------------
  230. void CItem_DynamicResupply::InputKill( inputdata_t &data )
  231. {
  232. UTIL_Remove( this );
  233. }
  234. //-----------------------------------------------------------------------------
  235. // Purpose:
  236. // Input : &data -
  237. //-----------------------------------------------------------------------------
  238. void CItem_DynamicResupply::InputCalculateType( inputdata_t &data )
  239. {
  240. CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
  241. SpawnDynamicItem( pPlayer );
  242. }
  243. //-----------------------------------------------------------------------------
  244. // Purpose:
  245. // Input : &data -
  246. //-----------------------------------------------------------------------------
  247. void CItem_DynamicResupply::InputBecomeMaster( inputdata_t &data )
  248. {
  249. if ( g_MasterResupply )
  250. g_MasterResupply->m_bIsMaster = false;
  251. g_MasterResupply = this;
  252. m_bIsMaster = true;
  253. // Stop thinking now that I am the master.
  254. SetThink( NULL );
  255. }
  256. //-----------------------------------------------------------------------------
  257. // Chooses an item when the player is full
  258. //-----------------------------------------------------------------------------
  259. void CItem_DynamicResupply::SpawnFullItem( CItem_DynamicResupply *pMaster, CBasePlayer *pPlayer, int iDebug )
  260. {
  261. // Can we not actually spawn the item?
  262. if ( !HasSpawnFlags(SF_DYNAMICRESUPPLY_ALWAYS_SPAWN) )
  263. return;
  264. float flRatio[NUM_AMMO_ITEMS];
  265. int i;
  266. float flTotalProb = 0.0f;
  267. for ( i = 0; i < NUM_AMMO_ITEMS; ++i )
  268. {
  269. int iAmmoType = GetAmmoDef()->Index( g_DynamicResupplyAmmoItems[i].sAmmoDef );
  270. bool bCanSpawn = pPlayer->Weapon_GetWpnForAmmo( iAmmoType ) != NULL;
  271. if ( bCanSpawn && ( g_DynamicResupplyAmmoItems[i].flFullProbability != 0 ) && ( pMaster->m_flDesiredAmmo[i] != 0.0f ) )
  272. {
  273. flTotalProb += g_DynamicResupplyAmmoItems[i].flFullProbability;
  274. flRatio[i] = flTotalProb;
  275. }
  276. else
  277. {
  278. flRatio[i] = -1.0f;
  279. }
  280. }
  281. if ( flTotalProb == 0.0f )
  282. {
  283. // If we're supposed to fallback to just a health vial, do that and finish.
  284. if ( pMaster->HasSpawnFlags(SF_DYNAMICRESUPPLY_FALLBACK_TO_VIAL) )
  285. {
  286. CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles(), this );
  287. if ( iDebug )
  288. {
  289. Msg("Player is full, spawning item_healthvial due to spawnflag.\n");
  290. }
  291. return;
  292. }
  293. // Otherwise, spawn the first ammo item in the list
  294. flRatio[0] = 1.0f;
  295. flTotalProb = 1.0f;
  296. }
  297. float flChoice = random->RandomFloat( 0.0f, flTotalProb );
  298. for ( i = 0; i < NUM_AMMO_ITEMS; ++i )
  299. {
  300. if ( flChoice <= flRatio[i] )
  301. {
  302. CBaseEntity::Create( g_DynamicResupplyAmmoItems[i].sEntityName, GetAbsOrigin(), GetAbsAngles(), this );
  303. if ( iDebug )
  304. {
  305. Msg("Player is full, spawning %s \n", g_DynamicResupplyAmmoItems[i].sEntityName );
  306. }
  307. return;
  308. }
  309. }
  310. if ( iDebug )
  311. {
  312. Msg("Player is full on all health + ammo, is not spawning.\n" );
  313. }
  314. }
  315. //-----------------------------------------------------------------------------
  316. // Purpose:
  317. //-----------------------------------------------------------------------------
  318. void CItem_DynamicResupply::FindPotentialItems( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo )
  319. {
  320. int i;
  321. for ( i = 0; i < nCount; ++i )
  322. {
  323. pSpawnInfo[i].m_iPotentialItems = 0;
  324. }
  325. // Count the potential addition of items in the PVS
  326. CBaseEntity *pEntity = NULL;
  327. while ( (pEntity = UTIL_EntitiesInPVS( this, pEntity )) != NULL )
  328. {
  329. if ( pEntity->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ) > (POTENTIAL_ITEM_RADIUS * POTENTIAL_ITEM_RADIUS) )
  330. continue;
  331. for ( i = 0; i < nCount; ++i )
  332. {
  333. if ( !FClassnameIs( pEntity, pItems[i].sEntityName ) )
  334. continue;
  335. if ( iDebug == 2 )
  336. {
  337. NDebugOverlay::Line( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), 0,255,0, true, 20.0 );
  338. }
  339. ++pSpawnInfo[i].m_iPotentialItems;
  340. break;
  341. }
  342. }
  343. if ( iDebug )
  344. {
  345. Msg("Searching the PVS:\n");
  346. for ( int i = 0; i < nCount; i++ )
  347. {
  348. Msg(" Found %d '%s' in the PVS.\n", pSpawnInfo[i].m_iPotentialItems, pItems[i].sEntityName );
  349. }
  350. }
  351. }
  352. //-----------------------------------------------------------------------------
  353. // Purpose:
  354. //-----------------------------------------------------------------------------
  355. void CItem_DynamicResupply::ComputeHealthRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo )
  356. {
  357. for ( int i = 0; i < NUM_HEALTH_ITEMS; i++ )
  358. {
  359. // Figure out the current level of this resupply type
  360. float flMax;
  361. if ( i == DS_HEALTH_INDEX )
  362. {
  363. // Health
  364. flMax = pPlayer->GetMaxHealth();
  365. float flCurrentHealth = pPlayer->GetHealth() + (pSpawnInfo[i].m_iPotentialItems * sk_healthkit.GetFloat());
  366. pSpawnInfo[i].m_flCurrentRatio = (flCurrentHealth / flMax);
  367. }
  368. else if ( i == DS_ARMOR_INDEX )
  369. {
  370. // Armor
  371. // Ignore armor if we don't have the suit
  372. if ( !pPlayer->IsSuitEquipped() )
  373. {
  374. pSpawnInfo[i].m_flCurrentRatio = 1.0;
  375. }
  376. else
  377. {
  378. flMax = MAX_NORMAL_BATTERY;
  379. float flCurrentArmor = pPlayer->ArmorValue() + (pSpawnInfo[i].m_iPotentialItems * sk_battery.GetFloat());
  380. pSpawnInfo[i].m_flCurrentRatio = (flCurrentArmor / flMax);
  381. }
  382. }
  383. pSpawnInfo[i].m_flDesiredRatio = pMaster->m_flDesiredHealth[i] * sk_dynamic_resupply_modifier.GetFloat();
  384. pSpawnInfo[i].m_flDelta = pSpawnInfo[i].m_flDesiredRatio - pSpawnInfo[i].m_flCurrentRatio;
  385. pSpawnInfo[i].m_flDelta = clamp( pSpawnInfo[i].m_flDelta, 0, 1 );
  386. }
  387. if ( iDebug )
  388. {
  389. Msg("Calculating desired health ratios & deltas:\n");
  390. for ( int i = 0; i < NUM_HEALTH_ITEMS; i++ )
  391. {
  392. Msg(" %s Desired Ratio: %.2f, Current Ratio: %.2f = Delta of %.2f\n",
  393. g_DynamicResupplyHealthItems[i].sEntityName, pSpawnInfo[i].m_flDesiredRatio, pSpawnInfo[i].m_flCurrentRatio, pSpawnInfo[i].m_flDelta );
  394. }
  395. }
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Purpose:
  399. //-----------------------------------------------------------------------------
  400. void CItem_DynamicResupply::ComputeAmmoRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo )
  401. {
  402. for ( int i = 0; i < NUM_AMMO_ITEMS; i++ )
  403. {
  404. // Get the ammodef's
  405. int iAmmoType = GetAmmoDef()->Index( g_DynamicResupplyAmmoItems[i].sAmmoDef );
  406. Assert( iAmmoType != -1 );
  407. // Ignore ammo types if we don't have a weapon that uses it (except for the grenade)
  408. if ( (i != DS_GRENADE_INDEX) && !pPlayer->Weapon_GetWpnForAmmo( iAmmoType ) )
  409. {
  410. pSpawnInfo[i].m_flCurrentRatio = 1.0;
  411. }
  412. else
  413. {
  414. float flMax = GetAmmoDef()->MaxCarry( iAmmoType );
  415. float flCurrentAmmo = pPlayer->GetAmmoCount( iAmmoType );
  416. flCurrentAmmo += (pSpawnInfo[i].m_iPotentialItems * g_DynamicResupplyAmmoItems[i].iAmmoCount);
  417. pSpawnInfo[i].m_flCurrentRatio = (flCurrentAmmo / flMax);
  418. }
  419. // Use the master if we're supposed to
  420. pSpawnInfo[i].m_flDesiredRatio = pMaster->m_flDesiredAmmo[i] * sk_dynamic_resupply_modifier.GetFloat();
  421. pSpawnInfo[i].m_flDelta = pSpawnInfo[i].m_flDesiredRatio - pSpawnInfo[i].m_flCurrentRatio;
  422. pSpawnInfo[i].m_flDelta = clamp( pSpawnInfo[i].m_flDelta, 0, 1 );
  423. }
  424. if ( iDebug )
  425. {
  426. Msg("Calculating desired ammo ratios & deltas:\n");
  427. for ( int i = 0; i < NUM_AMMO_ITEMS; i++ )
  428. {
  429. Msg(" %s Desired Ratio: %.2f, Current Ratio: %.2f = Delta of %.2f\n",
  430. g_DynamicResupplyAmmoItems[i].sEntityName, pSpawnInfo[i].m_flDesiredRatio, pSpawnInfo[i].m_flCurrentRatio, pSpawnInfo[i].m_flDelta );
  431. }
  432. }
  433. }
  434. //-----------------------------------------------------------------------------
  435. // Purpose:
  436. //-----------------------------------------------------------------------------
  437. bool CItem_DynamicResupply::SpawnItemFromRatio( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo, Vector *pVecSpawnOrigin )
  438. {
  439. // Now find the one we're farthest from
  440. float flFarthest = 0;
  441. int iSelectedIndex = -1;
  442. for ( int i = 0; i < nCount; ++i )
  443. {
  444. if ( pSpawnInfo[i].m_flDelta > flFarthest )
  445. {
  446. flFarthest = pSpawnInfo[i].m_flDelta;
  447. iSelectedIndex = i;
  448. }
  449. }
  450. if ( iSelectedIndex < 0 )
  451. return false;
  452. if ( iDebug )
  453. {
  454. Msg("Chosen item: %s (had farthest delta, %.2f)\n", pItems[iSelectedIndex].sEntityName, pSpawnInfo[iSelectedIndex].m_flDelta );
  455. }
  456. CBaseEntity *pEnt = CBaseEntity::Create( pItems[iSelectedIndex].sEntityName, *pVecSpawnOrigin, GetAbsAngles(), this );
  457. pEnt->SetAbsVelocity( GetAbsVelocity() );
  458. pEnt->SetLocalAngularVelocity( GetLocalAngularVelocity() );
  459. // Move the entity up so that it doesn't go below the spawn origin
  460. Vector vecWorldMins, vecWorldMaxs;
  461. pEnt->CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs );
  462. if ( vecWorldMins.z < pVecSpawnOrigin->z )
  463. {
  464. float dz = pVecSpawnOrigin->z - vecWorldMins.z;
  465. pVecSpawnOrigin->z += dz;
  466. vecWorldMaxs.z += dz;
  467. pEnt->SetAbsOrigin( *pVecSpawnOrigin );
  468. }
  469. // Update the spawn position to spawn them on top of each other
  470. pVecSpawnOrigin->z = vecWorldMaxs.z + 6.0f;
  471. pVecSpawnOrigin->x += random->RandomFloat( -6, 6 );
  472. pVecSpawnOrigin->y += random->RandomFloat( -6, 6 );
  473. return true;
  474. }
  475. //-----------------------------------------------------------------------------
  476. // Purpose:
  477. //-----------------------------------------------------------------------------
  478. void CItem_DynamicResupply::SpawnDynamicItem( CBasePlayer *pPlayer )
  479. {
  480. Assert( pPlayer );
  481. // If we're the master, we never want to spawn
  482. if ( g_MasterResupply == this )
  483. return;
  484. int iDebug = g_debug_dynamicresupplies.GetInt();
  485. if ( iDebug )
  486. {
  487. Msg("Spawning item_dynamic_resupply:\n");
  488. }
  489. SpawnInfo_t pAmmoInfo[ NUM_AMMO_ITEMS ];
  490. SpawnInfo_t pHealthInfo[ NUM_HEALTH_ITEMS ];
  491. // Count the potential addition of items in the PVS
  492. FindPotentialItems( NUM_HEALTH_ITEMS, g_DynamicResupplyHealthItems, iDebug, pHealthInfo );
  493. FindPotentialItems( NUM_AMMO_ITEMS, g_DynamicResupplyAmmoItems, iDebug, pAmmoInfo );
  494. // Use the master if we're supposed to
  495. CItem_DynamicResupply *pMaster = this;
  496. if ( HasSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER ) && g_MasterResupply )
  497. {
  498. pMaster = g_MasterResupply;
  499. }
  500. // Compute desired ratios for health and ammo
  501. ComputeHealthRatios( pMaster, pPlayer, iDebug, pHealthInfo );
  502. ComputeAmmoRatios( pMaster, pPlayer, iDebug, pAmmoInfo );
  503. Vector vecSpawnOrigin = GetAbsOrigin();
  504. bool bHealthSpawned = SpawnItemFromRatio( NUM_HEALTH_ITEMS, g_DynamicResupplyHealthItems, iDebug, pHealthInfo, &vecSpawnOrigin );
  505. bool bAmmoSpawned = SpawnItemFromRatio( NUM_AMMO_ITEMS, g_DynamicResupplyAmmoItems, iDebug, pAmmoInfo, &vecSpawnOrigin );
  506. if ( !bHealthSpawned && !bAmmoSpawned )
  507. {
  508. SpawnFullItem( pMaster, pPlayer, iDebug );
  509. }
  510. SetThink( NULL );
  511. UTIL_Remove( this );
  512. }
  513. //-----------------------------------------------------------------------------
  514. // Purpose:
  515. // Output : float
  516. //-----------------------------------------------------------------------------
  517. float DynamicResupply_GetDesiredHealthPercentage( void )
  518. {
  519. // Return what the master supply dictates
  520. if ( g_MasterResupply != NULL )
  521. return g_MasterResupply->GetDesiredHealthPercentage();
  522. // Full health if they haven't specified otherwise
  523. return 1.0f;
  524. }
  525. //-----------------------------------------------------------------------------
  526. //
  527. //-----------------------------------------------------------------------------
  528. void DynamicResupply_InitFromAlternateMaster( CBaseEntity *pTargetEnt, string_t iszMaster )
  529. {
  530. if ( iszMaster== NULL_STRING )
  531. {
  532. return;
  533. }
  534. CItem_DynamicResupply *pTargetResupply = assert_cast<CItem_DynamicResupply *>( pTargetEnt );
  535. CBaseEntity *pMasterEnt = gEntList.FindEntityByName( NULL, iszMaster );
  536. if ( !pMasterEnt || !pMasterEnt->ClassMatches( pTargetResupply->GetClassname() ) )
  537. {
  538. DevWarning( "Invalid item_dynamic_resupply name %s\n", STRING( iszMaster ) );
  539. return;
  540. }
  541. CItem_DynamicResupply *pMasterResupply = assert_cast<CItem_DynamicResupply *>( pMasterEnt );
  542. pTargetResupply->RemoveSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER );
  543. memcpy( pTargetResupply->m_flDesiredHealth, pMasterResupply->m_flDesiredHealth, sizeof( pMasterResupply->m_flDesiredHealth ) );
  544. memcpy( pTargetResupply->m_flDesiredAmmo, pMasterResupply->m_flDesiredAmmo, sizeof( pMasterResupply->m_flDesiredAmmo ) );
  545. }