//========= 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 { typedef CAI_ComponentWithOuter 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 ); }