//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "item_dynamic_resupply.h" #include "props.h" #include "items.h" #include "ammodef.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar sk_dynamic_resupply_modifier( "sk_dynamic_resupply_modifier","1.0" ); extern ConVar sk_battery; extern ConVar sk_healthkit; 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." ); struct DynamicResupplyItems_t { const char *sEntityName; const char *sAmmoDef; int iAmmoCount; float flFullProbability; // Probability of spawning if the player meeds all goals }; struct SpawnInfo_t { float m_flDesiredRatio; float m_flCurrentRatio; float m_flDelta; int m_iPotentialItems; }; // Health types static DynamicResupplyItems_t g_DynamicResupplyHealthItems[] = { { "item_healthkit", "Health", 0, 0.0f, }, { "item_battery", "Armor", 0, 0.0f }, }; // Ammo types static DynamicResupplyItems_t g_DynamicResupplyAmmoItems[] = { { "item_ammo_pistol", "Pistol", SIZE_AMMO_PISTOL, 0.5f }, { "item_ammo_smg1", "SMG1", SIZE_AMMO_SMG1, 0.4f }, { "item_ammo_smg1_grenade", "SMG1_Grenade", SIZE_AMMO_SMG1_GRENADE, 0.0f }, { "item_ammo_ar2", "AR2", SIZE_AMMO_AR2, 0.0f }, { "item_box_buckshot", "Buckshot", SIZE_AMMO_BUCKSHOT, 0.0f }, { "item_rpg_round", "RPG_Round", SIZE_AMMO_RPG_ROUND, 0.0f }, { "weapon_frag", "Grenade", 1, 0.1f }, { "item_ammo_357", "357", SIZE_AMMO_357, 0.0f }, { "item_ammo_crossbow", "XBowBolt", SIZE_AMMO_CROSSBOW, 0.0f }, { "item_ammo_ar2_altfire", "AR2AltFire", SIZE_AMMO_AR2_ALTFIRE, 0.0f }, }; #define DS_HEALTH_INDEX 0 #define DS_ARMOR_INDEX 1 #define DS_GRENADE_INDEX 6 #define NUM_HEALTH_ITEMS (ARRAYSIZE(g_DynamicResupplyHealthItems)) #define NUM_AMMO_ITEMS (ARRAYSIZE(g_DynamicResupplyAmmoItems)) #define DYNAMIC_ITEM_THINK 1.0 #define POTENTIAL_ITEM_RADIUS 1024 //----------------------------------------------------------------------------- // Purpose: An item that dynamically decides what the player needs most and spawns that. //----------------------------------------------------------------------------- class CItem_DynamicResupply : public CPointEntity { DECLARE_CLASS( CItem_DynamicResupply, CPointEntity ); public: DECLARE_DATADESC(); CItem_DynamicResupply(); void Spawn( void ); void Precache( void ); void Activate( void ); void CheckPVSThink( void ); // Inputs void InputKill( inputdata_t &data ); void InputCalculateType( inputdata_t &data ); void InputBecomeMaster( inputdata_t &data ); float GetDesiredHealthPercentage( void ) const { return m_flDesiredHealth[0]; } private: friend void DynamicResupply_InitFromAlternateMaster( CBaseEntity *pTargetEnt, string_t iszMaster ); void FindPotentialItems( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo ); void ComputeHealthRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo ); void ComputeAmmoRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo ); bool SpawnItemFromRatio( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo, Vector *pVecSpawnOrigin ); // Spawns an item when the player is full void SpawnFullItem( CItem_DynamicResupply *pMaster, CBasePlayer *pPlayer, int iDebug ); void SpawnDynamicItem( CBasePlayer *pPlayer ); enum Versions { VERSION_0, VERSION_1_PERSISTENT_MASTER, VERSION_CURRENT = VERSION_1_PERSISTENT_MASTER, }; int m_version; float m_flDesiredHealth[ NUM_HEALTH_ITEMS ]; float m_flDesiredAmmo[ NUM_AMMO_ITEMS ]; bool m_bIsMaster; }; LINK_ENTITY_TO_CLASS(item_dynamic_resupply, CItem_DynamicResupply); // Master typedef CHandle DynamicResupplyHandle_t; static DynamicResupplyHandle_t g_MasterResupply; //----------------------------------------------------------------------------- // Save/load: //----------------------------------------------------------------------------- BEGIN_DATADESC( CItem_DynamicResupply ) DEFINE_THINKFUNC( CheckPVSThink ), DEFINE_INPUTFUNC( FIELD_VOID, "Kill", InputKill ), DEFINE_INPUTFUNC( FIELD_VOID, "CalculateType", InputCalculateType ), DEFINE_INPUTFUNC( FIELD_VOID, "BecomeMaster", InputBecomeMaster ), DEFINE_KEYFIELD( m_flDesiredHealth[0], FIELD_FLOAT, "DesiredHealth" ), DEFINE_KEYFIELD( m_flDesiredHealth[1], FIELD_FLOAT, "DesiredArmor" ), DEFINE_KEYFIELD( m_flDesiredAmmo[0], FIELD_FLOAT, "DesiredAmmoPistol" ), DEFINE_KEYFIELD( m_flDesiredAmmo[1], FIELD_FLOAT, "DesiredAmmoSMG1" ), DEFINE_KEYFIELD( m_flDesiredAmmo[2], FIELD_FLOAT, "DesiredAmmoSMG1_Grenade" ), DEFINE_KEYFIELD( m_flDesiredAmmo[3], FIELD_FLOAT, "DesiredAmmoAR2" ), DEFINE_KEYFIELD( m_flDesiredAmmo[4], FIELD_FLOAT, "DesiredAmmoBuckshot" ), DEFINE_KEYFIELD( m_flDesiredAmmo[5], FIELD_FLOAT, "DesiredAmmoRPG_Round" ), DEFINE_KEYFIELD( m_flDesiredAmmo[6], FIELD_FLOAT, "DesiredAmmoGrenade" ), DEFINE_KEYFIELD( m_flDesiredAmmo[7], FIELD_FLOAT, "DesiredAmmo357" ), DEFINE_KEYFIELD( m_flDesiredAmmo[8], FIELD_FLOAT, "DesiredAmmoCrossbow" ), DEFINE_KEYFIELD( m_flDesiredAmmo[9], FIELD_FLOAT, "DesiredAmmoAR2_AltFire" ), DEFINE_FIELD( m_version, FIELD_INTEGER ), DEFINE_FIELD( m_bIsMaster, FIELD_BOOLEAN ), // Silence, Classcheck! // DEFINE_ARRAY( m_flDesiredHealth, FIELD_FLOAT, NUM_HEALTH_ITEMS ), // DEFINE_ARRAY( m_flDesiredAmmo, FIELD_FLOAT, NUM_AMMO_ITEMS ), END_DATADESC() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CItem_DynamicResupply::CItem_DynamicResupply( void ) { AddSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER ); m_version = VERSION_CURRENT; // Setup default values m_flDesiredHealth[0] = 1.0; // Health m_flDesiredHealth[1] = 0.3; // Armor m_flDesiredAmmo[0] = 0.5; // Pistol m_flDesiredAmmo[1] = 0.5; // SMG1 m_flDesiredAmmo[2] = 0.1; // SMG1 Grenade m_flDesiredAmmo[3] = 0.4; // AR2 m_flDesiredAmmo[4] = 0.5; // Shotgun m_flDesiredAmmo[5] = 0.0; // RPG Round m_flDesiredAmmo[6] = 0.1; // Grenade m_flDesiredAmmo[7] = 0; // 357 m_flDesiredAmmo[8] = 0; // Crossbow m_flDesiredAmmo[9] = 0; // AR2 alt-fire } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::Spawn( void ) { if ( g_pGameRules->IsAllowedToSpawn( this ) == false ) { UTIL_Remove( this ); return; } // Don't callback to spawn Precache(); m_bIsMaster = HasSpawnFlags( SF_DYNAMICRESUPPLY_IS_MASTER ); // Am I the master? if ( !HasSpawnFlags( SF_DYNAMICRESUPPLY_IS_MASTER | SF_DYNAMICRESUPPLY_ALTERNATE_MASTER ) ) { // Stagger the thinks a bit so they don't all think at the same time SetNextThink( gpGlobals->curtime + RandomFloat(0.2f, 0.4f) ); SetThink( &CItem_DynamicResupply::CheckPVSThink ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::Activate( void ) { BaseClass::Activate(); if ( HasSpawnFlags( SF_DYNAMICRESUPPLY_IS_MASTER ) ) { if ( !g_MasterResupply && ( m_bIsMaster || m_version < VERSION_1_PERSISTENT_MASTER ) ) { g_MasterResupply = this; } else { m_bIsMaster = false; } } if ( !HasSpawnFlags( SF_DYNAMICRESUPPLY_ALTERNATE_MASTER ) && HasSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER ) && gpGlobals->curtime < 1.0 ) { if ( !g_MasterResupply ) { Warning( "item_dynamic_resupply set to 'Use Master', but no item_dynamic_resupply master exists.\n" ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::Precache( void ) { // Precache all the items potentially spawned int i; for ( i = 0; i < NUM_HEALTH_ITEMS; i++ ) { UTIL_PrecacheOther( g_DynamicResupplyHealthItems[i].sEntityName ); } for ( i = 0; i < NUM_AMMO_ITEMS; i++ ) { UTIL_PrecacheOther( g_DynamicResupplyAmmoItems[i].sEntityName ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::CheckPVSThink( void ) { edict_t *pentPlayer = UTIL_FindClientInPVS( edict() ); if ( pentPlayer ) { CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pentPlayer ); if ( pPlayer ) { SpawnDynamicItem( pPlayer ); return; } } SetNextThink( gpGlobals->curtime + DYNAMIC_ITEM_THINK ); } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CItem_DynamicResupply::InputKill( inputdata_t &data ) { UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CItem_DynamicResupply::InputCalculateType( inputdata_t &data ) { CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); SpawnDynamicItem( pPlayer ); } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void CItem_DynamicResupply::InputBecomeMaster( inputdata_t &data ) { if ( g_MasterResupply ) g_MasterResupply->m_bIsMaster = false; g_MasterResupply = this; m_bIsMaster = true; // Stop thinking now that I am the master. SetThink( NULL ); } //----------------------------------------------------------------------------- // Chooses an item when the player is full //----------------------------------------------------------------------------- void CItem_DynamicResupply::SpawnFullItem( CItem_DynamicResupply *pMaster, CBasePlayer *pPlayer, int iDebug ) { // Can we not actually spawn the item? if ( !HasSpawnFlags(SF_DYNAMICRESUPPLY_ALWAYS_SPAWN) ) return; float flRatio[NUM_AMMO_ITEMS]; int i; float flTotalProb = 0.0f; for ( i = 0; i < NUM_AMMO_ITEMS; ++i ) { int iAmmoType = GetAmmoDef()->Index( g_DynamicResupplyAmmoItems[i].sAmmoDef ); bool bCanSpawn = pPlayer->Weapon_GetWpnForAmmo( iAmmoType ) != NULL; if ( bCanSpawn && ( g_DynamicResupplyAmmoItems[i].flFullProbability != 0 ) && ( pMaster->m_flDesiredAmmo[i] != 0.0f ) ) { flTotalProb += g_DynamicResupplyAmmoItems[i].flFullProbability; flRatio[i] = flTotalProb; } else { flRatio[i] = -1.0f; } } if ( flTotalProb == 0.0f ) { // If we're supposed to fallback to just a health vial, do that and finish. if ( pMaster->HasSpawnFlags(SF_DYNAMICRESUPPLY_FALLBACK_TO_VIAL) ) { CBaseEntity::Create( "item_healthvial", GetAbsOrigin(), GetAbsAngles(), this ); if ( iDebug ) { Msg("Player is full, spawning item_healthvial due to spawnflag.\n"); } return; } // Otherwise, spawn the first ammo item in the list flRatio[0] = 1.0f; flTotalProb = 1.0f; } float flChoice = random->RandomFloat( 0.0f, flTotalProb ); for ( i = 0; i < NUM_AMMO_ITEMS; ++i ) { if ( flChoice <= flRatio[i] ) { CBaseEntity::Create( g_DynamicResupplyAmmoItems[i].sEntityName, GetAbsOrigin(), GetAbsAngles(), this ); if ( iDebug ) { Msg("Player is full, spawning %s \n", g_DynamicResupplyAmmoItems[i].sEntityName ); } return; } } if ( iDebug ) { Msg("Player is full on all health + ammo, is not spawning.\n" ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::FindPotentialItems( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo ) { int i; for ( i = 0; i < nCount; ++i ) { pSpawnInfo[i].m_iPotentialItems = 0; } // Count the potential addition of items in the PVS CBaseEntity *pEntity = NULL; while ( (pEntity = UTIL_EntitiesInPVS( this, pEntity )) != NULL ) { if ( pEntity->WorldSpaceCenter().DistToSqr( WorldSpaceCenter() ) > (POTENTIAL_ITEM_RADIUS * POTENTIAL_ITEM_RADIUS) ) continue; for ( i = 0; i < nCount; ++i ) { if ( !FClassnameIs( pEntity, pItems[i].sEntityName ) ) continue; if ( iDebug == 2 ) { NDebugOverlay::Line( WorldSpaceCenter(), pEntity->WorldSpaceCenter(), 0,255,0, true, 20.0 ); } ++pSpawnInfo[i].m_iPotentialItems; break; } } if ( iDebug ) { Msg("Searching the PVS:\n"); for ( int i = 0; i < nCount; i++ ) { Msg(" Found %d '%s' in the PVS.\n", pSpawnInfo[i].m_iPotentialItems, pItems[i].sEntityName ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::ComputeHealthRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo ) { for ( int i = 0; i < NUM_HEALTH_ITEMS; i++ ) { // Figure out the current level of this resupply type float flMax; if ( i == DS_HEALTH_INDEX ) { // Health flMax = pPlayer->GetMaxHealth(); float flCurrentHealth = pPlayer->GetHealth() + (pSpawnInfo[i].m_iPotentialItems * sk_healthkit.GetFloat()); pSpawnInfo[i].m_flCurrentRatio = (flCurrentHealth / flMax); } else if ( i == DS_ARMOR_INDEX ) { // Armor // Ignore armor if we don't have the suit if ( !pPlayer->IsSuitEquipped() ) { pSpawnInfo[i].m_flCurrentRatio = 1.0; } else { flMax = MAX_NORMAL_BATTERY; float flCurrentArmor = pPlayer->ArmorValue() + (pSpawnInfo[i].m_iPotentialItems * sk_battery.GetFloat()); pSpawnInfo[i].m_flCurrentRatio = (flCurrentArmor / flMax); } } pSpawnInfo[i].m_flDesiredRatio = pMaster->m_flDesiredHealth[i] * sk_dynamic_resupply_modifier.GetFloat(); pSpawnInfo[i].m_flDelta = pSpawnInfo[i].m_flDesiredRatio - pSpawnInfo[i].m_flCurrentRatio; pSpawnInfo[i].m_flDelta = clamp( pSpawnInfo[i].m_flDelta, 0, 1 ); } if ( iDebug ) { Msg("Calculating desired health ratios & deltas:\n"); for ( int i = 0; i < NUM_HEALTH_ITEMS; i++ ) { Msg(" %s Desired Ratio: %.2f, Current Ratio: %.2f = Delta of %.2f\n", g_DynamicResupplyHealthItems[i].sEntityName, pSpawnInfo[i].m_flDesiredRatio, pSpawnInfo[i].m_flCurrentRatio, pSpawnInfo[i].m_flDelta ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::ComputeAmmoRatios( CItem_DynamicResupply* pMaster, CBasePlayer *pPlayer, int iDebug, SpawnInfo_t *pSpawnInfo ) { for ( int i = 0; i < NUM_AMMO_ITEMS; i++ ) { // Get the ammodef's int iAmmoType = GetAmmoDef()->Index( g_DynamicResupplyAmmoItems[i].sAmmoDef ); Assert( iAmmoType != -1 ); // Ignore ammo types if we don't have a weapon that uses it (except for the grenade) if ( (i != DS_GRENADE_INDEX) && !pPlayer->Weapon_GetWpnForAmmo( iAmmoType ) ) { pSpawnInfo[i].m_flCurrentRatio = 1.0; } else { float flMax = GetAmmoDef()->MaxCarry( iAmmoType ); float flCurrentAmmo = pPlayer->GetAmmoCount( iAmmoType ); flCurrentAmmo += (pSpawnInfo[i].m_iPotentialItems * g_DynamicResupplyAmmoItems[i].iAmmoCount); pSpawnInfo[i].m_flCurrentRatio = (flCurrentAmmo / flMax); } // Use the master if we're supposed to pSpawnInfo[i].m_flDesiredRatio = pMaster->m_flDesiredAmmo[i] * sk_dynamic_resupply_modifier.GetFloat(); pSpawnInfo[i].m_flDelta = pSpawnInfo[i].m_flDesiredRatio - pSpawnInfo[i].m_flCurrentRatio; pSpawnInfo[i].m_flDelta = clamp( pSpawnInfo[i].m_flDelta, 0, 1 ); } if ( iDebug ) { Msg("Calculating desired ammo ratios & deltas:\n"); for ( int i = 0; i < NUM_AMMO_ITEMS; i++ ) { Msg(" %s Desired Ratio: %.2f, Current Ratio: %.2f = Delta of %.2f\n", g_DynamicResupplyAmmoItems[i].sEntityName, pSpawnInfo[i].m_flDesiredRatio, pSpawnInfo[i].m_flCurrentRatio, pSpawnInfo[i].m_flDelta ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CItem_DynamicResupply::SpawnItemFromRatio( int nCount, DynamicResupplyItems_t *pItems, int iDebug, SpawnInfo_t *pSpawnInfo, Vector *pVecSpawnOrigin ) { // Now find the one we're farthest from float flFarthest = 0; int iSelectedIndex = -1; for ( int i = 0; i < nCount; ++i ) { if ( pSpawnInfo[i].m_flDelta > flFarthest ) { flFarthest = pSpawnInfo[i].m_flDelta; iSelectedIndex = i; } } if ( iSelectedIndex < 0 ) return false; if ( iDebug ) { Msg("Chosen item: %s (had farthest delta, %.2f)\n", pItems[iSelectedIndex].sEntityName, pSpawnInfo[iSelectedIndex].m_flDelta ); } CBaseEntity *pEnt = CBaseEntity::Create( pItems[iSelectedIndex].sEntityName, *pVecSpawnOrigin, GetAbsAngles(), this ); pEnt->SetAbsVelocity( GetAbsVelocity() ); pEnt->SetLocalAngularVelocity( GetLocalAngularVelocity() ); // Move the entity up so that it doesn't go below the spawn origin Vector vecWorldMins, vecWorldMaxs; pEnt->CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs ); if ( vecWorldMins.z < pVecSpawnOrigin->z ) { float dz = pVecSpawnOrigin->z - vecWorldMins.z; pVecSpawnOrigin->z += dz; vecWorldMaxs.z += dz; pEnt->SetAbsOrigin( *pVecSpawnOrigin ); } // Update the spawn position to spawn them on top of each other pVecSpawnOrigin->z = vecWorldMaxs.z + 6.0f; pVecSpawnOrigin->x += random->RandomFloat( -6, 6 ); pVecSpawnOrigin->y += random->RandomFloat( -6, 6 ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CItem_DynamicResupply::SpawnDynamicItem( CBasePlayer *pPlayer ) { Assert( pPlayer ); // If we're the master, we never want to spawn if ( g_MasterResupply == this ) return; int iDebug = g_debug_dynamicresupplies.GetInt(); if ( iDebug ) { Msg("Spawning item_dynamic_resupply:\n"); } SpawnInfo_t pAmmoInfo[ NUM_AMMO_ITEMS ]; SpawnInfo_t pHealthInfo[ NUM_HEALTH_ITEMS ]; // Count the potential addition of items in the PVS FindPotentialItems( NUM_HEALTH_ITEMS, g_DynamicResupplyHealthItems, iDebug, pHealthInfo ); FindPotentialItems( NUM_AMMO_ITEMS, g_DynamicResupplyAmmoItems, iDebug, pAmmoInfo ); // Use the master if we're supposed to CItem_DynamicResupply *pMaster = this; if ( HasSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER ) && g_MasterResupply ) { pMaster = g_MasterResupply; } // Compute desired ratios for health and ammo ComputeHealthRatios( pMaster, pPlayer, iDebug, pHealthInfo ); ComputeAmmoRatios( pMaster, pPlayer, iDebug, pAmmoInfo ); Vector vecSpawnOrigin = GetAbsOrigin(); bool bHealthSpawned = SpawnItemFromRatio( NUM_HEALTH_ITEMS, g_DynamicResupplyHealthItems, iDebug, pHealthInfo, &vecSpawnOrigin ); bool bAmmoSpawned = SpawnItemFromRatio( NUM_AMMO_ITEMS, g_DynamicResupplyAmmoItems, iDebug, pAmmoInfo, &vecSpawnOrigin ); if ( !bHealthSpawned && !bAmmoSpawned ) { SpawnFullItem( pMaster, pPlayer, iDebug ); } SetThink( NULL ); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float DynamicResupply_GetDesiredHealthPercentage( void ) { // Return what the master supply dictates if ( g_MasterResupply != NULL ) return g_MasterResupply->GetDesiredHealthPercentage(); // Full health if they haven't specified otherwise return 1.0f; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void DynamicResupply_InitFromAlternateMaster( CBaseEntity *pTargetEnt, string_t iszMaster ) { if ( iszMaster== NULL_STRING ) { return; } CItem_DynamicResupply *pTargetResupply = assert_cast( pTargetEnt ); CBaseEntity *pMasterEnt = gEntList.FindEntityByName( NULL, iszMaster ); if ( !pMasterEnt || !pMasterEnt->ClassMatches( pTargetResupply->GetClassname() ) ) { DevWarning( "Invalid item_dynamic_resupply name %s\n", STRING( iszMaster ) ); return; } CItem_DynamicResupply *pMasterResupply = assert_cast( pMasterEnt ); pTargetResupply->RemoveSpawnFlags( SF_DYNAMICRESUPPLY_USE_MASTER ); memcpy( pTargetResupply->m_flDesiredHealth, pMasterResupply->m_flDesiredHealth, sizeof( pMasterResupply->m_flDesiredHealth ) ); memcpy( pTargetResupply->m_flDesiredAmmo, pMasterResupply->m_flDesiredAmmo, sizeof( pMasterResupply->m_flDesiredAmmo ) ); }