summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_controller.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_controller.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/hl1/hl1_npc_controller.cpp')
-rw-r--r--game/server/hl1/hl1_npc_controller.cpp1313
1 files changed, 1313 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_controller.cpp b/game/server/hl1/hl1_npc_controller.cpp
new file mode 100644
index 0000000..bc27aa0
--- /dev/null
+++ b/game/server/hl1/hl1_npc_controller.cpp
@@ -0,0 +1,1313 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
+// events
+//
+// $Workfile: $
+// $Date: $
+//
+//-----------------------------------------------------------------------------
+// $Log: $
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_default.h"
+#include "ai_task.h"
+#include "ai_schedule.h"
+#include "ai_node.h"
+#include "ai_hull.h"
+#include "ai_hint.h"
+#include "ai_memory.h"
+#include "ai_route.h"
+#include "ai_motor.h"
+//#include "hl1_npc_controller.h"
+#include "ai_basenpc_flyer.h"
+#include "soundent.h"
+#include "game.h"
+#include "npcevent.h"
+#include "entitylist.h"
+#include "activitylist.h"
+#include "animation.h"
+#include "basecombatweapon.h"
+#include "IEffects.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "ammodef.h"
+#include "Sprite.h"
+#include "ai_moveprobe.h"
+
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define CONTROLLER_AE_HEAD_OPEN 1
+#define CONTROLLER_AE_BALL_SHOOT 2
+#define CONTROLLER_AE_SMALL_SHOOT 3
+#define CONTROLLER_AE_POWERUP_FULL 4
+#define CONTROLLER_AE_POWERUP_HALF 5
+
+#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs
+
+#define DIST_TO_CHECK 200
+
+ConVar sk_controller_health ( "sk_controller_health", "60" );
+ConVar sk_controller_dmgzap ( "sk_controller_dmgzap", "15" );
+ConVar sk_controller_speedball ( "sk_controller_speedball", "650" );
+ConVar sk_controller_dmgball ( "sk_controller_dmgball", "3" );
+
+int ACT_CONTROLLER_UP;
+int ACT_CONTROLLER_DOWN;
+int ACT_CONTROLLER_LEFT;
+int ACT_CONTROLLER_RIGHT;
+int ACT_CONTROLLER_FORWARD;
+int ACT_CONTROLLER_BACKWARD;
+
+class CSprite;
+class CNPC_Controller;
+
+enum
+{
+ TASK_CONTROLLER_CHASE_ENEMY = LAST_SHARED_TASK,
+ TASK_CONTROLLER_STRAFE,
+ TASK_CONTROLLER_TAKECOVER,
+ TASK_CONTROLLER_FAIL,
+};
+
+enum
+{
+ SCHED_CONTROLLER_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
+ SCHED_CONTROLLER_STRAFE,
+ SCHED_CONTROLLER_TAKECOVER,
+ SCHED_CONTROLLER_FAIL,
+};
+
+class CControllerNavigator : public CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator>
+{
+ typedef CAI_ComponentWithOuter<CNPC_Controller, CAI_Navigator> BaseClass;
+public:
+ CControllerNavigator( CNPC_Controller *pOuter )
+ : BaseClass( pOuter )
+ {
+ }
+
+ bool ActivityIsLocomotive( Activity activity ) { return true; }
+};
+
+class CNPC_Controller : public CAI_BaseFlyingBot
+{
+public:
+
+ DECLARE_CLASS( CNPC_Controller, CAI_BaseFlyingBot );
+ DEFINE_CUSTOM_AI;
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ float MaxYawSpeed( void ) { return 120.0f; }
+ Class_T Classify ( void ) { return CLASS_ALIEN_MILITARY; }
+
+ void HandleAnimEvent( animevent_t *pEvent );
+
+ void RunAI( void );
+
+ int RangeAttack1Conditions ( float flDot, float flDist ); // balls
+ int RangeAttack2Conditions ( float flDot, float flDist ); // head
+ int MeleeAttack1Conditions ( float flDot, float flDist ) { return COND_NONE; }
+ int MeleeAttack2Conditions ( float flDot, float flDist ) { return COND_NONE; }
+
+ int TranslateSchedule( int scheduleType );
+ void StartTask ( const Task_t *pTask );
+ void RunTask ( const Task_t *pTask );
+
+ void Stop( void );
+ bool OverridePathMove( float flInterval );
+ bool OverrideMove( float flInterval );
+
+ void MoveToTarget( float flInterval, const Vector &vecMoveTarget );
+
+ Activity NPC_TranslateActivity( Activity eNewActivity );
+ void SetActivity ( Activity NewActivity );
+ bool ShouldAdvanceRoute( float flWaypointDist );
+ int LookupFloat( );
+
+ friend class CControllerNavigator;
+ CAI_Navigator *CreateNavigator()
+ {
+ return new CControllerNavigator( this );
+ }
+
+ bool ShouldGib( const CTakeDamageInfo &info );
+ bool HasAlienGibs( void ) { return true; }
+ bool HasHumanGibs( void ) { return false; }
+
+ float m_flShootTime;
+ float m_flShootEnd;
+
+ void PainSound( const CTakeDamageInfo &info );
+ void AlertSound( void );
+ void IdleSound( void );
+ void AttackSound( void );
+ void DeathSound( const CTakeDamageInfo &info );
+
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ void Event_Killed( const CTakeDamageInfo &info );
+
+ CSprite *m_pBall[2]; // hand balls
+ int m_iBall[2]; // how bright it should be
+ float m_iBallTime[2]; // when it should be that color
+ int m_iBallCurrent[2]; // current brightness
+
+ Vector m_vecEstVelocity;
+
+ Vector m_velocity;
+ bool m_fInCombat;
+
+ void SetSequence( int nSequence );
+
+ int IRelationPriority( CBaseEntity *pTarget );
+};
+
+class CNPC_ControllerHeadBall : public CAI_BaseNPC
+{
+public:
+ DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ void EXPORT HuntThink( void );
+ void EXPORT KillThink( void );
+ void EXPORT BounceTouch( CBaseEntity *pOther );
+ void MovetoTarget( Vector vecTarget );
+
+ float m_flSpawnTime;
+ Vector m_vecIdeal;
+ EHANDLE m_hOwner;
+
+ CSprite *m_pSprite;
+};
+
+class CNPC_ControllerZapBall : public CAI_BaseNPC
+{
+public:
+ DECLARE_CLASS( CNPC_ControllerHeadBall, CAI_BaseNPC );
+
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+
+ void EXPORT AnimateThink( void );
+ void EXPORT ExplodeTouch( CBaseEntity *pOther );
+
+ void Kill( void );
+
+ EHANDLE m_hOwner;
+ float m_flSpawnTime;
+
+ CSprite *m_pSprite;
+};
+
+LINK_ENTITY_TO_CLASS( monster_alien_controller, CNPC_Controller );
+
+BEGIN_DATADESC( CNPC_Controller )
+
+ DEFINE_ARRAY( m_pBall, FIELD_CLASSPTR, 2 ),
+ DEFINE_ARRAY( m_iBall, FIELD_INTEGER, 2 ),
+ DEFINE_ARRAY( m_iBallTime, FIELD_TIME, 2 ),
+ DEFINE_ARRAY( m_iBallCurrent, FIELD_INTEGER, 2 ),
+ DEFINE_FIELD( m_vecEstVelocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_velocity, FIELD_VECTOR ),
+ DEFINE_FIELD( m_fInCombat, FIELD_BOOLEAN ),
+
+ DEFINE_FIELD( m_flShootTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flShootEnd, FIELD_TIME ),
+
+END_DATADESC()
+
+
+void CNPC_Controller::Spawn()
+{
+ Precache( );
+
+ SetModel( "models/controller.mdl" );
+ UTIL_SetSize( this, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ));
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ SetMoveType( MOVETYPE_STEP );
+ SetGravity(0.001);
+
+
+ m_bloodColor = BLOOD_COLOR_GREEN;
+ m_iHealth = sk_controller_health.GetFloat();
+
+ m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ SetRenderColor( 255, 255, 255, 255 );
+
+ CapabilitiesClear();
+
+ AddFlag( FL_FLY );
+ SetNavType( NAV_FLY );
+
+ CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_RANGE_ATTACK2 | bits_CAP_MOVE_SHOOT);
+
+ NPCInit();
+
+
+ SetDefaultEyeOffset();
+}
+
+//=========================================================
+// Precache - precaches all resources this monster needs
+//=========================================================
+void CNPC_Controller::Precache()
+{
+ PrecacheModel("models/controller.mdl");
+
+ PrecacheModel( "sprites/xspark4.vmt");
+
+ UTIL_PrecacheOther( "controller_energy_ball" );
+ UTIL_PrecacheOther( "controller_head_ball" );
+
+ PrecacheScriptSound( "Controller.Pain" );
+ PrecacheScriptSound( "Controller.Alert" );
+ PrecacheScriptSound( "Controller.Die" );
+ PrecacheScriptSound( "Controller.Idle" );
+ PrecacheScriptSound( "Controller.Attack" );
+
+}
+
+//=========================================================
+// TakeDamage -
+//=========================================================
+int CNPC_Controller::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ PainSound( info );
+ return BaseClass::OnTakeDamage_Alive( info );
+}
+
+bool CNPC_Controller::ShouldGib( const CTakeDamageInfo &info )
+{
+ if ( info.GetDamageType() & DMG_NEVERGIB )
+ return false;
+
+ if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) )
+ return true;
+
+ return false;
+
+}
+
+int CNPC_Controller::IRelationPriority( CBaseEntity *pTarget )
+{
+ if ( pTarget->Classify() == CLASS_PLAYER )
+ {
+ return BaseClass::IRelationPriority ( pTarget ) + 1;
+ }
+
+ return BaseClass::IRelationPriority( pTarget );
+}
+
+void CNPC_Controller::Event_Killed( const CTakeDamageInfo &info )
+{
+ if( ShouldGib(info) )
+ {
+ //remove the balls
+ if (m_pBall[0])
+ {
+ UTIL_Remove( m_pBall[0] );
+ m_pBall[0] = NULL;
+ }
+ if (m_pBall[1])
+ {
+ UTIL_Remove( m_pBall[1] );
+ m_pBall[1] = NULL;
+ }
+ }
+ else
+ {
+ // fade balls
+ if (m_pBall[0])
+ {
+ m_pBall[0]->FadeAndDie( 2 );
+ m_pBall[0] = NULL;
+ }
+ if (m_pBall[1])
+ {
+ m_pBall[1]->FadeAndDie( 2 );
+ m_pBall[1] = NULL;
+ }
+ }
+
+ BaseClass::Event_Killed( info );
+}
+
+void CNPC_Controller::PainSound( const CTakeDamageInfo &info )
+{
+ if (random->RandomInt(0,5) < 2)
+ {
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Pain" );
+ }
+}
+
+void CNPC_Controller::AlertSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Alert" );
+}
+
+void CNPC_Controller::IdleSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Idle" );
+}
+
+void CNPC_Controller::AttackSound( void )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Attack" );
+}
+
+void CNPC_Controller::DeathSound( const CTakeDamageInfo &info )
+{
+ CPASAttenuationFilter filter( this );
+ EmitSound( filter, entindex(), "Controller.Die" );
+}
+
+//=========================================================
+// HandleAnimEvent - catches the monster-specific messages
+// that occur when tagged animation frames are played.
+//=========================================================
+void CNPC_Controller::HandleAnimEvent( animevent_t *pEvent )
+{
+ switch( pEvent->event )
+ {
+ case CONTROLLER_AE_HEAD_OPEN:
+ {
+ Vector vecStart;
+ QAngle angleGun;
+
+ GetAttachment( 0, vecStart, angleGun );
+
+ // BUGBUG - attach to attachment point!
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0, 1 /*radius*/, 0.2, -32 );
+
+ m_iBall[0] = 192;
+ m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ m_iBall[1] = 255;
+ m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+
+ }
+ break;
+
+ case CONTROLLER_AE_BALL_SHOOT:
+ {
+ Vector vecStart;
+ QAngle angleGun;
+
+ GetAttachment( 1, vecStart, angleGun );
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0, 1 /*radius*/, 0.1, 32 );
+
+ CAI_BaseNPC *pBall = (CAI_BaseNPC*)Create( "controller_head_ball", vecStart, angleGun );
+
+ pBall->SetAbsVelocity( Vector(0,0,32) );
+ pBall->SetEnemy( GetEnemy() );
+
+// DevMsg( 1, "controller shooting head ball\n" );
+
+ m_iBall[0] = 0;
+ m_iBall[1] = 0;
+ }
+ break;
+
+ case CONTROLLER_AE_SMALL_SHOOT:
+ {
+ AttackSound( );
+ m_flShootTime = gpGlobals->curtime;
+ m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0;
+ }
+ break;
+ case CONTROLLER_AE_POWERUP_FULL:
+ {
+ m_iBall[0] = 255;
+ m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ m_iBall[1] = 255;
+ m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ }
+ break;
+ case CONTROLLER_AE_POWERUP_HALF:
+ {
+ m_iBall[0] = 192;
+ m_iBallTime[0] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ m_iBall[1] = 192;
+ m_iBallTime[1] = gpGlobals->curtime + atoi( pEvent->options ) / 15.0;
+ }
+ break;
+ default:
+ BaseClass::HandleAnimEvent( pEvent );
+ break;
+ }
+}
+
+
+//=========================================================
+// AI Schedules Specific to this monster
+//=========================================================
+
+AI_BEGIN_CUSTOM_NPC( monster_alien_controller, CNPC_Controller )
+
+ //declare our tasks
+ DECLARE_TASK( TASK_CONTROLLER_CHASE_ENEMY )
+ DECLARE_TASK( TASK_CONTROLLER_STRAFE )
+ DECLARE_TASK( TASK_CONTROLLER_TAKECOVER )
+ DECLARE_TASK( TASK_CONTROLLER_FAIL )
+
+ DECLARE_ACTIVITY( ACT_CONTROLLER_UP )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_DOWN )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_LEFT )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_RIGHT )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_FORWARD )
+ DECLARE_ACTIVITY( ACT_CONTROLLER_BACKWARD )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_CHASE_ENEMY
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_CHASE_ENEMY,
+
+ " Tasks"
+ " TASK_GET_PATH_TO_ENEMY 128"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_TASK_FAILED"
+
+
+ )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_STRAFE
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_STRAFE,
+
+ " Tasks"
+ " TASK_WAIT 0.2"
+ " TASK_GET_PATH_TO_ENEMY 128"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_TAKECOVER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_TAKECOVER,
+
+ " Tasks"
+ " TASK_WAIT 0.2"
+ " TASK_FIND_COVER_FROM_ENEMY 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_WAIT 1"
+ " "
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_CONTROLLER_FAIL
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_CONTROLLER_FAIL,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
+ " TASK_WAIT 2"
+ " TASK_WAIT_PVS 0"
+ )
+
+AI_END_CUSTOM_NPC()
+
+//=========================================================
+// StartTask
+//=========================================================
+void CNPC_Controller::StartTask( const Task_t *pTask )
+{
+ BaseClass::StartTask( pTask );
+}
+
+Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed )
+{
+ Vector vecTo = vecDst - vecSrc;
+
+ float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed;
+ float b = 0 * DotProduct(vecTo, vecMove); // why does this work?
+ float c = DotProduct( vecTo, vecTo );
+
+ float t;
+ if (a == 0)
+ {
+ t = c / (flSpeed * flSpeed);
+ }
+ else
+ {
+ t = b * b - 4 * a * c;
+ t = sqrt( t ) / (2.0 * a);
+ float t1 = -b +t;
+ float t2 = -b -t;
+
+ if (t1 < 0 || t2 < t1)
+ t = t2;
+ else
+ t = t1;
+ }
+
+ if (t < 0.1)
+ t = 0.1;
+ if (t > 10.0)
+ t = 10.0;
+
+ Vector vecHit = vecTo + vecMove * t;
+ VectorNormalize( vecHit );
+ return vecHit * flSpeed;
+}
+
+
+int CNPC_Controller::LookupFloat( )
+{
+ if (m_velocity.Length( ) < 32.0)
+ {
+ return ACT_CONTROLLER_UP;
+ }
+
+ Vector vecForward, vecRight, vecUp;
+ AngleVectors( GetAbsAngles(), &vecForward, &vecRight, &vecUp );
+
+ float x = DotProduct( vecForward, m_velocity );
+ float y = DotProduct( vecRight, m_velocity );
+ float z = DotProduct( vecUp, m_velocity );
+
+ if (fabs(x) > fabs(y) && fabs(x) > fabs(z))
+ {
+ if (x > 0)
+ return ACT_CONTROLLER_FORWARD;
+ else
+ return ACT_CONTROLLER_BACKWARD;
+ }
+ else if (fabs(y) > fabs(z))
+ {
+ if (y > 0)
+ return ACT_CONTROLLER_RIGHT;
+ else
+ return ACT_CONTROLLER_LEFT;
+ }
+ else
+ {
+ if (z > 0)
+ return ACT_CONTROLLER_UP;
+ else
+ return ACT_CONTROLLER_DOWN;
+ }
+}
+
+
+//=========================================================
+// RunTask
+//=========================================================
+void CNPC_Controller::RunTask ( const Task_t *pTask )
+{
+ if (m_flShootEnd > gpGlobals->curtime)
+ {
+ Vector vecHand;
+ QAngle vecAngle;
+
+ GetAttachment( 2, vecHand, vecAngle );
+
+ while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->curtime)
+ {
+ Vector vecSrc = vecHand + GetAbsVelocity() * (m_flShootTime - gpGlobals->curtime);
+ Vector vecDir;
+
+ if (GetEnemy() != NULL)
+ {
+ if (HasCondition( COND_SEE_ENEMY ))
+ {
+ m_vecEstVelocity = m_vecEstVelocity * 0.5 + GetEnemy()->GetAbsVelocity() * 0.5;
+ }
+ else
+ {
+ m_vecEstVelocity = m_vecEstVelocity * 0.8;
+ }
+ vecDir = Intersect( vecSrc, GetEnemy()->BodyTarget( GetAbsOrigin() ), m_vecEstVelocity, sk_controller_speedball.GetFloat() );
+
+ float delta = 0.03490; // +-2 degree
+ vecDir = vecDir + Vector( random->RandomFloat( -delta, delta ), random->RandomFloat( -delta, delta ), random->RandomFloat( -delta, delta ) ) * sk_controller_speedball.GetFloat();
+
+ vecSrc = vecSrc + vecDir * (gpGlobals->curtime - m_flShootTime);
+ CAI_BaseNPC *pBall = (CAI_BaseNPC*)Create( "controller_energy_ball", vecSrc, GetAbsAngles(), this );
+ pBall->SetAbsVelocity( vecDir );
+
+// DevMsg( 2, "controller shooting energy ball\n" );
+ }
+
+ m_flShootTime += 0.2;
+ }
+
+ if (m_flShootTime > m_flShootEnd)
+ {
+ m_iBall[0] = 64;
+ m_iBallTime[0] = m_flShootEnd;
+ m_iBall[1] = 64;
+ m_iBallTime[1] = m_flShootEnd;
+ m_fInCombat = FALSE;
+ }
+ }
+
+ switch ( pTask->iTask )
+ {
+ case TASK_WAIT_FOR_MOVEMENT:
+ case TASK_WAIT:
+ case TASK_WAIT_FACE_ENEMY:
+ case TASK_WAIT_PVS:
+ {
+ if( GetEnemy() )
+ {
+ float idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
+ GetMotor()->SetIdealYawAndUpdate( idealYaw );
+ }
+
+ if ( IsSequenceFinished() || GetActivity() == ACT_IDLE)
+ {
+ m_fInCombat = false;
+ }
+
+ BaseClass::RunTask ( pTask );
+
+ if (!m_fInCombat)
+ {
+ if( HasCondition( COND_CAN_RANGE_ATTACK1 ))
+ {
+ SetActivity( ACT_RANGE_ATTACK1 );
+ SetCycle( 0 );
+ ResetSequenceInfo( );
+ m_fInCombat = true;
+ }
+ else if( HasCondition( COND_CAN_RANGE_ATTACK2 ) )
+ {
+ SetActivity( ACT_RANGE_ATTACK2 );
+ SetCycle( 0 );
+ ResetSequenceInfo( );
+ m_fInCombat = true;
+ }
+ else
+ {
+ int iFloatActivity = LookupFloat();
+ if( IsSequenceFinished() || iFloatActivity != GetActivity() )
+ {
+ SetActivity( (Activity)iFloatActivity );
+ }
+ }
+ }
+ }
+ break;
+ default:
+ BaseClass::RunTask ( pTask );
+ break;
+ }
+}
+
+void CNPC_Controller::SetSequence( int nSequence )
+{
+ BaseClass::SetSequence( nSequence );
+}
+
+
+//=========================================================
+//=========================================================
+int CNPC_Controller::TranslateSchedule( int scheduleType )
+{
+ switch ( scheduleType )
+ {
+ case SCHED_CHASE_ENEMY:
+ return SCHED_CONTROLLER_CHASE_ENEMY;
+ case SCHED_RANGE_ATTACK1:
+ return SCHED_CONTROLLER_STRAFE;
+ case SCHED_RANGE_ATTACK2:
+ case SCHED_MELEE_ATTACK1:
+ case SCHED_MELEE_ATTACK2:
+ case SCHED_TAKE_COVER_FROM_ENEMY:
+ return SCHED_CONTROLLER_TAKECOVER;
+ case SCHED_FAIL:
+ return SCHED_CONTROLLER_FAIL;
+
+ default:
+ break;
+ }
+
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+
+//=========================================================
+// CheckRangeAttack1 - shoot a bigass energy ball out of their head
+//=========================================================
+int CNPC_Controller::RangeAttack1Conditions ( float flDot, float flDist )
+{
+ if( flDist > 2048 )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if( flDist <= 256 )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+// if( flDot <= 0.5 )
+// {
+// return COND_NOT_FACING_ATTACK;
+// }
+
+ return COND_CAN_RANGE_ATTACK1;
+}
+
+//=========================================================
+// CheckRangeAttack1 - head
+//=========================================================
+int CNPC_Controller::RangeAttack2Conditions ( float flDot, float flDist )
+{
+ if( flDist > 2048 )
+ {
+ return COND_TOO_FAR_TO_ATTACK;
+ }
+
+ if( flDist <= 64 )
+ {
+ return COND_TOO_CLOSE_TO_ATTACK;
+ }
+
+// if( flDot <= 0.5 )
+// {
+// return COND_NOT_FACING_ATTACK;
+// }
+
+ return COND_CAN_RANGE_ATTACK2;
+}
+
+//=========================================================
+//=========================================================
+Activity CNPC_Controller::NPC_TranslateActivity( Activity eNewActivity )
+{
+ switch ( eNewActivity)
+ {
+ case ACT_IDLE:
+ return (Activity)LookupFloat();
+ break;
+
+ default:
+ return BaseClass::NPC_TranslateActivity( eNewActivity );
+ }
+}
+
+//=========================================================
+// SetActivity -
+//=========================================================
+void CNPC_Controller::SetActivity ( Activity NewActivity )
+{
+ BaseClass::SetActivity( NewActivity );
+ m_flGroundSpeed = 100;
+}
+
+//=========================================================
+// RunAI
+//=========================================================
+void CNPC_Controller::RunAI( void )
+{
+ BaseClass::RunAI();
+
+ Vector vecStart;
+ QAngle angleGun;
+
+ //some kind of hack in hl1 ?
+// if ( HasMemory( bits_MEMORY_KILLED ) )
+ //use this instead
+ if( !IsAlive() )
+ return;
+
+ for (int i = 0; i < 2; i++)
+ {
+ if (m_pBall[i] == NULL)
+ {
+ m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), TRUE );
+ m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pBall[i]->SetAttachment( this, (i + 3) );
+ m_pBall[i]->SetScale( 1.0 );
+ }
+
+ float t = m_iBallTime[i] - gpGlobals->curtime;
+ if (t > 0.1)
+ t = 0.1 / t;
+ else
+ t = 1.0;
+
+ m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t;
+
+ m_pBall[i]->SetBrightness( m_iBallCurrent[i] );
+
+ GetAttachment( i + 2, vecStart, angleGun );
+ m_pBall[i]->SetAbsOrigin( vecStart );
+
+ CBroadcastRecipientFilter filter;
+ GetAttachment( i + 3, vecStart, angleGun );
+ te->DynamicLight( filter, 0.0, &vecStart, 255, 192, 64, 0/*exponent*/, m_iBallCurrent[i] / 8 /*radius*/, 0.5, 0 );
+ }
+}
+
+//=========================================================
+// Stop -
+//=========================================================
+void CNPC_Controller::Stop( void )
+{
+ SetIdealActivity( GetStoppedActivity() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles movement towards the last move target.
+// Input : flInterval -
+//-----------------------------------------------------------------------------
+bool CNPC_Controller::OverridePathMove( float flInterval )
+{
+ CBaseEntity *pMoveTarget = (GetTarget()) ? GetTarget() : GetEnemy();
+ Vector waypointDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
+
+ float flWaypointDist = waypointDir.Length2D();
+ VectorNormalize(waypointDir);
+
+ // cut corner?
+ if (flWaypointDist < 128)
+ {
+ if (m_flGroundSpeed > 100)
+ m_flGroundSpeed -= 40;
+ }
+ else
+ {
+ if (m_flGroundSpeed < 400)
+ m_flGroundSpeed += 10;
+ }
+
+ m_velocity = m_velocity * 0.8 + m_flGroundSpeed * waypointDir * 0.5;
+ SetAbsVelocity( m_velocity );
+
+ // -----------------------------------------------------------------
+ // Check route is blocked
+ // ------------------------------------------------------------------
+ Vector checkPos = GetLocalOrigin() + (waypointDir * (m_flGroundSpeed * flInterval));
+
+ AIMoveTrace_t moveTrace;
+ GetMoveProbe()->MoveLimit( NAV_FLY, GetLocalOrigin(), checkPos, MASK_NPCSOLID|CONTENTS_WATER,
+ pMoveTarget, &moveTrace);
+ if (IsMoveBlocked( moveTrace ))
+ {
+ TaskFail(FAIL_NO_ROUTE);
+ GetNavigator()->ClearGoal();
+ return true;
+ }
+
+ // ----------------------------------------------
+
+ Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetLocalOrigin();
+
+ if ( ProgressFlyPath( flInterval, pMoveTarget, MASK_NPCSOLID, false, 64 ) == AINPP_COMPLETE )
+ {
+ {
+ m_vLastPatrolDir = lastPatrolDir;
+ VectorNormalize(m_vLastPatrolDir);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CNPC_Controller::OverrideMove( float flInterval )
+{
+ if (m_flGroundSpeed == 0)
+ {
+ m_flGroundSpeed = 100;
+ }
+
+ // ----------------------------------------------
+ // Select move target
+ // ----------------------------------------------
+ CBaseEntity *pMoveTarget = NULL;
+ if (GetTarget() != NULL )
+ {
+ pMoveTarget = GetTarget();
+ }
+ else if (GetEnemy() != NULL )
+ {
+ pMoveTarget = GetEnemy();
+ }
+
+ // ----------------------------------------------
+ // Select move target position
+ // ----------------------------------------------
+ Vector vMoveTargetPos(0,0,0);
+ if (GetTarget())
+ {
+ vMoveTargetPos = GetTarget()->GetAbsOrigin();
+ }
+ else if (GetEnemy() != NULL)
+ {
+ vMoveTargetPos = GetEnemy()->GetAbsOrigin();
+ }
+
+ // -----------------------------------------
+ // See if we can fly there directly
+ // -----------------------------------------
+ if (pMoveTarget /*|| HaveInspectTarget()*/)
+ {
+ trace_t tr;
+
+ if (pMoveTarget)
+ {
+ UTIL_TraceEntity( this, GetAbsOrigin(), vMoveTargetPos,
+ MASK_NPCSOLID_BRUSHONLY, pMoveTarget, GetCollisionGroup(), &tr);
+ }
+ else
+ {
+ UTIL_TraceEntity( this, GetAbsOrigin(), vMoveTargetPos, MASK_NPCSOLID_BRUSHONLY, &tr);
+ }
+/*
+ float fTargetDist = (1-tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
+ if (fTargetDist > 50)
+ {
+ //SetCondition( COND_SCANNER_FLY_BLOCKED );
+ }
+ else
+ {
+ //SetCondition( COND_SCANNER_FLY_CLEAR );
+ }
+*/
+ }
+
+ // -----------------------------------------------------------------
+ // If I have a route, keep it updated and move toward target
+ // ------------------------------------------------------------------
+ if (GetNavigator()->IsGoalActive())
+ {
+ if ( OverridePathMove( flInterval ) )
+ return true;
+ }
+ else
+ {
+ //do nothing
+ Stop();
+ TaskComplete();
+ }
+
+ return true;
+}
+
+void CNPC_Controller::MoveToTarget( float flInterval, const Vector &vecMoveTarget )
+{
+ const float myAccel = 300.0;
+ const float myDecay = 9.0;
+
+ //TurnHeadToTarget( flInterval, MoveTarget );
+ MoveToLocation( flInterval, vecMoveTarget, myAccel, (2 * myAccel), myDecay );
+}
+
+//=========================================================
+// Controller bouncy ball attack
+//=========================================================
+
+LINK_ENTITY_TO_CLASS( controller_head_ball, CNPC_ControllerHeadBall );
+
+BEGIN_DATADESC( CNPC_ControllerHeadBall )
+
+ DEFINE_THINKFUNC( HuntThink ),
+ DEFINE_THINKFUNC( KillThink ),
+ DEFINE_ENTITYFUNC( BounceTouch ),
+
+ DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),
+
+ DEFINE_FIELD( m_flSpawnTime, FIELD_TIME ),
+ DEFINE_FIELD( m_vecIdeal, FIELD_VECTOR ),
+ DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
+
+END_DATADESC()
+
+
+void CNPC_ControllerHeadBall::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetMoveType( MOVETYPE_FLY );
+ SetSolid( SOLID_BBOX );
+ SetSize( vec3_origin, vec3_origin );
+
+ m_pSprite = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), FALSE );
+ m_pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pSprite->SetAttachment( this, 0 );
+ m_pSprite->SetScale( 2.0 );
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
+ UTIL_SetOrigin( this, GetAbsOrigin() );
+
+ SetThink( &CNPC_ControllerHeadBall::HuntThink );
+ SetTouch( &CNPC_ControllerHeadBall::BounceTouch );
+
+// m_vecIdeal = vec3_origin; //(0,0,0)
+
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ m_hOwner = GetOwnerEntity();
+
+ m_flSpawnTime = gpGlobals->curtime;
+}
+
+
+void CNPC_ControllerHeadBall::Precache( void )
+{
+ PrecacheModel( "sprites/xspark4.vmt");
+}
+
+extern short g_sModelIndexLaser;
+
+void CNPC_ControllerHeadBall::HuntThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ if( !m_pSprite )
+ {
+ Assert(0);
+ return;
+ }
+
+ m_pSprite->SetBrightness( m_pSprite->GetBrightness() - 5, 0.1f );
+
+ CBroadcastRecipientFilter filter;
+ te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 255, 255, 0, m_pSprite->GetBrightness() / 16, 0.2, 0 );
+
+ // check world boundaries
+ if (gpGlobals->curtime - m_flSpawnTime > 5 || m_pSprite->GetBrightness() < 64 /*|| GetEnemy() == NULL || m_hOwner == NULL*/ || !IsInWorld() )
+ {
+ SetTouch( NULL );
+ SetThink( &CNPC_ControllerHeadBall::KillThink );
+ SetNextThink( gpGlobals->curtime );
+ return;
+ }
+
+ if( !GetEnemy() )
+ return;
+
+ MovetoTarget( GetEnemy()->GetAbsOrigin() );
+
+ if ((GetEnemy()->WorldSpaceCenter() - GetAbsOrigin()).Length() < 64)
+ {
+ trace_t tr;
+
+ UTIL_TraceLine( GetAbsOrigin(), GetEnemy()->WorldSpaceCenter(), MASK_ALL, this, COLLISION_GROUP_NONE, &tr );
+
+ CBaseEntity *pEntity = tr.m_pEnt;
+ if (pEntity != NULL && pEntity->m_takedamage == DAMAGE_YES)
+ {
+ ClearMultiDamage( );
+ Vector dir = GetAbsVelocity();
+ VectorNormalize( dir );
+ CTakeDamageInfo info( this, this, sk_controller_dmgball.GetFloat(), DMG_SHOCK );
+ CalculateMeleeDamageForce( &info, dir, tr.endpos );
+ pEntity->DispatchTraceAttack( info, dir, &tr );
+ ApplyMultiDamage();
+
+ int haloindex = 0;
+ int fadelength = 0;
+ int amplitude = 0;
+ const Vector vecEnd = tr.endpos;
+ te->BeamEntPoint( filter, 0.0, entindex(), NULL, 0, &(tr.m_pEnt->GetAbsOrigin()),
+ g_sModelIndexLaser, haloindex /* no halo */, 0, 10, 3, 20, 20, fadelength,
+ amplitude, 255, 255, 255, 255, 10 );
+
+ }
+
+ UTIL_EmitAmbientSound( GetSoundSourceIndex(), GetAbsOrigin(), "Controller.ElectroSound", 0.5, SNDLVL_NORM, 0, 100 );
+
+ SetNextAttack( gpGlobals->curtime + 3.0 );
+
+ SetThink( &CNPC_ControllerHeadBall::KillThink );
+ SetNextThink( gpGlobals->curtime + 0.3 );
+ }
+}
+
+void CNPC_ControllerHeadBall::MovetoTarget( Vector vecTarget )
+{
+ // accelerate
+ float flSpeed = m_vecIdeal.Length();
+ if (flSpeed == 0)
+ {
+ m_vecIdeal = GetAbsVelocity();
+ flSpeed = m_vecIdeal.Length();
+ }
+
+ if (flSpeed > 400)
+ {
+ VectorNormalize( m_vecIdeal );
+ m_vecIdeal = m_vecIdeal * 400;
+ }
+
+ Vector t = vecTarget - GetAbsOrigin();
+ VectorNormalize(t);
+ m_vecIdeal = m_vecIdeal + t * 100;
+ SetAbsVelocity(m_vecIdeal);
+}
+
+void CNPC_ControllerHeadBall::BounceTouch( CBaseEntity *pOther )
+{
+ Vector vecDir = m_vecIdeal;
+ VectorNormalize( vecDir );
+
+ trace_t tr;
+ tr = CBaseEntity::GetTouchTrace( );
+
+ float n = -DotProduct(tr.plane.normal, vecDir);
+
+ vecDir = 2.0 * tr.plane.normal * n + vecDir;
+
+ m_vecIdeal = vecDir * m_vecIdeal.Length();
+}
+
+void CNPC_ControllerHeadBall::KillThink( void )
+{
+ UTIL_Remove( m_pSprite );
+ UTIL_Remove( this );
+}
+
+
+//=========================================================
+// Controller Zap attack
+//=========================================================
+
+LINK_ENTITY_TO_CLASS( controller_energy_ball, CNPC_ControllerZapBall );
+
+BEGIN_DATADESC( CNPC_ControllerZapBall )
+
+ DEFINE_THINKFUNC( AnimateThink ),
+ DEFINE_ENTITYFUNC( ExplodeTouch ),
+
+ DEFINE_FIELD( m_hOwner, FIELD_EHANDLE ),
+ DEFINE_FIELD( m_flSpawnTime, FIELD_TIME ),
+ DEFINE_FIELD( m_pSprite, FIELD_CLASSPTR ),
+
+END_DATADESC()
+
+
+void CNPC_ControllerZapBall::Spawn( void )
+{
+ Precache( );
+ // motor
+ SetMoveType( MOVETYPE_FLY );
+// SetSolid( SOLID_CUSTOM );
+ SetSolid( SOLID_BBOX );
+ SetSize( vec3_origin, vec3_origin );
+
+ m_pSprite = CSprite::SpriteCreate( "sprites/xspark4.vmt", GetAbsOrigin(), FALSE );
+ m_pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation );
+ m_pSprite->SetAttachment( this, 0 );
+ m_pSprite->SetScale( 0.5 );
+
+ UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) );
+ UTIL_SetOrigin( this, GetAbsOrigin() );
+
+ SetThink( &CNPC_ControllerZapBall::AnimateThink );
+ SetTouch( &CNPC_ControllerZapBall::ExplodeTouch );
+
+ m_hOwner = GetOwnerEntity();
+
+ m_flSpawnTime = gpGlobals->curtime; // keep track of when ball spawned
+ SetNextThink( gpGlobals->curtime + 0.1 );
+}
+
+
+void CNPC_ControllerZapBall::Precache( void )
+{
+ PrecacheModel( "sprites/xspark4.vmt");
+}
+
+
+void CNPC_ControllerZapBall::AnimateThink( void )
+{
+ SetNextThink( gpGlobals->curtime + 0.1 );
+
+ SetCycle( ((int)GetCycle() + 1) % 11 );
+
+ if (gpGlobals->curtime - m_flSpawnTime > 5 || GetAbsVelocity().Length() < 10)
+ {
+ SetTouch( NULL );
+ Kill();
+ }
+}
+
+
+void CNPC_ControllerZapBall::ExplodeTouch( CBaseEntity *pOther )
+{
+ if (m_takedamage = DAMAGE_YES )
+ {
+ trace_t tr;
+ tr = GetTouchTrace( );
+
+ ClearMultiDamage( );
+
+ Vector vecAttackDir = GetAbsVelocity();
+ VectorNormalize( vecAttackDir );
+
+ if (m_hOwner != NULL)
+ {
+ CTakeDamageInfo info( this, m_hOwner, sk_controller_dmgball.GetFloat(), DMG_ENERGYBEAM );
+ CalculateMeleeDamageForce( &info, vecAttackDir, tr.endpos );
+ pOther->DispatchTraceAttack( info, vecAttackDir, &tr );
+ }
+ else
+ {
+ CTakeDamageInfo info( this, this, sk_controller_dmgball.GetFloat(), DMG_ENERGYBEAM );
+ CalculateMeleeDamageForce( &info, vecAttackDir, tr.endpos );
+ pOther->DispatchTraceAttack( info, vecAttackDir, &tr );
+ }
+
+ ApplyMultiDamage();
+
+ // void UTIL_EmitAmbientSound( CBaseEntity *entity, const Vector &vecOrigin, const char *samp, float vol, soundlevel_t soundlevel, int fFlags, int pitch, float soundtime /*= 0.0f*/ )
+
+ UTIL_EmitAmbientSound( GetSoundSourceIndex(), tr.endpos, "Controller.ElectroSound", 0.3, SNDLVL_NORM, 0, random->RandomInt( 90, 99 ) );
+ }
+
+ Kill();
+}
+
+void CNPC_ControllerZapBall::Kill( void )
+{
+ UTIL_Remove( m_pSprite );
+ UTIL_Remove( this );
+}