diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/tf_obj_dispenser.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/tf/tf_obj_dispenser.cpp')
| -rw-r--r-- | game/server/tf/tf_obj_dispenser.cpp | 1261 |
1 files changed, 1261 insertions, 0 deletions
diff --git a/game/server/tf/tf_obj_dispenser.cpp b/game/server/tf/tf_obj_dispenser.cpp new file mode 100644 index 0000000..ba22acd --- /dev/null +++ b/game/server/tf/tf_obj_dispenser.cpp @@ -0,0 +1,1261 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Engineer's Dispenser +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" + +#include "tf_obj_dispenser.h" +#include "engine/IEngineSound.h" +#include "tf_player.h" +#include "tf_team.h" +#include "tf_gamerules.h" +#include "vguiscreen.h" +#include "world.h" +#include "explode.h" +#include "tf_gamestats.h" +#include "tf_halloween_souls_pickup.h" +#include "tf_fx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define DISPENSER_MINS Vector( -20, -20, 0 ) +#define DISPENSER_MAXS Vector( 20, 20, 55 ) // tweak me + +#define MINI_DISPENSER_MINS Vector( -20, -20, 0 ) +#define MINI_DISPENSER_MAXS Vector( 20, 20, 20 ) + +#define DISPENSER_TRIGGER_MINS Vector( -70, -70, 0 ) +#define DISPENSER_TRIGGER_MAXS Vector( 70, 70, 50 ) // tweak me + +#define REFILL_CONTEXT "RefillContext" +#define DISPENSE_CONTEXT "DispenseContext" + +ConVar tf_cart_spell_drop_rate( "tf_cart_spell_drop_rate", "4" ); +ConVar tf_cart_duck_drop_rate( "tf_cart_duck_drop_rate", "10", FCVAR_DEVELOPMENTONLY ); +ConVar tf_cart_soul_drop_rate( "tf_cart_soul_drop_rate", "10", FCVAR_DEVELOPMENTONLY ); + +//----------------------------------------------------------------------------- +// Purpose: SendProxy that converts the Healing list UtlVector to entindices +//----------------------------------------------------------------------------- +void SendProxy_HealingList( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID ) +{ + CObjectDispenser *pDispenser = (CObjectDispenser*)pStruct; + + // If this assertion fails, then SendProxyArrayLength_HealingArray must have failed. + Assert( iElement < pDispenser->m_hHealingTargets.Size() ); + + CBaseEntity *pEnt = pDispenser->m_hHealingTargets[iElement].Get(); + EHANDLE hOther = pEnt; + + SendProxy_EHandleToInt( pProp, pStruct, &hOther, pOut, iElement, objectID ); +} + +int SendProxyArrayLength_HealingArray( const void *pStruct, int objectID ) +{ + CObjectDispenser *pDispenser = (CObjectDispenser*)pStruct; + return pDispenser->m_hHealingTargets.Count(); +} + +IMPLEMENT_SERVERCLASS_ST( CObjectDispenser, DT_ObjectDispenser ) + SendPropInt( SENDINFO( m_iState ), 2 ), + SendPropInt( SENDINFO( m_iAmmoMetal ), -1, SPROP_VARINT ), + SendPropInt( SENDINFO( m_iMiniBombCounter ), -1, SPROP_VARINT ), + + SendPropArray2( + SendProxyArrayLength_HealingArray, + SendPropInt("healing_array_element", 0, SIZEOF_IGNORE, NUM_NETWORKED_EHANDLE_BITS, SPROP_UNSIGNED, SendProxy_HealingList), + MAX_PLAYERS, + 0, + "healing_array" + ) + +END_SEND_TABLE() + +BEGIN_DATADESC( CObjectDispenser ) + DEFINE_THINKFUNC( RefillThink ), + DEFINE_THINKFUNC( DispenseThink ), + + // key + DEFINE_KEYFIELD( m_iszCustomTouchTrigger, FIELD_STRING, "touch_trigger" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS(obj_dispenser, CObjectDispenser); +PRECACHE_REGISTER(obj_dispenser); + +// How much ammo is given our per use +#define DISPENSER_DROP_METAL 40 + +float g_flDispenserHealRates[4] = +{ + 0, + 10.0, + 15.0, + 20.0 +}; + +float g_flDispenserAmmoRates[4] = +{ + 0, + 0.2, + 0.3, + 0.4 +}; + +LINK_ENTITY_TO_CLASS( dispenser_touch_trigger, CDispenserTouchTrigger ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectDispenser::CObjectDispenser() +{ + int iHealth = GetMaxHealthForCurrentLevel(); + m_hTouchTrigger = NULL; + SetMaxHealth( iHealth ); + SetHealth( iHealth ); + UseClientSideAnimation(); + + m_hTouchingEntities.Purge(); + m_bUseGenerateMetalSound = true; + m_bThrown = false; + + m_bPlayAmmoPickupSound = true; + + SetType( OBJ_DISPENSER ); +} + +CObjectDispenser::~CObjectDispenser() +{ + if ( m_hTouchTrigger.Get() ) + { + UTIL_Remove( m_hTouchTrigger ); + } + + ResetHealingTargets(); + + StopSound( "Building_Dispenser.Idle" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::DetonateObject( void ) +{ + // We already dying? + if ( m_bDying ) + return; + +#ifdef STAGING_ONLY + // If we're built, explode for damage + if ( IsMiniBuilding() && !IsCarried() && !IsBuilding() && !IsPlacing() ) + { + Vector vecOrigin = GetAbsOrigin(); + CTraceFilterIgnorePlayers traceFilter( NULL, COLLISION_GROUP_PROJECTILE ); + + // base 50 damage, scale by metal amount + float flDamage = RemapValClamped( m_iAmmoMetal, 0, MINI_DISPENSER_MAX_METAL, 50.0f, 300.0f ); + CTakeDamageInfo info( this, GetOwner(), flDamage, DMG_BLAST ); + + // Scale blast radius + float flRadius = RemapValClamped( m_iAmmoMetal, 0, MINI_DISPENSER_MAX_METAL, 150.0f, 200.0f ); + CTFRadiusDamageInfo radiusinfo( &info, vecOrigin, flRadius, NULL, flRadius ); + TFGameRules()->RadiusDamage( radiusinfo ); + + CPVSFilter filter( vecOrigin ); + TE_TFExplosion( filter, 0.0f, vecOrigin, Vector(0,0,0), TF_WEAPON_GRENADE_PIPEBOMB, kInvalidEHandleExplosion, -1, SPECIAL1, INVALID_STRING_INDEX ); + } +#endif + + TFGameRules()->OnDispenserDestroyed( this ); + + BaseClass::DetonateObject(); +} + +//----------------------------------------------------------------------------- +void CObjectDispenser::DestroyObject( void ) +{ + if ( TFGameRules() ) + { + TFGameRules()->OnDispenserDestroyed( this ); + } + + BaseClass::DestroyObject(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::Spawn() +{ + SetModel( GetPlacementModel() ); + + m_iState.Set( DISPENSER_STATE_IDLE ); + + SetTouch( &CObjectDispenser::Touch ); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::FirstSpawn() +{ + SetSolid( SOLID_BBOX ); + + bool bShouldBeMini = ShouldBeMiniBuilding( GetOwner() ); + + UTIL_SetSize(this, + bShouldBeMini ? MINI_DISPENSER_MINS : DISPENSER_MINS, + bShouldBeMini ? MINI_DISPENSER_MAXS : DISPENSER_MAXS ); + + m_takedamage = DAMAGE_YES; + m_iAmmoMetal = 0; + + int iHealth = GetMaxHealthForCurrentLevel(); + + SetMaxHealth( iHealth ); + SetHealth( iHealth ); + + BaseClass::FirstSpawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char* CObjectDispenser::GetBuildingModel( int iLevel ) +{ +#ifdef STAGING_ONLY + if ( ShouldBeMiniBuilding( GetOwner() ) ) + { + return MINI_DISPENSER_MODEL_BUILDING; + } + else +#endif // STAGING_ONLY + { + switch ( iLevel ) + { + case 1: + return DISPENSER_MODEL_BUILDING; + break; + case 2: + return DISPENSER_MODEL_BUILDING_LVL2; + break; + case 3: + return DISPENSER_MODEL_BUILDING_LVL3; + break; + default: + return DISPENSER_MODEL_BUILDING; + break; + } + } + + Assert( 0 ); + return DISPENSER_MODEL; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char* CObjectDispenser::GetFinishedModel( int iLevel ) +{ +#ifdef STAGING_ONLY + if ( IsMiniBuilding() ) + { + return MINI_DISPENSER_MODEL; + } + else +#endif // STAGING_ONLY + { + switch ( iLevel ) + { + case 1: + return DISPENSER_MODEL; + break; + case 2: + return DISPENSER_MODEL_LVL2; + break; + case 3: + return DISPENSER_MODEL_LVL3; + break; + default: + return DISPENSER_MODEL; + break; + } + } + + Assert( 0 ); + return DISPENSER_MODEL; +} + +const char* CObjectDispenser::GetPlacementModel() +{ + return /*IsMiniBuilding() ? MINI_DISPENSER_MODEL_PLACEMENT :*/ DISPENSER_MODEL_PLACEMENT; +} + +void CObjectDispenser::StartPlacement( CTFPlayer *pPlayer ) +{ + BaseClass::StartPlacement( pPlayer ); +} + +//----------------------------------------------------------------------------- +// Purpose: Start building the object +//----------------------------------------------------------------------------- +bool CObjectDispenser::StartBuilding( CBaseEntity *pBuilder ) +{ + SetStartBuildingModel(); + + // Have to re-call this in case the player changed their weapon + // between StartPlacement and StartBuilding. + MakeMiniBuilding( GetBuilder() ); + + CreateBuildPoints(); + + TFGameRules()->OnDispenserBuilt( this ); + + bool bIsCarried = IsCarried(); + + bool bStartBuildingResult = BaseClass::StartBuilding( pBuilder ); + + if ( bIsCarried ) + { + OnEndBeingCarried( pBuilder ); + } + + return bStartBuildingResult; +} + +void CObjectDispenser::SetStartBuildingModel( void ) +{ + SetModel( GetBuildingModel( 1 ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::MakeMiniBuilding( CTFPlayer* pPlayer ) +{ + if ( !ShouldBeMiniBuilding( pPlayer ) || IsMiniBuilding() ) + return; + + BaseClass::MakeMiniBuilding( pPlayer ); + + int iHealth = GetMaxHealthForCurrentLevel(); + + SetMaxHealth( iHealth ); + SetHealth( iHealth ); +} + +//----------------------------------------------------------------------------- +// Purpose: Raises the Dispenser one level +//----------------------------------------------------------------------------- +void CObjectDispenser::StartUpgrading( void ) +{ + // m_iUpgradeLevel is incremented in BaseClass::StartUpgrading(), + // but we need to set the model before calling it so SetActivity() will be successful + SetModel( GetBuildingModel( m_iUpgradeLevel+1 ) ); + + // clear our healing list, everyone will be + // added again at the new heal rate in DispenseThink() + ResetHealingTargets(); + + BaseClass::StartUpgrading(); + + m_iState.Set( DISPENSER_STATE_UPGRADING ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::FinishUpgrading( void ) +{ + m_iState.Set( DISPENSER_STATE_IDLE ); + + BaseClass::FinishUpgrading(); + + if ( m_iUpgradeLevel > 1 ) + { + SetModel( GetFinishedModel( m_iUpgradeLevel ) ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::SetModel( const char *pModel ) +{ + BaseClass::SetModel( pModel ); + + // Reset this after model change +#ifdef STAGING_ONLY + UTIL_SetSize(this, + IsMiniBuilding() ? MINI_DISPENSER_MINS : DISPENSER_MINS, + IsMiniBuilding() ? MINI_DISPENSER_MAXS : DISPENSER_MAXS ); +#else + UTIL_SetSize( this, + DISPENSER_MINS, + DISPENSER_MAXS ); +#endif // STAGING_ONLY + ResetSequenceInfo(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::InitializeMapPlacedObject( void ) +{ + // make sure we are using an appropriate model so that the control panels are in the right place + SetModel( GetFinishedModel( 1 ) ); + + BaseClass::InitializeMapPlacedObject(); +} + +bool CObjectDispenser::ShouldBeMiniBuilding( CTFPlayer* pPlayer ) +{ +#ifdef STAGING_ONLY + int nMiniDispenserEnabled = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( GetOwner(), nMiniDispenserEnabled, allows_building_mini_dispenser ); + return nMiniDispenserEnabled != 0; +#else + return false; +#endif // STAGING_ONLY +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CObjectDispenser::GetMaxUpgradeLevel() +{ +#ifdef STAGING_ONLY + if ( IsMiniBuilding() ) + return DISPENSER_MINI_MAX_LEVEL; +#endif // STAGING_ONLY + + return BaseClass::GetMaxUpgradeLevel(); +} + +//----------------------------------------------------------------------------- +// Purpose: Finished building +//----------------------------------------------------------------------------- +void CObjectDispenser::OnGoActive( void ) +{ + SetModel( GetFinishedModel( 1 ) ); + + CreateBuildPoints(); + + ReattachChildren(); + + // Put some ammo in the Dispenser + if ( !m_bCarryDeploy && !IsMiniBuilding() ) + { + m_iAmmoMetal = TFGameRules()->IsQuickBuildTime() ? DISPENSER_MAX_METAL_AMMO : 25; + } + + // Begin thinking + SetContextThink( &CObjectDispenser::RefillThink, gpGlobals->curtime + 3, REFILL_CONTEXT ); + SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT ); + + m_flNextAmmoDispense = gpGlobals->curtime + 0.5; + + if ( m_hTouchTrigger.Get() && dynamic_cast<CObjectCartDispenser*>(this) != NULL ) + { + UTIL_Remove( m_hTouchTrigger.Get() ); + m_hTouchTrigger = NULL; + } + + float flRadius = GetDispenserRadius(); + + if ( m_iszCustomTouchTrigger != NULL_STRING ) + { + m_hTouchTrigger = dynamic_cast<CDispenserTouchTrigger *> ( gEntList.FindEntityByName( NULL, m_iszCustomTouchTrigger ) ); + + if ( m_hTouchTrigger.Get() != NULL ) + { + m_hTouchTrigger->SetOwnerEntity( this ); //owned + } + } + + if ( m_hTouchTrigger.Get() == NULL ) + { + m_hTouchTrigger = CBaseEntity::Create( "dispenser_touch_trigger", GetAbsOrigin(), vec3_angle, this ); + UTIL_SetSize(m_hTouchTrigger.Get(), Vector(-flRadius,-flRadius,-flRadius), Vector(flRadius,flRadius,flRadius) ); + m_hTouchTrigger->SetSolid(SOLID_BBOX); + } + + Assert( m_hTouchTrigger.Get() ); + + BaseClass::OnGoActive(); + + PlayActiveSound(); +} + +void CObjectDispenser::PlayActiveSound() +{ + EmitSound( "Building_Dispenser.Idle" ); +} + +//----------------------------------------------------------------------------- +// Spawn the vgui control screens on the object +//----------------------------------------------------------------------------- +void CObjectDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + // Panels 0 and 1 are both control panels for now + if ( nPanelIndex == 0 || nPanelIndex == 1 ) + { + if ( GetTeamNumber() == TF_TEAM_RED ) + { + pPanelName = "screen_obj_dispenser_red"; + } + else + { + pPanelName = "screen_obj_dispenser_blue"; + } + } + else + { + BaseClass::GetControlPanelInfo( nPanelIndex, pPanelName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::Precache() +{ + BaseClass::Precache(); + + int iModelIndex; + + PrecacheModel( DISPENSER_MODEL_PLACEMENT ); + + iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING ); + PrecacheGibsForModel( iModelIndex ); + + iModelIndex = PrecacheModel( DISPENSER_MODEL ); + PrecacheGibsForModel( iModelIndex ); + + iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING_LVL2 ); + PrecacheGibsForModel( iModelIndex ); + + iModelIndex = PrecacheModel( DISPENSER_MODEL_LVL2 ); + PrecacheGibsForModel( iModelIndex ); + + iModelIndex = PrecacheModel( DISPENSER_MODEL_BUILDING_LVL3 ); + PrecacheGibsForModel( iModelIndex ); + + iModelIndex = PrecacheModel( DISPENSER_MODEL_LVL3 ); + PrecacheGibsForModel( iModelIndex ); + +#ifdef STAGING_ONLY + PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL_PLACEMENT ) ); + PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL_BUILDING ) ); + PrecacheGibsForModel( PrecacheModel( MINI_DISPENSER_MODEL ) ); +#endif // STAGING_ONLY + + PrecacheVGuiScreen( "screen_obj_dispenser_blue" ); + PrecacheVGuiScreen( "screen_obj_dispenser_red" ); + + PrecacheScriptSound( "Building_Dispenser.Idle" ); + PrecacheScriptSound( "Building_Dispenser.GenerateMetal" ); + PrecacheScriptSound( "Building_Dispenser.Heal" ); + + PrecacheParticleSystem( "dispenser_heal_red" ); + PrecacheParticleSystem( "dispenser_heal_blue" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectDispenser::DispenseAmmo( CTFPlayer *pPlayer ) +{ + int iTotalPickedUp = 0; + int iAmmoToAdd = 0; + + int nNoPrimaryAmmoFromDispensersWhileActive = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer->GetActiveWeapon(), nNoPrimaryAmmoFromDispensersWhileActive, no_primary_ammo_from_dispensers ); + +#ifdef STAGING_ONLY + float flAmmoRate = IsMiniBuilding() ? DISPENSER_MINI_AMMO_RATE : g_flDispenserAmmoRates[GetUpgradeLevel()]; +#else + float flAmmoRate = g_flDispenserAmmoRates[GetUpgradeLevel()]; +#endif + + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flAmmoRate, mult_dispenser_rate ); + + if ( nNoPrimaryAmmoFromDispensersWhileActive == 0 ) + { + // primary + iAmmoToAdd = (int)( pPlayer->GetMaxAmmo( TF_AMMO_PRIMARY ) * flAmmoRate ); + iTotalPickedUp += pPlayer->GiveAmmo( iAmmoToAdd, TF_AMMO_PRIMARY, !m_bPlayAmmoPickupSound, kAmmoSource_DispenserOrCart ); + } + + // secondary + iAmmoToAdd = (int)( pPlayer->GetMaxAmmo( TF_AMMO_SECONDARY ) * flAmmoRate ); + iTotalPickedUp += pPlayer->GiveAmmo( iAmmoToAdd, TF_AMMO_SECONDARY, !m_bPlayAmmoPickupSound, kAmmoSource_DispenserOrCart ); + + // metal + int iNoMetalFromDispenserWhileActive = 0; + CALL_ATTRIB_HOOK_INT_ON_OTHER( pPlayer->GetActiveWeapon(), iNoMetalFromDispenserWhileActive, no_metal_from_dispensers_while_active ); + if ( iNoMetalFromDispenserWhileActive == 0 ) + { + iTotalPickedUp += DispenseMetal( pPlayer ); + } + + if ( iTotalPickedUp > 0 ) + { + if ( m_bPlayAmmoPickupSound ) + { + EmitSound( "BaseCombatCharacter.AmmoPickup" ); + } + + CTFPlayer *pOwner = GetOwner(); + if ( pOwner && pOwner != pPlayer ) + { + // This is crude; it doesn't account for the value difference in resupplying rockets vs pistol bullets. + // Still, it's better than nothing when trying to measure the value classes generate. + if ( TFGameRules() && + TFGameRules()->GameModeUsesUpgrades() && + TFGameRules()->State_Get() == GR_STATE_RND_RUNNING ) + { + CTF_GameStats.Event_PlayerAwardBonusPoints( pOwner, pPlayer, 1 ); + } + } + + return true; + } + + // return false if we didn't pick up anything + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CObjectDispenser::DispenseMetal( CTFPlayer *pPlayer ) +{ + int iMetalToGive = DISPENSER_DROP_METAL + ((GetUpgradeLevel()-1) * 10); + int iMetal = pPlayer->GiveAmmo( Min( m_iAmmoMetal.Get(), iMetalToGive ), TF_AMMO_METAL, false, kAmmoSource_DispenserOrCart ); + m_iAmmoMetal -= iMetal; + + return iMetal; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::RefillThink( void ) +{ + if ( IsCarried() ) + return; + + SetContextThink( &CObjectDispenser::RefillThink, gpGlobals->curtime + 6, REFILL_CONTEXT ); + + if ( IsDisabled() ) + { + return; + } + + // Auto-refill half the amount as tfc, but twice as often + if ( m_iAmmoMetal < DISPENSER_MAX_METAL_AMMO ) + { + int iMetal = (DISPENSER_MAX_METAL_AMMO * 0.1) + ((GetUpgradeLevel()-1) * 10); + + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), iMetal, mult_dispenser_rate ); + + m_iAmmoMetal = Min( m_iAmmoMetal + iMetal, DISPENSER_MAX_METAL_AMMO ); + + if ( m_bUseGenerateMetalSound ) + { + EmitSound( "Building_Dispenser.GenerateMetal" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Generate ammo over time +//----------------------------------------------------------------------------- +void CObjectDispenser::DispenseThink( void ) +{ + if ( IsCarried() ) + return; + + if ( IsDisabled() ) + { + // Don't heal or dispense ammo + SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT ); + + // stop healing everyone + ResetHealingTargets(); + return; + } + + float flRadius = GetDispenserRadius(); + + if ( m_flNextAmmoDispense <= gpGlobals->curtime ) + { + int iNumNearbyPlayers = 0; + + if ( GetOwner() ) + { + // find players in sphere, that are visible + if ( ( flRadius != m_flPrevRadius ) && m_hTouchTrigger.Get() ) + { + UTIL_SetSize( m_hTouchTrigger.Get(), Vector( -flRadius, -flRadius, -flRadius ), Vector( flRadius, flRadius, flRadius ) ); + } + } + + m_flPrevRadius = flRadius; + + Vector vecOrigin = GetAbsOrigin() + Vector(0,0,32); + + CBaseEntity *pListOfNearbyEntities[32]; + int iNumberOfNearbyEntities = UTIL_EntitiesInSphere( pListOfNearbyEntities, ARRAYSIZE( pListOfNearbyEntities ), vecOrigin, flRadius, FL_CLIENT ); + for ( int i=0;i<iNumberOfNearbyEntities;i++ ) + { + CTFPlayer *pPlayer = ToTFPlayer( pListOfNearbyEntities[i] ); + + if ( !pPlayer || !pPlayer->IsAlive() ) + continue; + + if ( pPlayer->GetTeamNumber() != GetTeamNumber() ) + { + if ( !pPlayer->IsPlayerClass( TF_CLASS_SPY ) || ( pPlayer->m_Shared.GetDisguiseTeam() != GetTeamNumber() ) ) + continue; + } + + DispenseAmmo( pPlayer ); + + iNumNearbyPlayers++; + } + + // Try to dispense more often when no players are around so we + // give it as soon as possible when a new player shows up +#ifdef STAGING_ONLY + float flNextAmmoDelay = IsMiniBuilding() ? DISPENSER_MINI_AMMO_THINK : 1.0; +#else + float flNextAmmoDelay = 1.0; +#endif + m_flNextAmmoDispense = gpGlobals->curtime + ( ( iNumNearbyPlayers > 0 ) ? flNextAmmoDelay : 0.1 ); + } + + // for each player in touching list + int iSize = m_hTouchingEntities.Count(); + bool bIsAnyTeammateTouching = false; + + if ( m_hTouchTrigger ) + { + for ( int i = iSize-1; i >= 0; i-- ) + { + EHANDLE hOther = m_hTouchingEntities[i]; + + CBaseEntity *pEnt = hOther.Get(); + if ( !pEnt ) + continue; + + // stop touching and healing a dead entity, or one that is grossly out of range (EndTouch() can be flakey) + float flDistSqr = (m_hTouchTrigger->WorldSpaceCenter() - pEnt->WorldSpaceCenter()).LengthSqr(); + Vector vecMins, vecMaxs; + m_hTouchTrigger->GetCollideable()->WorldSpaceSurroundingBounds( &vecMins, &vecMaxs ); + float flDoubleRadiusSqr = ( vecMaxs - vecMins ).LengthSqr(); + if ( !pEnt->IsAlive() || ( flDistSqr > flDoubleRadiusSqr ) ) + { + m_hTouchingEntities.FindAndRemove( hOther ); + StopHealing( pEnt ); + continue; + } + + bIsAnyTeammateTouching |= ( pEnt->IsPlayer() && pEnt->GetTeamNumber() == GetTeamNumber() ); + + + bool bHealingTarget = IsHealingTarget( pEnt ); + bool bValidHealTarget = CouldHealTarget( pEnt ); + + if ( bHealingTarget && !bValidHealTarget ) + { + // if we can't see them, remove them from healing list + // does nothing if we are not healing them already + StopHealing( pEnt ); + } + else if ( !bHealingTarget && bValidHealTarget ) + { + // if we can see them, add to healing list + // does nothing if we are healing them already + StartHealing( pEnt ); + } + } + } + + if ( bIsAnyTeammateTouching ) + { + if ( !m_spellTimer.HasStarted() ) + { + m_spellTimer.Start( tf_cart_spell_drop_rate.GetFloat() ); + } + + if ( !m_duckTimer.HasStarted() ) + { + m_duckTimer.Start( tf_cart_duck_drop_rate.GetFloat() ); + } + + if ( !m_soulTimer.HasStarted() ) + { + m_soulTimer.Start( tf_cart_soul_drop_rate.GetFloat() ); + } + } + else + { + m_spellTimer.Invalidate(); + m_duckTimer.Invalidate(); + m_soulTimer.Invalidate(); + } + + if ( m_spellTimer.HasStarted() && m_spellTimer.IsElapsed() ) + { + m_spellTimer.Start( tf_cart_spell_drop_rate.GetFloat() ); + DropSpellPickup(); + } + + if ( m_duckTimer.HasStarted() && m_duckTimer.IsElapsed() ) + { + m_duckTimer.Start( tf_cart_duck_drop_rate.GetFloat() ); + DropDuckPickup(); + } + + if ( m_soulTimer.HasStarted() && m_soulTimer.IsElapsed() ) + { + m_soulTimer.Start( tf_cart_soul_drop_rate.GetFloat() ); + DispenseSouls(); + } + + SetContextThink( &CObjectDispenser::DispenseThink, gpGlobals->curtime + 0.1, DISPENSE_CONTEXT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::StartTouch( CBaseEntity *pOther ) +{ + // add to touching entities + EHANDLE hOther = pOther; + m_hTouchingEntities.AddToTail( hOther ); + + if ( !IsBuilding() && !IsDisabled() && CouldHealTarget( pOther ) && !IsHealingTarget( pOther ) ) + { + // try to start healing them + StartHealing( pOther ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::Touch( CBaseEntity *pOther ) +{ + // We dont want to touch these + if ( pOther->IsSolidFlagSet( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) ) + return; + + // Handle hitting skybox (disappear). + const trace_t *pTrace = &CBaseEntity::GetTouchTrace(); + if( pTrace->surface.flags & SURF_SKY ) + { + UTIL_Remove( this ); + return; + } + +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::EndTouch( CBaseEntity *pOther ) +{ + // remove from touching entities + EHANDLE hOther = pOther; + m_hTouchingEntities.FindAndRemove( hOther ); + + // remove from healing list + StopHealing( pOther ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::ResetHealingTargets( void ) +{ + // tell all the players we're not healing them anymore + for ( int i = m_hHealingTargets.Count()-1 ; i >= 0 ; i-- ) + { + EHANDLE hEnt = m_hHealingTargets[i]; + CBaseEntity *pOther = hEnt.Get(); + + if ( pOther ) + { + StopHealing( pOther ); + } + } + +} + +//----------------------------------------------------------------------------- +// Purpose: Try to start healing this target +//----------------------------------------------------------------------------- +float CObjectDispenser::GetHealRate() const +{ +#ifdef STAGING_ONLY + float flHealRate = IsMiniBuilding() ? DISPENSER_MINI_HEAL_RATE : g_flDispenserHealRates[GetUpgradeLevel()]; +#else + float flHealRate = g_flDispenserHealRates[GetUpgradeLevel()]; +#endif + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetBuilder(), flHealRate, mult_dispenser_rate ); + + return flHealRate; +} + +//----------------------------------------------------------------------------- +// Purpose: Try to start healing this target +//----------------------------------------------------------------------------- +void CObjectDispenser::StartHealing( CBaseEntity *pOther ) +{ + if ( IsCarried() ) + return; + + AddHealingTarget( pOther ); + + CTFPlayer *pPlayer = ToTFPlayer( pOther ); + + if ( pPlayer ) + { + float flHealRate = GetHealRate(); + float flOverhealBonus = 1.0; + pPlayer->m_Shared.Heal( this, flHealRate, flOverhealBonus, 1.0, true, GetBuilder() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Stop healing this target +//----------------------------------------------------------------------------- +void CObjectDispenser::StopHealing( CBaseEntity *pOther ) +{ + if ( RemoveHealingTarget( pOther ) ) + { + CTFPlayer *pPlayer = ToTFPlayer( pOther ); + + if ( pPlayer ) + { + float flHealingDone = pPlayer->m_Shared.StopHealing( this ); + if ( GetBuilder() && pOther != GetBuilder() && flHealingDone > 0 ) + { + GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DISPENSER_HEAL_GRIND, floor( flHealingDone ) ); + + if ( GetBuilder()->GetTeam() == pOther->GetTeam() ) + { + // Strange Health Provided to Allies + EconEntity_OnOwnerKillEaterEvent( + dynamic_cast<CEconEntity *>( GetBuilder()->GetEntityForLoadoutSlot( LOADOUT_POSITION_PDA ) ), + GetBuilder(), + pPlayer, + kKillEaterEvent_HealingProvided, + (int)flHealingDone + ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Is this a valid heal target? and not already healing them? +//----------------------------------------------------------------------------- +bool CObjectDispenser::CouldHealTarget( CBaseEntity *pTarget ) +{ + if ( !HasSpawnFlags( SF_DISPENSER_IGNORE_LOS ) && !pTarget->FVisible( this, MASK_BLOCKLOS ) ) + return false; + + if ( pTarget->IsPlayer() && pTarget->IsAlive() ) + { + CTFPlayer *pTFPlayer = ToTFPlayer( pTarget ); + + // Don't heal while in purgatory. Players take damage constantly while down there in + // order to flush them out. If we're healing players down there, that goes against + // the purpose of the damage. + if ( pTFPlayer->IsInPurgatory() ) + return false; + + // don't heal enemies unless they are disguised as our team + int iTeam = GetTeamNumber(); + int iPlayerTeam = pTFPlayer->GetTeamNumber(); + + if ( iPlayerTeam != iTeam && pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) ) + { + iPlayerTeam = pTFPlayer->m_Shared.GetDisguiseTeam(); + } + + if ( iPlayerTeam != iTeam ) + { + return false; + } + + if ( HasSpawnFlags( SF_DISPENSER_DONT_HEAL_DISGUISED_SPIES ) ) + { + // if they're invis, no heals + if ( pTFPlayer->m_Shared.IsStealthed() ) + { + return false; + } + + // if they're disguised as enemy + if ( pTFPlayer->m_Shared.InCond( TF_COND_DISGUISED ) && + pTFPlayer->m_Shared.GetDisguiseTeam() != iTeam ) + { + return false; + } + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CObjectDispenser::GetDispenserRadius( void ) +{ + float flRadius = 64.f; + + if ( GetOwner() ) + { + CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( GetOwner(), flRadius, mult_dispenser_radius ); + } + + return flRadius; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectDispenser::AddHealingTarget( CBaseEntity *pOther ) +{ + // add to tail + EHANDLE hOther = pOther; + m_hHealingTargets.AddToTail( hOther ); + NetworkStateChanged(); + + // check how many healing targets we now have and possibly award an achievement + if ( m_hHealingTargets.Count() >= 3 && GetBuilder() ) + { + GetBuilder()->AwardAchievement( ACHIEVEMENT_TF_ENGINEER_DISPENSER_HEAL_GROUP ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CObjectDispenser::RemoveHealingTarget( CBaseEntity *pOther ) +{ + // remove + EHANDLE hOther = pOther; + bool bFound = m_hHealingTargets.FindAndRemove( hOther ); + NetworkStateChanged(); + + return bFound; +} + +//----------------------------------------------------------------------------- +// Purpose: Are we healing this target already +//----------------------------------------------------------------------------- +bool CObjectDispenser::IsHealingTarget( CBaseEntity *pTarget ) +{ + EHANDLE hOther = pTarget; + return m_hHealingTargets.HasElement( hOther ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CObjectDispenser::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf( tempstr, sizeof( tempstr ),"Metal: %d", m_iAmmoMetal.Get() ); + EntityText(text_offset,tempstr,0); + text_offset++; + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void CObjectDispenser::MakeCarriedObject( CTFPlayer *pCarrier ) +{ + if ( m_hTouchTrigger.Get() ) + { + UTIL_Remove( m_hTouchTrigger ); + } + + ResetHealingTargets(); + + m_hTouchingEntities.Purge(); + + StopSound( "Building_Dispenser.Idle" ); + + BaseClass::MakeCarriedObject( pCarrier ); +} + + +//----------------------------------------------------------------------------- +// Cart Dispenser +//----------------------------------------------------------------------------- + +BEGIN_DATADESC( CObjectCartDispenser ) + DEFINE_INPUTFUNC( FIELD_INTEGER, "FireHalloweenBonus", InputFireHalloweenBonus ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDispenserLevel", InputSetDispenserLevel ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CObjectCartDispenser, DT_ObjectCartDispenser ) +END_SEND_TABLE() + +LINK_ENTITY_TO_CLASS( mapobj_cart_dispenser, CObjectCartDispenser ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CObjectCartDispenser::CObjectCartDispenser() +{ + m_bUseGenerateMetalSound = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectCartDispenser::Spawn( void ) +{ + // This cast is for the benefit of GCC + m_fObjectFlags |= (int)OF_DOESNT_HAVE_A_MODEL; + m_takedamage = DAMAGE_NO; + m_iUpgradeLevel = 1; + + TFGameRules()->OnDispenserBuilt( this ); +} + +//----------------------------------------------------------------------------- +// Purpose: Finished building +//----------------------------------------------------------------------------- +void CObjectCartDispenser::OnGoActive( void ) +{ + BaseClass::OnGoActive(); + + SetModel( "" ); +} + +//----------------------------------------------------------------------------- +// Spawn the vgui control screens on the object +//----------------------------------------------------------------------------- +void CObjectCartDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) +{ + // no panels + return; +} + +//----------------------------------------------------------------------------- +// Don't decrement our metal count +//----------------------------------------------------------------------------- +int CObjectCartDispenser::DispenseMetal( CTFPlayer *pPlayer ) +{ + int iMetal = pPlayer->GiveAmmo( MIN( m_iAmmoMetal, DISPENSER_DROP_METAL ), TF_AMMO_METAL, false, kAmmoSource_DispenserOrCart ); + return iMetal; +} + + +void CObjectCartDispenser::DropSpellPickup() +{ + if ( TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_HIGHTOWER ) ) + { + TFGameRules()->DropSpellPickup( GetAbsOrigin() ); + } +} + +void CObjectCartDispenser::DropDuckPickup() +{ + if ( TFGameRules()->IsHolidayActive( kHoliday_EOTL ) && TFGameRules()->ShouldDropBonusDuck() ) + { + TFGameRules()->DropBonusDuck( GetAbsOrigin() ); + } +} + +void CObjectCartDispenser::DispenseSouls() +{ + // Give a soul to the entire team + if ( TFGameRules() && TFGameRules()->IsHolidayActive( kHoliday_Halloween ) ) + { + TFGameRules()->DropHalloweenSoulPackToTeam( 1, GetAbsOrigin(), GetTeamNumber(), TEAM_SPECTATOR ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectCartDispenser::SetModel( const char *pModel ) +{ + CBaseObject::SetModel( pModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: Give players near the dispenser a bonus +//----------------------------------------------------------------------------- +void CObjectCartDispenser::InputFireHalloweenBonus( inputdata_t &inputdata ) +{ + for ( int i = m_hHealingTargets.Count()-1 ; i >= 0 ; i-- ) + { + EHANDLE hEnt = m_hHealingTargets[i]; + CTFPlayer *pTFPlayer = ToTFPlayer( hEnt.Get() ); + + if ( pTFPlayer ) + { + pTFPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_CTF_CAPTURE, inputdata.value.Int() ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectCartDispenser::InputSetDispenserLevel( inputdata_t &inputdata ) +{ + int iLevel = inputdata.value.Int(); + + if ( iLevel >= 1 && iLevel <= 3 ) + { + m_iUpgradeLevel = iLevel; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectCartDispenser::InputEnable( inputdata_t &inputdata ) +{ + SetDisabled( false ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CObjectCartDispenser::InputDisable( inputdata_t &inputdata ) +{ + SetDisabled( true ); +} + + |