//========= 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(this) != NULL ) { UTIL_Remove( m_hTouchTrigger.Get() ); m_hTouchTrigger = NULL; } float flRadius = GetDispenserRadius(); if ( m_iszCustomTouchTrigger != NULL_STRING ) { m_hTouchTrigger = dynamic_cast ( 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;iIsAlive() ) 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( 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 ); }