source-engine/game/server/hl1/hl1_npc_controller.cpp
2022-04-16 12:05:19 +03:00

1314 lines
32 KiB
C++

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