diff options
| author | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:31:46 -0800 |
|---|---|---|
| committer | Jørgen P. Tjernø <[email protected]> | 2013-12-02 19:46:31 -0800 |
| commit | f56bb35301836e56582a575a75864392a0177875 (patch) | |
| tree | de61ddd39de3e7df52759711950b4c288592f0dc /sp/src/game/server/episodic/npc_combine_cannon.cpp | |
| parent | Mark some more files as text. (diff) | |
| download | source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip | |
Fix line endings. WHAMMY.
Diffstat (limited to 'sp/src/game/server/episodic/npc_combine_cannon.cpp')
| -rw-r--r-- | sp/src/game/server/episodic/npc_combine_cannon.cpp | 2670 |
1 files changed, 1335 insertions, 1335 deletions
diff --git a/sp/src/game/server/episodic/npc_combine_cannon.cpp b/sp/src/game/server/episodic/npc_combine_cannon.cpp index e4c3ba89..18ae490e 100644 --- a/sp/src/game/server/episodic/npc_combine_cannon.cpp +++ b/sp/src/game/server/episodic/npc_combine_cannon.cpp @@ -1,1335 +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()
+//========= 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() |