//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Bullseyes act as targets for other NPC's to attack and to trigger // events // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "basecombatcharacter.h" #include "ai_basenpc.h" #include "decals.h" #include "filters.h" #include "npc_bullseye.h" #include "collisionutils.h" #include "igamesystem.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" class CBullseyeList : public CAutoGameSystem { public: CBullseyeList( char const *name ) : CAutoGameSystem( name ) { } virtual void LevelShutdownPostEntity() { Clear(); } void Clear() { m_list.Purge(); } void AddToList( CNPC_Bullseye *pBullseye ); void RemoveFromList( CNPC_Bullseye *pBullseye ); CUtlVector< CNPC_Bullseye * > m_list; }; void CBullseyeList::AddToList( CNPC_Bullseye *pBullseye ) { m_list.AddToTail( pBullseye ); } void CBullseyeList::RemoveFromList( CNPC_Bullseye *pBullseye ) { int index = m_list.Find( pBullseye ); if ( index != m_list.InvalidIndex() ) { m_list.FastRemove( index ); } } CBullseyeList g_BullseyeList( "CBullseyeList" ); int FindBullseyesInCone( CBaseEntity **pList, int listMax, const Vector &coneOrigin, const Vector &coneAxis, float coneAngleCos, float coneLength ) { if ( listMax <= 0 ) return 0; int count = 0; for ( int i = g_BullseyeList.m_list.Count() - 1; i >= 0; --i ) { CNPC_Bullseye *pTest = g_BullseyeList.m_list[i]; if ( IsPointInCone( pTest->GetAbsOrigin(), coneOrigin, coneAxis, coneAngleCos, coneLength ) ) { pList[count] = pTest; count++; if ( count >= listMax ) break; } } return count; } ConVar sk_bullseye_health( "sk_bullseye_health","0"); BEGIN_DATADESC( CNPC_Bullseye ) DEFINE_FIELD( m_hPainPartner, FIELD_EHANDLE ), DEFINE_KEYFIELD( m_fAutoaimRadius, FIELD_FLOAT, "autoaimradius" ), DEFINE_KEYFIELD( m_flFieldOfView, FIELD_FLOAT, "minangle" ), DEFINE_KEYFIELD( m_flMinDistValidEnemy, FIELD_FLOAT, "mindist" ), // DEFINE_FIELD( m_bPerfectAccuracy, FIELD_BOOLEAN ), // Don't save // Function Pointers DEFINE_THINKFUNC( BullseyeThink ), DEFINE_INPUTFUNC( FIELD_VOID, "InputTargeted", InputTargeted ), DEFINE_INPUTFUNC( FIELD_VOID, "InputReleased", InputReleased ), // Outputs DEFINE_OUTPUT( m_OnTargeted, "OnTargeted"), DEFINE_OUTPUT( m_OnReleased, "OnReleased"), END_DATADESC() LINK_ENTITY_TO_CLASS( npc_bullseye, CNPC_Bullseye ); //----------------------------------------------------------------------------- // Purpose: Constructor //----------------------------------------------------------------------------- CNPC_Bullseye::CNPC_Bullseye( void ) { m_takedamage = DAMAGE_YES; m_iHealth = sk_bullseye_health.GetFloat(); m_hPainPartner = NULL; g_BullseyeList.AddToList( this ); m_flFieldOfView = 360; m_flMinDistValidEnemy = 0; } CNPC_Bullseye::~CNPC_Bullseye( void ) { g_BullseyeList.RemoveFromList( this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Bullseye::Precache( void ) { BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Bullseye::Spawn( void ) { Precache(); // This is a dummy model that is never used! UTIL_SetSize(this, Vector(-16,-16,-16), Vector(16,16,16)); SetMoveType( MOVETYPE_NONE ); SetBloodColor( BLOOD_COLOR_RED ); ClearEffects(); SetGravity( 0.0 ); m_flFieldOfView = cos( DEG2RAD(m_flFieldOfView) / 2.0 ); //Got blood? if ( m_spawnflags & SF_BULLSEYE_BLEED ) { SetBloodColor(BLOOD_COLOR_RED); } else { SetBloodColor(DONT_BLEED); } AddFlag( FL_NPC ); AddEFlags( EFL_NO_DISSOLVE ); SetThink( &CNPC_Bullseye::BullseyeThink ); SetNextThink( gpGlobals->curtime + 0.1f ); SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); if( m_spawnflags & SF_BULLSEYE_NONSOLID ) { AddSolidFlags( FSOLID_NOT_SOLID ); } if ( m_spawnflags & SF_BULLSEYE_VPHYSICSSHADOW ) { VPhysicsInitShadow( false, false ); } if( m_spawnflags & SF_BULLSEYE_NODAMAGE ) { m_takedamage = DAMAGE_NO; } else { m_takedamage = DAMAGE_YES; } AddEffects( EF_NODRAW ); //Check our water level PhysicsCheckWater(); CapabilitiesAdd( bits_CAP_SIMPLE_RADIUS_DAMAGE ); m_iMaxHealth = GetHealth(); if( m_fAutoaimRadius > 0.0f ) { // Make this an aimtarget, since it has some autoaim influence. AddFlag(FL_AIMTARGET); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Bullseye::Activate( void ) { BaseClass::Activate(); if ( m_spawnflags & SF_BULLSEYE_PERFECTACC ) { m_bPerfectAccuracy = true; } else { m_bPerfectAccuracy = false; } } //------------------------------------------------------------------------------ // Purpose : Override so doesn't fall to ground when killed //------------------------------------------------------------------------------ void CNPC_Bullseye::Event_Killed( const CTakeDamageInfo &info ) { BaseClass::Event_Killed( info ); if( GetParent() ) { if( GetParent()->ClassMatches("prop_combine_ball") ) { // If this bullseye is parented to a combine ball, explode the combine ball // and remove this bullseye. variant_t emptyVariant; GetParent()->AcceptInput( "explode", this, this, emptyVariant, 0 ); // Unhook. SetParent(NULL); UTIL_Remove(this); return; } } SetMoveType( MOVETYPE_NONE ); AddSolidFlags( FSOLID_NOT_SOLID ); UTIL_SetSize(this, vec3_origin, vec3_origin ); SetNextThink( gpGlobals->curtime + 0.1f ); SetThink( &CBaseEntity::SUB_Remove ); } //------------------------------------------------------------------------------ // Purpose : Override base implimentation to let decals pass through // me onto the surface beneath // Input : // Output : //------------------------------------------------------------------------------ void CNPC_Bullseye::DecalTrace( trace_t *pOldTrace, char const *decalName ) { int index = decalsystem->GetDecalIndexForName( decalName ); if ( index < 0 ) return; // Get direction of original trace Vector vTraceDir = pOldTrace->endpos - pOldTrace->startpos; VectorNormalize(vTraceDir); // Create a new trace that passes through me Vector vStartTrace = pOldTrace->endpos - (1.0 * vTraceDir); Vector vEndTrace = pOldTrace->endpos + (MAX_TRACE_LENGTH * vTraceDir); trace_t pNewTrace; AI_TraceLine(vStartTrace, vEndTrace, MASK_SHOT, this, COLLISION_GROUP_NONE, &pNewTrace); CBroadcastRecipientFilter filter; te->Decal( filter, 0.0, &pNewTrace.endpos, &pNewTrace.startpos, ENTINDEX( pNewTrace.m_pEnt ), pNewTrace.hitbox, index ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Bullseye::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) { // Get direction of original trace Vector vTraceDir = pTrace->endpos - pTrace->startpos; VectorNormalize(vTraceDir); // Create a new trace that passes through me Vector vStartTrace = pTrace->endpos - (1.0 * vTraceDir); Vector vEndTrace = pTrace->endpos + (MAX_TRACE_LENGTH * vTraceDir); trace_t pNewTrace; AI_TraceLine(vStartTrace, vEndTrace, MASK_SHOT, this, COLLISION_GROUP_NONE, &pNewTrace); CBaseEntity *pEntity = pNewTrace.m_pEnt; // Only do this for BSP model entities if ( ( pEntity ) && ( pEntity->IsBSPModel() == false ) ) return; BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName ); } //----------------------------------------------------------------------------- // Purpose: // // // Output : //----------------------------------------------------------------------------- Class_T CNPC_Bullseye::Classify( void ) { return CLASS_BULLSEYE; } void CNPC_Bullseye::OnRestore( void ) { if ( m_spawnflags & SF_BULLSEYE_VPHYSICSSHADOW ) { IPhysicsObject *pObject = VPhysicsGetObject(); if ( pObject == NULL ) { VPhysicsInitShadow( false, false ); } } BaseClass::OnRestore(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_Bullseye::BullseyeThink( void ) { ClearCondition( COND_LIGHT_DAMAGE ); ClearCondition( COND_HEAVY_DAMAGE ); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_Bullseye::CanBecomeRagdoll() { return false; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- bool CNPC_Bullseye::CanBeAnEnemyOf( CBaseEntity *pEnemy ) { static const float flFullFov = cos( DEG2RAD(360) / 2.0 ); if ( fabsf( m_flFieldOfView - flFullFov ) > .01 ) { if ( !FInViewCone( pEnemy ) ) { return false; } } if ( m_flMinDistValidEnemy > 0 ) { float distSq = ( GetAbsOrigin().AsVector2D() - pEnemy->GetAbsOrigin().AsVector2D() ).LengthSqr(); if ( distSq < Square( m_flMinDistValidEnemy ) ) { return false; } } return BaseClass::CanBeAnEnemyOf( pEnemy ); } //----------------------------------------------------------------------------- // Purpose: Bullseyes should always report light damage if any amount of damage is taken // Input : fDamage - amount of damage // bitsDamageType - damage type //----------------------------------------------------------------------------- bool CNPC_Bullseye::IsLightDamage( const CTakeDamageInfo &info ) { return ( info.GetDamage() > 0 ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pAttacker - // flDamage - // &vecDir - // *ptr - // bitsDamageType - //----------------------------------------------------------------------------- void CNPC_Bullseye::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) { //If specified, we must be the enemy of the target if ( m_spawnflags & SF_BULLSEYE_ENEMYDAMAGEONLY ) { CAI_BaseNPC *pInstigator = info.GetAttacker()->MyNPCPointer(); if ( pInstigator == NULL ) return; if ( pInstigator->GetEnemy() != this ) return; } //We can bleed if we want to, we can leave decals behind... if ( ( m_spawnflags & SF_BULLSEYE_BLEED ) && ( m_takedamage == DAMAGE_NO ) ) { TraceBleed( info.GetDamage(), vecDir, ptr, info.GetDamageType() ); } BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pInflictor - // *pAttacker - // flDamage - // bitsDamageType - // Output : int //----------------------------------------------------------------------------- int CNPC_Bullseye::OnTakeDamage( const CTakeDamageInfo &info ) { SetNextThink( gpGlobals->curtime ); //If specified, we must be the enemy of the target if ( m_spawnflags & SF_BULLSEYE_ENEMYDAMAGEONLY ) { CAI_BaseNPC *pInstigator = info.GetAttacker()->MyNPCPointer(); if ( pInstigator == NULL ) return 0; if ( pInstigator->GetEnemy() != this ) return 0; } //If we're a pain proxy, send the damage through if ( m_hPainPartner != NULL ) { m_hPainPartner->TakeDamage( info ); //Fire all pain indicators but take no real damage CTakeDamageInfo subInfo = info; subInfo.SetDamage( 0 ); return BaseClass::OnTakeDamage( subInfo ); } return BaseClass::OnTakeDamage( info ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pOther - //----------------------------------------------------------------------------- void CNPC_Bullseye::SetPainPartner( CBaseEntity *pOther ) { m_hPainPartner = pOther; } void CNPC_Bullseye::InputTargeted( inputdata_t &inputdata ) { m_OnTargeted.FireOutput( inputdata.pActivator, inputdata.pCaller, 0 ); } void CNPC_Bullseye::InputReleased( inputdata_t &inputdata ) { m_OnReleased.FireOutput( inputdata.pActivator, inputdata.pCaller, 0 ); }