diff options
Diffstat (limited to 'mp/src/game/server/episodic/npc_combine_cannon.cpp')
| -rw-r--r-- | mp/src/game/server/episodic/npc_combine_cannon.cpp | 1335 |
1 files changed, 1335 insertions, 0 deletions
diff --git a/mp/src/game/server/episodic/npc_combine_cannon.cpp b/mp/src/game/server/episodic/npc_combine_cannon.cpp new file mode 100644 index 00000000..e4c3ba89 --- /dev/null +++ b/mp/src/game/server/episodic/npc_combine_cannon.cpp @@ -0,0 +1,1335 @@ +//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "ammodef.h"
+#include "ai_memory.h"
+#include "weapon_rpg.h"
+#include "effect_color_tables.h"
+#include "te_effect_dispatch.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern const char* g_pModelNameLaser;
+
+// No model, impervious to damage.
+#define SF_STARTDISABLED (1 << 19)
+
+#define CANNON_PAINT_ENEMY_TIME 1.0f
+#define CANNON_SUBSEQUENT_PAINT_TIME 0.4f
+#define CANNON_PAINT_NPC_TIME_NOISE 1.0f
+
+#define NUM_ANCILLARY_BEAMS 4
+
+int gHaloTexture = 0;
+
+//-----------------------------------------------------------------------------
+//
+// Combine Cannon
+//
+//-----------------------------------------------------------------------------
+class CNPC_Combine_Cannon : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Combine_Cannon, CAI_BaseNPC );
+
+public:
+ CNPC_Combine_Cannon( void );
+ virtual void Precache( void );
+ virtual void Spawn( void );
+ virtual Class_T Classify( void );
+ virtual float MaxYawSpeed( void );
+ virtual Vector EyePosition( void );
+ virtual void UpdateOnRemove( void );
+ virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ virtual bool QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC = false );
+ virtual void StartTask( const Task_t *pTask );
+ virtual void RunTask( const Task_t *pTask );
+ virtual int RangeAttack1Conditions( float flDot, float flDist );
+ virtual int SelectSchedule( void );
+ virtual int TranslateSchedule( int scheduleType );
+ virtual void PrescheduleThink( void );
+ virtual bool FCanCheckAttacks ( void );
+ virtual int Restore( IRestore &restore );
+ virtual void OnScheduleChange( void );
+ virtual bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
+
+ virtual bool WeaponLOSCondition( const Vector &ownerPos, const Vector &targetPos, bool bSetConditions) { return true; }
+
+ virtual int GetSoundInterests( void ) { return (SOUND_PLAYER|SOUND_COMBAT|SOUND_DANGER); }
+
+ virtual bool ShouldNotDistanceCull( void ) { return true; }
+
+ virtual void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
+
+ virtual const char *GetTracerType( void ) { return "HelicopterTracer"; }
+
+private:
+
+ void ScopeGlint( void );
+ void AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn );
+
+ float GetRefireTime( void ) { return 0.1f; }
+
+ bool IsLaserOn( void ) { return m_pBeam != NULL; }
+ bool FireBullet( const Vector &vecTarget, bool bDirectShot );
+ Vector DesiredBodyTarget( CBaseEntity *pTarget );
+ Vector LeadTarget( CBaseEntity *pTarget );
+ Vector GetBulletOrigin( void );
+
+ static const char *pAttackSounds[];
+
+ void ClearTargetGroup( void );
+
+ float GetWaitTimePercentage( float flTime, bool fLinear );
+
+ void GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress );
+
+ bool VerifyShot( CBaseEntity *pTarget );
+
+ void SetSweepTarget( const char *pszTarget );
+
+ // Inputs
+ void InputEnableSniper( inputdata_t &inputdata );
+ void InputDisableSniper( inputdata_t &inputdata );
+
+ void LaserOff( void );
+ void LaserOn( const Vector &vecTarget, const Vector &vecDeviance );
+
+ void PaintTarget( const Vector &vecTarget, float flPaintTime );
+
+private:
+
+ void CreateLaser( void );
+ void CreateAncillaryBeams( void );
+ void UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis );
+
+ int m_iAmmoType;
+ float m_flBarrageDuration;
+ Vector m_vecPaintCursor;
+ float m_flPaintTime;
+
+ CHandle<CBeam> m_pBeam;
+ CHandle<CBeam> m_pAncillaryBeams[NUM_ANCILLARY_BEAMS];
+ EHANDLE m_hBarrageTarget;
+
+ bool m_fEnabled;
+ Vector m_vecPaintStart; // used to track where a sweep starts for the purpose of interpolating.
+ float m_flTimeLastAttackedPlayer;
+ float m_flTimeLastShotMissed;
+ float m_flSightDist;
+
+ DEFINE_CUSTOM_AI;
+
+ DECLARE_DATADESC();
+};
+
+LINK_ENTITY_TO_CLASS( npc_combine_cannon, CNPC_Combine_Cannon );
+
+//=========================================================
+//=========================================================
+BEGIN_DATADESC( CNPC_Combine_Cannon )
+
+ DEFINE_FIELD( m_fEnabled, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vecPaintStart, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flPaintTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecPaintCursor, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_pBeam, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flTimeLastAttackedPlayer, FIELD_TIME ),
+ DEFINE_FIELD( m_flTimeLastShotMissed, FIELD_TIME ),
+ DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flBarrageDuration, FIELD_TIME ),
+ DEFINE_FIELD( m_hBarrageTarget, FIELD_EHANDLE ),
+
+ DEFINE_ARRAY( m_pAncillaryBeams, FIELD_EHANDLE, NUM_ANCILLARY_BEAMS ),
+
+ DEFINE_KEYFIELD( m_flSightDist, FIELD_FLOAT, "sightdist" ),
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "EnableSniper", InputEnableSniper ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "DisableSniper", InputDisableSniper ),
+
+END_DATADESC()
+
+
+//=========================================================
+// Private conditions
+//=========================================================
+enum Sniper_Conds
+{
+ COND_CANNON_ENABLED = LAST_SHARED_CONDITION,
+ COND_CANNON_DISABLED,
+ COND_CANNON_NO_SHOT,
+};
+
+
+//=========================================================
+// schedules
+//=========================================================
+enum
+{
+ SCHED_CANNON_CAMP = LAST_SHARED_SCHEDULE,
+ SCHED_CANNON_ATTACK,
+ SCHED_CANNON_DISABLEDWAIT,
+ SCHED_CANNON_SNAPATTACK,
+};
+
+//=========================================================
+// tasks
+//=========================================================
+enum
+{
+ TASK_CANNON_PAINT_ENEMY = LAST_SHARED_TASK,
+ TASK_CANNON_PAINT_DECOY,
+ TASK_CANNON_ATTACK_CURSOR,
+};
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_Combine_Cannon::CNPC_Combine_Cannon( void ) :
+ m_pBeam( NULL ),
+ m_hBarrageTarget( NULL )
+{
+#ifdef _DEBUG
+ m_vecPaintCursor.Init();
+ m_vecPaintStart.Init();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CNPC_Combine_Cannon::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
+{
+ Disposition_t disp = IRelationType(pEntity);
+ if ( disp != D_HT )
+ {
+ // Don't bother with anything I wouldn't shoot.
+ return false;
+ }
+
+ if ( !FInViewCone(pEntity) )
+ {
+ // Yes, this does call FInViewCone twice a frame for all entities checked for
+ // visibility, but doing this allows us to cut out a bunch of traces that would
+ // be done by VerifyShot for entities that aren't even in our viewcone.
+ return false;
+ }
+
+ if ( VerifyShot( pEntity ) )
+ return BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC );
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Hide the beams
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::LaserOff( void )
+{
+ if ( m_pBeam != NULL )
+ {
+ m_pBeam->TurnOn();
+ }
+
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] == NULL )
+ continue;
+
+ m_pAncillaryBeams[i]->TurnOn();
+ }
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Switch on the laser and point it at a direction
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::LaserOn( const Vector &vecTarget, const Vector &vecDeviance )
+{
+ if ( m_pBeam != NULL )
+ {
+ m_pBeam->TurnOff();
+
+ // Don't aim right at the guy right now.
+ Vector vecInitialAim;
+
+ if( vecDeviance == vec3_origin )
+ {
+ // Start the aim where it last left off!
+ vecInitialAim = m_vecPaintCursor;
+ }
+ else
+ {
+ vecInitialAim = vecTarget;
+ }
+
+ vecInitialAim.x += random->RandomFloat( -vecDeviance.x, vecDeviance.x );
+ vecInitialAim.y += random->RandomFloat( -vecDeviance.y, vecDeviance.y );
+ vecInitialAim.z += random->RandomFloat( -vecDeviance.z, vecDeviance.z );
+
+ m_pBeam->SetStartPos( GetBulletOrigin() );
+ m_pBeam->SetEndPos( vecInitialAim );
+
+ m_vecPaintStart = vecInitialAim;
+ }
+
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] == NULL )
+ continue;
+
+ m_pAncillaryBeams[i]->TurnOff();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Crikey!
+//-----------------------------------------------------------------------------
+float CNPC_Combine_Cannon::GetWaitTimePercentage( float flTime, bool fLinear )
+{
+ float flElapsedTime;
+ float flTimeParameter;
+
+ flElapsedTime = flTime - (GetWaitFinishTime() - gpGlobals->curtime);
+
+ flTimeParameter = ( flElapsedTime / flTime );
+
+ if( fLinear )
+ {
+ return flTimeParameter;
+ }
+ else
+ {
+ return (1 + sin( (M_PI * flTimeParameter) - (M_PI / 2) ) ) / 2;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::GetPaintAim( const Vector &vecStart, const Vector &vecGoal, float flParameter, Vector *pProgress )
+{
+ // Quaternions
+ Vector vecIdealDir;
+ QAngle vecIdealAngles;
+ QAngle vecCurrentAngles;
+ Vector vecCurrentDir;
+ Vector vecBulletOrigin = GetBulletOrigin();
+
+ // vecIdealDir is where the gun should be aimed when the painting
+ // time is up. This can be approximate. This is only for drawing the
+ // laser, not actually aiming the weapon. A large discrepancy will look
+ // bad, though.
+ vecIdealDir = vecGoal - vecBulletOrigin;
+ VectorNormalize(vecIdealDir);
+
+ // Now turn vecIdealDir into angles!
+ VectorAngles( vecIdealDir, vecIdealAngles );
+
+ // This is the vector of the beam's current aim.
+ vecCurrentDir = m_vecPaintStart - vecBulletOrigin;
+ VectorNormalize(vecCurrentDir);
+
+ // Turn this to angles, too.
+ VectorAngles( vecCurrentDir, vecCurrentAngles );
+
+ Quaternion idealQuat;
+ Quaternion currentQuat;
+ Quaternion aimQuat;
+
+ AngleQuaternion( vecIdealAngles, idealQuat );
+ AngleQuaternion( vecCurrentAngles, currentQuat );
+
+ QuaternionSlerp( currentQuat, idealQuat, flParameter, aimQuat );
+
+ QuaternionAngles( aimQuat, vecCurrentAngles );
+
+ // Rebuild the current aim vector.
+ AngleVectors( vecCurrentAngles, &vecCurrentDir );
+
+ *pProgress = vecCurrentDir;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::CreateLaser( void )
+{
+ if ( m_pBeam != NULL )
+ return;
+
+ m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
+
+ m_pBeam->SetColor( 0, 100, 255 );
+
+ m_pBeam->PointsInit( vec3_origin, GetBulletOrigin() );
+ m_pBeam->SetBrightness( 255 );
+ m_pBeam->SetNoise( 0 );
+ m_pBeam->SetWidth( 1.0f );
+ m_pBeam->SetEndWidth( 0 );
+ m_pBeam->SetScrollRate( 0 );
+ m_pBeam->SetFadeLength( 0 );
+ m_pBeam->SetHaloTexture( gHaloTexture );
+ m_pBeam->SetHaloScale( 16.0f );
+
+ // Think faster while painting
+ SetNextThink( gpGlobals->curtime + 0.02f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::CreateAncillaryBeams( void )
+{
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] != NULL )
+ continue;
+
+ m_pAncillaryBeams[i] = CBeam::BeamCreate( g_pModelNameLaser, 2.0f );
+ m_pAncillaryBeams[i]->SetColor( 0, 100, 255 );
+
+ m_pAncillaryBeams[i]->PointsInit( vec3_origin, GetBulletOrigin() );
+ m_pAncillaryBeams[i]->SetBrightness( 255 );
+ m_pAncillaryBeams[i]->SetNoise( 0 );
+ m_pAncillaryBeams[i]->SetWidth( 1.0f );
+ m_pAncillaryBeams[i]->SetEndWidth( 0 );
+ m_pAncillaryBeams[i]->SetScrollRate( 0 );
+ m_pAncillaryBeams[i]->SetFadeLength( 0 );
+ m_pAncillaryBeams[i]->SetHaloTexture( gHaloTexture );
+ m_pAncillaryBeams[i]->SetHaloScale( 16.0f );
+ m_pAncillaryBeams[i]->TurnOff();
+ }
+}
+
+#define LINE_LENGTH 1600.0f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : flConvergencePerc -
+// vecBasis -
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::UpdateAncillaryBeams( float flConvergencePerc, const Vector &vecOrigin, const Vector &vecBasis )
+{
+ // Multiple beams deviate from the basis direction by a certain number of degrees and "converge"
+ // at the basis vector over a duration of time, the position in that duration expressed by
+ // flConvergencePerc. The beams are most deviated at 0 and fully converged at 1.
+
+ float flRotationOffset = (2*M_PI)/(float)NUM_ANCILLARY_BEAMS; // Degrees separating each beam, in radians
+ float flDeviation = DEG2RAD(90) * ( 1.0f - flConvergencePerc );
+ float flOffset;
+ Vector vecFinal;
+ Vector vecOffset;
+
+ matrix3x4_t matRotate;
+ QAngle vecAngles;
+ VectorAngles( vecBasis, vecAngles );
+ vecAngles[PITCH] += 90.0f;
+ AngleMatrix( vecAngles, vecOrigin, matRotate );
+
+ trace_t tr;
+
+ float flScale = LINE_LENGTH * flDeviation;
+
+ // For each beam, find its offset and trace outwards to place its endpoint
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( flConvergencePerc >= 0.99f )
+ {
+ m_pAncillaryBeams[i]->TurnOn();
+ continue;
+ }
+
+ m_pAncillaryBeams[i]->TurnOff();
+
+ // Find the number of radians offset we are
+ flOffset = (float) i * flRotationOffset + DEG2RAD( 30.0f );
+ flOffset += (M_PI/8.0f) * sin( gpGlobals->curtime * 3.0f );
+
+ // Construct a circle that's also offset by the line's length
+ vecOffset.x = cos( flOffset ) * flScale;
+ vecOffset.y = sin( flOffset ) * flScale;
+ vecOffset.z = LINE_LENGTH;
+
+ // Rotate this whole thing into the space of the basis vector
+ VectorRotate( vecOffset, matRotate, vecFinal );
+ VectorNormalize( vecFinal );
+
+ // Trace a line down that vector to find where we'll eventually stop our line
+ UTIL_TraceLine( vecOrigin, vecOrigin + ( vecFinal * LINE_LENGTH ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ // Move the beam to that position
+ m_pAncillaryBeams[i]->SetBrightness( 255.0f * flConvergencePerc );
+ m_pAncillaryBeams[i]->SetEndPos( tr.startpos );
+ m_pAncillaryBeams[i]->SetStartPos( tr.endpos );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Sweep the laser sight towards the point where the gun should be aimed
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::PaintTarget( const Vector &vecTarget, float flPaintTime )
+{
+ // vecStart is the barrel of the gun (or the laser sight)
+ Vector vecStart = GetBulletOrigin();
+
+ // keep painttime from hitting 0 exactly.
+ flPaintTime = MAX( flPaintTime, 0.000001f );
+
+ // Find out where we are in the arc of the paint duration
+ float flPaintPerc = GetWaitTimePercentage( flPaintTime, false );
+
+ ScopeGlint();
+
+ // Find out where along our line we're painting
+ Vector vecCurrentDir;
+ float flInterp = RemapValClamped( flPaintPerc, 0.0f, 0.5f, 0.0f, 1.0f );
+ flInterp = clamp( flInterp, 0.0f, 1.0f );
+ GetPaintAim( m_vecPaintStart, vecTarget, flInterp, &vecCurrentDir );
+
+#define THRESHOLD 0.9f
+ float flNoiseScale;
+
+ if ( flPaintPerc >= THRESHOLD )
+ {
+ flNoiseScale = 1 - (1 / (1 - THRESHOLD)) * ( flPaintPerc - THRESHOLD );
+ }
+ else if ( flPaintPerc <= 1 - THRESHOLD )
+ {
+ flNoiseScale = flPaintPerc / (1 - THRESHOLD);
+ }
+ else
+ {
+ flNoiseScale = 1;
+ }
+
+ // mult by P
+ vecCurrentDir.x += flNoiseScale * ( sin( 3 * M_PI * gpGlobals->curtime ) * 0.0006 );
+ vecCurrentDir.y += flNoiseScale * ( sin( 2 * M_PI * gpGlobals->curtime + 0.5 * M_PI ) * 0.0006 );
+ vecCurrentDir.z += flNoiseScale * ( sin( 1.5 * M_PI * gpGlobals->curtime + M_PI ) * 0.0006 );
+
+ // Find where our center is
+ trace_t tr;
+ UTIL_TraceLine( vecStart, vecStart + vecCurrentDir * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+ m_vecPaintCursor = tr.endpos;
+
+ // Update our beam position
+ m_pBeam->SetEndPos( tr.startpos );
+ m_pBeam->SetStartPos( tr.endpos );
+ m_pBeam->SetBrightness( 255.0f * flPaintPerc );
+ m_pBeam->RelinkBeam();
+
+ // Find points around that center point and make our designators converge at that point over time
+ UpdateAncillaryBeams( flPaintPerc, vecStart, vecCurrentDir );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::OnScheduleChange( void )
+{
+ LaserOff();
+
+ m_hBarrageTarget = NULL;
+
+ BaseClass::OnScheduleChange();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::Precache( void )
+{
+ PrecacheModel("models/combine_soldier.mdl");
+ PrecacheModel("effects/bluelaser1.vmt");
+
+ gHaloTexture = PrecacheModel("sprites/light_glow03.vmt");
+
+ PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::Spawn( void )
+{
+ Precache();
+
+ /// HACK:
+ SetModel( "models/combine_soldier.mdl" );
+
+ // Setup our ancillary beams but keep them hidden for now
+ CreateLaser();
+ CreateAncillaryBeams();
+
+ m_iAmmoType = GetAmmoDef()->Index( "CombineHeavyCannon" );
+
+ SetHullType( HULL_HUMAN );
+ SetHullSizeNormal();
+
+ UTIL_SetSize( this, Vector( -16, -16 , 0 ), Vector( 16, 16, 64 ) );
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_FLY );
+ m_bloodColor = DONT_BLEED;
+ m_iHealth = 10;
+ m_flFieldOfView = DOT_45DEGREE;
+ m_NPCState = NPC_STATE_NONE;
+
+ if( HasSpawnFlags( SF_STARTDISABLED ) )
+ {
+ m_fEnabled = false;
+ }
+ else
+ {
+ m_fEnabled = true;
+ }
+
+ CapabilitiesClear();
+ CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SIMPLE_RADIUS_DAMAGE );
+
+ m_HackedGunPos = Vector ( 0, 0, 0 );
+
+ AddSpawnFlags( SF_NPC_LONG_RANGE | SF_NPC_ALWAYSTHINK );
+
+ NPCInit();
+
+ // Limit our look distance
+ SetDistLook( m_flSightDist );
+
+ AddEffects( EF_NODRAW );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+
+ // Point the cursor straight ahead so that the sniper's
+ // first sweep of the laser doesn't look weird.
+ Vector vecForward;
+ AngleVectors( GetLocalAngles(), &vecForward );
+ m_vecPaintCursor = GetBulletOrigin() + vecForward * 1024;
+
+ // none!
+ GetEnemies()->SetFreeKnowledgeDuration( 0.0f );
+ GetEnemies()->SetEnemyDiscardTime( 2.0f );
+
+ m_flTimeLastAttackedPlayer = 0.0f;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Class_T CNPC_Combine_Cannon::Classify( void )
+{
+ if ( m_fEnabled )
+ return CLASS_COMBINE;
+
+ return CLASS_NONE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CNPC_Combine_Cannon::GetBulletOrigin( void )
+{
+ return GetAbsOrigin();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Nothing kills the cannon but entity I/O
+//-----------------------------------------------------------------------------
+int CNPC_Combine_Cannon::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // We are invulnerable to normal attacks for the moment
+ return 0;
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::UpdateOnRemove( void )
+{
+ // Remove the main laser
+ if ( m_pBeam != NULL )
+ {
+ UTIL_Remove( m_pBeam);
+ m_pBeam = NULL;
+ }
+
+ // Remove our ancillary beams
+ for ( int i = 0; i < NUM_ANCILLARY_BEAMS; i++ )
+ {
+ if ( m_pAncillaryBeams[i] == NULL )
+ continue;
+
+ UTIL_Remove( m_pAncillaryBeams[i] );
+ m_pAncillaryBeams[i] = NULL;
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+int CNPC_Combine_Cannon::SelectSchedule ( void )
+{
+ // Fire at our target
+ if( GetEnemy() && HasCondition( COND_CAN_RANGE_ATTACK1 ) )
+ return SCHED_RANGE_ATTACK1;
+
+ // Wait for a target
+ // TODO: Sweep like a sniper?
+ return SCHED_COMBAT_STAND;
+}
+
+//---------------------------------------------------------
+// Purpose:
+//---------------------------------------------------------
+bool CNPC_Combine_Cannon::FCanCheckAttacks ( void )
+{
+ return true;
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+bool CNPC_Combine_Cannon::VerifyShot( CBaseEntity *pTarget )
+{
+ trace_t tr;
+
+ Vector vecTarget = DesiredBodyTarget( pTarget );
+ UTIL_TraceLine( GetBulletOrigin(), vecTarget, MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction != 1.0 )
+ {
+ if( pTarget->IsPlayer() )
+ {
+ // if the target is the player, do another trace to see if we can shoot his eyeposition. This should help
+ // improve sniper responsiveness in cases where the player is hiding his chest from the sniper with his
+ // head in full view.
+ UTIL_TraceLine( GetBulletOrigin(), pTarget->EyePosition(), MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction == 1.0 )
+ {
+ return true;
+ }
+ }
+
+ // Trace hit something.
+ if( tr.m_pEnt )
+ {
+ if( tr.m_pEnt->m_takedamage == DAMAGE_YES )
+ {
+ // Just shoot it if I can hurt it. Probably a breakable or glass pane.
+ return true;
+ }
+ }
+
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CNPC_Combine_Cannon::RangeAttack1Conditions( float flDot, float flDist )
+{
+ if ( GetNextAttack() > gpGlobals->curtime )
+ return COND_NONE;
+
+ if ( HasCondition( COND_SEE_ENEMY ) && !HasCondition( COND_ENEMY_OCCLUDED ) )
+ {
+ if ( VerifyShot( GetEnemy() ) )
+ {
+ // Can see the enemy, have a clear shot to his midsection
+ ClearCondition( COND_CANNON_NO_SHOT );
+ return COND_CAN_RANGE_ATTACK1;
+ }
+ else
+ {
+ // Can see the enemy, but can't take a shot at his midsection
+ SetCondition( COND_CANNON_NO_SHOT );
+ return COND_NONE;
+ }
+ }
+
+ return COND_NONE;
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+int CNPC_Combine_Cannon::TranslateSchedule( int scheduleType )
+{
+ switch( scheduleType )
+ {
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_CANNON_ATTACK;
+ break;
+ }
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::ScopeGlint( void )
+{
+ CEffectData data;
+
+ data.m_vOrigin = GetAbsOrigin();
+ data.m_vNormal = vec3_origin;
+ data.m_vAngles = vec3_angle;
+ data.m_nColor = COMMAND_POINT_BLUE;
+
+ DispatchEffect( "CommandPointer", data );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *vecIn -
+//-----------------------------------------------------------------------------
+void CNPC_Combine_Cannon::AdjustShotPosition( CBaseEntity *pTarget, Vector *vecIn )
+{
+ if ( pTarget == NULL || vecIn == NULL )
+ return;
+
+ Vector low = pTarget->WorldSpaceCenter() - ( pTarget->WorldSpaceCenter() - pTarget->GetAbsOrigin() ) * .25;
+ Vector high = pTarget->EyePosition();
+ Vector delta = high - low;
+ Vector result = low + delta * 0.5;
+
+ // Only take the height
+ (*vecIn)[2] = result[2];
+}
+
+//---------------------------------------------------------
+// This starts the bullet state machine. The actual effects
+// of the bullet will happen later. This function schedules
+// those effects.
+//
+// fDirectShot indicates whether the bullet is a "direct shot"
+// that is - fired with the intent that it will strike the
+// enemy. Otherwise, the bullet is intended to strike a
+// decoy object or nothing at all in particular.
+//---------------------------------------------------------
+bool CNPC_Combine_Cannon::FireBullet( const Vector &vecTarget, bool bDirectShot )
+{
+ Vector vecBulletOrigin = GetBulletOrigin();
+ Vector vecDir = ( vecTarget - vecBulletOrigin );
+ VectorNormalize( vecDir );
+
+ FireBulletsInfo_t info;
+ info.m_iShots = 1;
+ info.m_iTracerFreq = 1.0f;
+ info.m_vecDirShooting = vecDir;
+ info.m_vecSrc = vecBulletOrigin;
+ info.m_flDistance = MAX_TRACE_LENGTH;
+ info.m_pAttacker = this;
+ info.m_iAmmoType = m_iAmmoType;
+ info.m_iPlayerDamage = 20.0f;
+ info.m_vecSpread = Vector( 0.015f, 0.015f, 0.015f ); // medium cone
+
+ FireBullets( info );
+
+ EmitSound( "NPC_Combine_Cannon.FireBullet" );
+
+ // Don't attack for a certain amount of time
+ SetNextAttack( gpGlobals->curtime + GetRefireTime() );
+
+ // Sniper had to be aiming here to fire here, so make it the cursor
+ m_vecPaintCursor = vecTarget;
+
+ LaserOff();
+
+ return true;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_CANNON_ATTACK_CURSOR:
+ break;
+
+ case TASK_RANGE_ATTACK1:
+ // Setup the information for this barrage
+ m_flBarrageDuration = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.5f );
+ m_hBarrageTarget = GetEnemy();
+ break;
+
+ case TASK_CANNON_PAINT_ENEMY:
+ {
+ if ( GetEnemy()->IsPlayer() )
+ {
+ float delay = random->RandomFloat( 0.0f, 0.3f );
+
+ if ( ( gpGlobals->curtime - m_flTimeLastAttackedPlayer ) < 1.0f )
+ {
+ SetWait( CANNON_SUBSEQUENT_PAINT_TIME );
+ m_flPaintTime = CANNON_SUBSEQUENT_PAINT_TIME;
+ }
+ else
+ {
+ SetWait( CANNON_PAINT_ENEMY_TIME + delay );
+ m_flPaintTime = CANNON_PAINT_ENEMY_TIME + delay;
+ }
+ }
+ else
+ {
+ // Use a random time
+ m_flPaintTime = CANNON_PAINT_ENEMY_TIME + random->RandomFloat( 0, CANNON_PAINT_NPC_TIME_NOISE );
+ SetWait( m_flPaintTime );
+ }
+
+ // Try to start the laser where the player can't miss seeing it!
+ Vector vecCursor;
+ AngleVectors( GetEnemy()->GetLocalAngles(), &vecCursor );
+ vecCursor *= 300;
+ vecCursor += GetEnemy()->EyePosition();
+ LaserOn( vecCursor, Vector( 16, 16, 16 ) );
+ }
+ break;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_CANNON_ATTACK_CURSOR:
+ if( FireBullet( m_vecPaintCursor, true ) )
+ {
+ TaskComplete();
+ }
+ break;
+
+ case TASK_RANGE_ATTACK1:
+ {
+ // Where we're focusing our fire
+ Vector vecTarget = ( m_hBarrageTarget == NULL ) ? m_vecPaintCursor : LeadTarget( m_hBarrageTarget );
+
+ // Fire at enemy
+ if ( FireBullet( vecTarget, true ) )
+ {
+ bool bPlayerIsEnemy = ( m_hBarrageTarget && m_hBarrageTarget->IsPlayer() );
+ bool bBarrageFinished = m_flBarrageDuration < gpGlobals->curtime;
+ bool bNoShot = ( QuerySeeEntity( m_hBarrageTarget ) == false ); // FIXME: Store this info off better
+ bool bSeePlayer = HasCondition( COND_SEE_PLAYER );
+
+ // Treat the player differently to normal NPCs
+ if ( bPlayerIsEnemy )
+ {
+ // Store the last time we shot for doing an abbreviated attack telegraph
+ m_flTimeLastAttackedPlayer = gpGlobals->curtime;
+
+ // If we've got no shot and we're done with our current barrage
+ if ( bNoShot && bBarrageFinished )
+ {
+ TaskComplete();
+ }
+ }
+ else if ( bBarrageFinished || bSeePlayer )
+ {
+ // Done with the barrage or we saw the player as a better target
+ TaskComplete();
+ }
+ }
+ }
+ break;
+
+ case TASK_CANNON_PAINT_ENEMY:
+ {
+ // See if we're done painting our target
+ if ( IsWaitFinished() )
+ {
+ TaskComplete();
+ }
+
+ // Continue to paint the target
+ PaintTarget( LeadTarget( GetEnemy() ), m_flPaintTime );
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// The sniper throws away the circular list of old decoys when we restore.
+//-----------------------------------------------------------------------------
+int CNPC_Combine_Cannon::Restore( IRestore &restore )
+{
+ return BaseClass::Restore( restore );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//
+//
+//-----------------------------------------------------------------------------
+float CNPC_Combine_Cannon::MaxYawSpeed( void )
+{
+ return 60;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::PrescheduleThink( void )
+{
+ BaseClass::PrescheduleThink();
+
+ // NOTE: We'll deal with this on the client
+ // Think faster if the beam is on, this gives the beam higher resolution.
+ if( m_pBeam )
+ {
+ SetNextThink( gpGlobals->curtime + 0.03 );
+ }
+ else
+ {
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ }
+
+ // If the enemy has just stepped into view, or we've acquired a new enemy,
+ // Record the last time we've seen the enemy as right now.
+ //
+ // If the enemy has been out of sight for a full second, mark him eluded.
+ if( GetEnemy() != NULL )
+ {
+ if( gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) > 30 )
+ {
+ // Stop pestering enemies after 30 seconds of frustration.
+ GetEnemies()->ClearMemory( GetEnemy() );
+ SetEnemy(NULL);
+ }
+ }
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Vector CNPC_Combine_Cannon::EyePosition( void )
+{
+ return GetAbsOrigin();
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Vector CNPC_Combine_Cannon::DesiredBodyTarget( CBaseEntity *pTarget )
+{
+ // By default, aim for the center
+ Vector vecTarget = pTarget->WorldSpaceCenter();
+
+ float flTimeSinceLastMiss = gpGlobals->curtime - m_flTimeLastShotMissed;
+
+ if( pTarget->GetFlags() & FL_CLIENT )
+ {
+ if( !BaseClass::FVisible( vecTarget ) )
+ {
+ // go to the player's eyes if his center is concealed.
+ // Bump up an inch so the player's not looking straight down a beam.
+ vecTarget = pTarget->EyePosition() + Vector( 0, 0, 1 );
+ }
+ }
+ else
+ {
+ if( pTarget->Classify() == CLASS_HEADCRAB )
+ {
+ // Headcrabs are tiny inside their boxes.
+ vecTarget = pTarget->GetAbsOrigin();
+ vecTarget.z += 4.0;
+ }
+ else if( pTarget->Classify() == CLASS_ZOMBIE )
+ {
+ if( flTimeSinceLastMiss > 0.0f && flTimeSinceLastMiss < 4.0f && hl2_episodic.GetBool() )
+ {
+ vecTarget = pTarget->BodyTarget( GetBulletOrigin(), false );
+ }
+ else
+ {
+ // Shoot zombies in the headcrab
+ vecTarget = pTarget->HeadTarget( GetBulletOrigin() );
+ }
+ }
+ else if( pTarget->Classify() == CLASS_ANTLION )
+ {
+ // Shoot about a few inches above the origin. This makes it easy to hit antlions
+ // even if they are on their backs.
+ vecTarget = pTarget->GetAbsOrigin();
+ vecTarget.z += 18.0f;
+ }
+ else if( pTarget->Classify() == CLASS_EARTH_FAUNA )
+ {
+ // Shoot birds in the center
+ }
+ else
+ {
+ // Shoot NPCs in the chest
+ vecTarget.z += 8.0f;
+ }
+ }
+
+ return vecTarget;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+Vector CNPC_Combine_Cannon::LeadTarget( CBaseEntity *pTarget )
+{
+ if ( pTarget != NULL )
+ {
+ Vector vecFuturePos;
+ UTIL_PredictedPosition( pTarget, 0.05f, &vecFuturePos );
+ AdjustShotPosition( pTarget, &vecFuturePos );
+
+ return vecFuturePos;
+ }
+
+ return vec3_origin;
+}
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::InputEnableSniper( inputdata_t &inputdata )
+{
+ ClearCondition( COND_CANNON_DISABLED );
+ SetCondition( COND_CANNON_ENABLED );
+
+ m_fEnabled = true;
+}
+
+
+//---------------------------------------------------------
+//---------------------------------------------------------
+void CNPC_Combine_Cannon::InputDisableSniper( inputdata_t &inputdata )
+{
+ ClearCondition( COND_CANNON_ENABLED );
+ SetCondition( COND_CANNON_DISABLED );
+
+ m_fEnabled = false;
+}
+
+//---------------------------------------------------------
+// See all NPC's easily.
+//
+// Only see the player if you can trace to both of his
+// eyeballs. That is, allow the player to peek around corners.
+// This is a little more expensive than the base class' check!
+//---------------------------------------------------------
+#define CANNON_EYE_DIST 0.75
+#define CANNON_TARGET_VERTICAL_OFFSET Vector( 0, 0, 5 );
+bool CNPC_Combine_Cannon::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
+{
+ // NPC
+ if ( pEntity->IsPlayer() == false )
+ return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
+
+ if ( pEntity->GetFlags() & FL_NOTARGET )
+ return false;
+
+ Vector vecVerticalOffset;
+ Vector vecRight;
+ Vector vecEye;
+ trace_t tr;
+
+ if( fabs( GetAbsOrigin().z - pEntity->WorldSpaceCenter().z ) <= 120.f )
+ {
+ // If the player is around the same elevation, look straight at his eyes.
+ // At the same elevation, the vertical peeking allowance makes it too easy
+ // for a player to dispatch the sniper from cover.
+ vecVerticalOffset = vec3_origin;
+ }
+ else
+ {
+ // Otherwise, look at a spot below his eyes. This allows the player to back away
+ // from his cover a bit and have a peek at the sniper without being detected.
+ vecVerticalOffset = CANNON_TARGET_VERTICAL_OFFSET;
+ }
+
+ AngleVectors( pEntity->GetLocalAngles(), NULL, &vecRight, NULL );
+
+ vecEye = vecRight * CANNON_EYE_DIST - vecVerticalOffset;
+ UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+#if 0
+ NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
+#endif
+
+ bool fCheckFailed = false;
+
+ if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
+ {
+ fCheckFailed = true;
+ }
+
+ // Don't check the other eye if the first eye failed.
+ if( !fCheckFailed )
+ {
+ vecEye = -vecRight * CANNON_EYE_DIST - vecVerticalOffset;
+ UTIL_TraceLine( EyePosition(), pEntity->EyePosition() + vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+#if 0
+ NDebugOverlay::Line(EyePosition(), tr.endpos, 0,255,0, true, 0.1);
+#endif
+
+ if ( tr.fraction != 1.0 && tr.m_pEnt != pEntity )
+ {
+ fCheckFailed = true;
+ }
+ }
+
+ if( !fCheckFailed )
+ {
+ // Can see the player.
+ return true;
+ }
+
+ // Now, if the check failed, see if the player is ducking and has recently
+ // fired a muzzleflash. If yes, see if you'd be able to see the player if
+ // they were standing in their current position instead of ducking. Since
+ // the sniper doesn't have a clear shot in this situation, he will harrass
+ // near the player.
+ CBasePlayer *pPlayer;
+
+ pPlayer = ToBasePlayer( pEntity );
+
+ if( (pPlayer->GetFlags() & FL_DUCKING) && pPlayer->MuzzleFlashTime() > gpGlobals->curtime )
+ {
+ vecEye = pPlayer->EyePosition() + Vector( 0, 0, 32 );
+ UTIL_TraceLine( EyePosition(), vecEye, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
+
+ if( tr.fraction != 1.0 )
+ {
+ // Everything failed.
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+ return false;
+ }
+ else
+ {
+ // Fake being able to see the player.
+ return true;
+ }
+ }
+
+ if (ppBlocker)
+ {
+ *ppBlocker = tr.m_pEnt;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Schedules
+//
+//-----------------------------------------------------------------------------
+
+AI_BEGIN_CUSTOM_NPC( npc_combine_cannon, CNPC_Combine_Cannon )
+
+ DECLARE_CONDITION( COND_CANNON_ENABLED );
+ DECLARE_CONDITION( COND_CANNON_DISABLED );
+ DECLARE_CONDITION( COND_CANNON_NO_SHOT );
+
+ DECLARE_TASK( TASK_CANNON_PAINT_ENEMY );
+ DECLARE_TASK( TASK_CANNON_ATTACK_CURSOR );
+
+ //=========================================================
+ // CAMP
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_CAMP,
+
+ " Tasks"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_CAN_RANGE_ATTACK1"
+ " COND_HEAR_DANGER"
+ " COND_CANNON_DISABLED"
+ )
+
+ //=========================================================
+ // ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_ATTACK,
+
+ " Tasks"
+ " TASK_CANNON_PAINT_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ " "
+ " Interrupts"
+ " COND_HEAR_DANGER"
+ " COND_CANNON_DISABLED"
+ )
+
+ //=========================================================
+ // ATTACK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_SNAPATTACK,
+
+ " Tasks"
+ " TASK_CANNON_ATTACK_CURSOR 0"
+ " "
+ " Interrupts"
+ " COND_ENEMY_OCCLUDED"
+ " COND_ENEMY_DEAD"
+ " COND_NEW_ENEMY"
+ " COND_HEAR_DANGER"
+ " COND_CANNON_DISABLED"
+ )
+
+ //=========================================================
+ // Sniper is allowed to process a couple conditions while
+ // disabled, but mostly he waits until he's enabled.
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CANNON_DISABLEDWAIT,
+
+ " Tasks"
+ " TASK_WAIT 0.5"
+ " "
+ " Interrupts"
+ " COND_CANNON_ENABLED"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ )
+
+AI_END_CUSTOM_NPC()
|