Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

837 lines
25 KiB

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "beam_shared.h"
#include "ai_motor.h"
#include "asw_ai_behavior_charge.h"
#include "ai_hint.h"
#include "ai_navigator.h"
#include "ai_memory.h"
#include "asw_alien.h"
#include "asw_marine.h"
#include "asw_gamerules.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
BEGIN_DATADESC( CAI_ASW_ChargeBehavior )
END_DATADESC();
LINK_BEHAVIOR_TO_CLASSNAME( CAI_ASW_ChargeBehavior );
Activity ACT_ALIEN_CHARGE_START;
Activity ACT_ALIEN_CHARGE_STOP;
Activity ACT_ALIEN_CHARGE_CRASH;
Activity ACT_ALIEN_CHARGE_RUN;
Activity ACT_ALIEN_CHARGE_ANTICIPATION;
Activity ACT_ALIEN_CHARGE_HIT;
ConVar asw_debug_charging( "asw_debug_charging", "0", FCVAR_CHEAT, "Visualize charging" );
//------------------------------------------------------------------------------
// Purpose: constructor
//------------------------------------------------------------------------------
CAI_ASW_ChargeBehavior::CAI_ASW_ChargeBehavior( )
{
m_flMinRange = 200.0f;
m_flMaxRange = 600.0f;
m_flAngle = cos( DEG2RAD( 90.0f - 15.0f ) );
m_IsCrouched = UTL_INVAL_SYMBOL;
m_iChargeMisses = 0;
m_flMinDamage = 1.0f;
m_flMaxDamage = 1.0f;
m_bDecidedNotToStop = false;
m_flRetryChargeChance = 0.0f;
m_FinishedChargeParm = UTL_INVAL_SYMBOL;
}
//------------------------------------------------------------------------------
// Purpose: function to set up parameters
// Input : szKeyName - the name of the key
// szValue - the value to be set
// Output : returns true of we handled this key
//------------------------------------------------------------------------------
bool CAI_ASW_ChargeBehavior::KeyValue( const char *szKeyName, const char *szValue )
{
if ( V_stricmp( szKeyName, "min_range" ) == 0 )
{
m_flMinRange = atof( szValue );
return true;
}
else if ( V_stricmp( szKeyName, "max_range" ) == 0 )
{
m_flMaxRange = atof( szValue );
return true;
}
else if ( V_stricmp( szKeyName, "angle" ) == 0 )
{
m_flAngle = cos( DEG2RAD( 90.0f - atof( szValue ) ) );
return true;
}
else if ( V_stricmp( szKeyName, "is_crouched" ) == 0 )
{
m_IsCrouched = GetSymbol( szValue );
return true;
}
else if ( V_stricmp( szKeyName, "min_damage" ) == 0 )
{
m_flMinDamage = atof( szValue );
return true;
}
else if ( V_stricmp( szKeyName, "max_damage" ) == 0 )
{
m_flMaxDamage = atof( szValue );
return true;
}
else if ( V_stricmp( szKeyName, "finished_charge_param" ) == 0 )
{
m_FinishedChargeParm = GetSymbol( szValue );
return true;
}
else if ( V_stricmp( szKeyName, "retry_charge_chance" ) == 0 )
{
m_flRetryChargeChance = atof( szValue );
return true;
}
return BaseClass::KeyValue( szKeyName, szValue );
}
//------------------------------------------------------------------------------
// Purpose: precaches any additional assets this behavior needs
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::Precache( void )
{
BaseClass::Precache();
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::Init( )
{
#if 0
CASW_Alien *pNPC = static_cast< CASW_Alien * >( GetOuter() );
if ( !pNPC )
{
return;
}
#endif
}
//------------------------------------------------------------------------------
// Purpose: determines if we can use this behavior currently
// Output : returns true if this behavior is able to run
//------------------------------------------------------------------------------
bool CAI_ASW_ChargeBehavior::CanSelectSchedule()
{
if ( !GetOuter()->IsInterruptable() )
{
return false;
}
if ( HasCondition( COND_CHARGE_CAN_CHARGE ) == false )
{
return false;
}
return BaseClass::CanSelectSchedule();
}
//------------------------------------------------------------------------------
// Purpose: sets / clears conditions for when the behavior is active. this is
// generally a larger set of conditions to interrupt any tasks.
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::GatherConditions( )
{
BaseClass::GatherConditions();
}
//------------------------------------------------------------------------------
// Purpose: sets / clears conditions for when the behavior is not active. this is
// mainly to have a smaller set of conditions to wake up the behavior.
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::GatherConditionsNotActive( )
{
BaseClass::GatherConditionsNotActive();
ClearCondition( COND_CHARGE_CAN_CHARGE );
Vector vRightNPC, vForwardNPC, vForwardEnemy;
AngleVectors( GetAbsAngles(), &vForwardNPC, &vRightNPC, NULL );
int nTotalPotential = GetEnemies()->NumEnemies();
CBaseEntity **pEligiblePlayers = ( CBaseEntity ** )stackalloc( sizeof( CBaseEntity * ) * nTotalPotential );
int nEligibleCount = 0;
AIEnemiesIter_t iter;
for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
{
CBaseEntity *pEntity = pEMemory->hEnemy;
Vector vDelta = GetAbsOrigin() - pEntity->GetAbsOrigin();
vForwardEnemy = vDelta;
float flLen = vForwardEnemy.NormalizeInPlace();
if ( flLen < m_flMinRange || flLen > m_flMaxRange )
{
continue;
}
float flResult = vForwardNPC.Dot( vForwardEnemy );
if ( flResult > 0.0f )
{ // we are behind
continue;
}
flResult = vRightNPC.Dot( vForwardEnemy );
if ( flResult >= -m_flAngle && flResult <= m_flAngle )
{
pEligiblePlayers[ nEligibleCount ] = pEntity;
nEligibleCount++;
}
}
int nBestEnemy = -1;
int nBestThreat = -999;
for ( int i = 0; i < nEligibleCount; i++ )
{
int nThreat = GetOuter()->IRelationPriority( pEligiblePlayers[ i ] );
if ( nThreat > nBestThreat )
{
nBestEnemy = i;
nBestThreat = nThreat;
}
}
if ( nBestEnemy >= 0 )
{
SetCondition( COND_CHARGE_CAN_CHARGE );
m_ChargeDestination = pEligiblePlayers[ nBestEnemy ]->GetAbsOrigin();
}
}
//------------------------------------------------------------------------------
// Purpose: general purpose routine to collect conditions used both during active
// and non-active states of the behavior.
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::GatherCommonConditions( )
{
BaseClass::GatherCommonConditions();
}
//------------------------------------------------------------------------------
// Purpose: routine called to start when a task initially starts
// Input : pTask - the task structure
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_CHARGE_MOVE:
{
// HACK: Because the alien stops running his normal blended movement
// here, he also needs to remove his blended movement layers!
GetMotor()->MoveStop();
Activity MovementActivity = HaveSequenceForActivity( ACT_ALIEN_CHARGE_START ) ? ACT_ALIEN_CHARGE_START : ACT_ALIEN_CHARGE_RUN;
if ( !HaveSequenceForActivity( MovementActivity ) && HaveSequenceForActivity( ACT_RUN_AGITATED ) )
{
MovementActivity = ACT_RUN_AGITATED;
}
if ( m_IsCrouched != UTL_INVAL_SYMBOL )
{
if ( GetBehaviorParam( m_IsCrouched ) != 0 )
{
MovementActivity = ACT_RUN_CROUCH;
}
}
SetActivity( MovementActivity );
m_bDecidedNotToStop = false;
}
break;
default:
BaseClass::StartTask( pTask );
break;
}
}
//------------------------------------------------------------------------------
// Purpose: routine called every frame when a task is running
// Input : pTask - the task structure
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::RunTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_CHARGE_MOVE:
{
Activity eActivity = GetOuter()->GetActivity();
// TODO: If it's frozen, crash instantly?
// See if we're trying to stop after hitting/missing our target
if ( eActivity == ACT_ALIEN_CHARGE_STOP || eActivity == ACT_ALIEN_CHARGE_CRASH )
{
if ( GetOuter()->IsActivityFinished() )
{
SetBehaviorParam( m_FinishedChargeParm, 1 );
TaskComplete();
return;
}
// Still in the process of slowing down. Run movement until it's done.
GetOuter()->AutoMovement();
return;
}
// Check for manual transition
if ( ( eActivity == ACT_ALIEN_CHARGE_START ) && ( GetOuter()->IsActivityFinished() ) )
{
SetActivity( ACT_ALIEN_CHARGE_RUN );
}
// See if we're still running
if ( eActivity == ACT_ALIEN_CHARGE_RUN || eActivity == ACT_ALIEN_CHARGE_START )
{
if ( HasCondition( COND_NEW_ENEMY ) || HasCondition( COND_LOST_ENEMY ) || HasCondition( COND_ENEMY_DEAD ) )
{
if ( HaveSequenceForActivity( ACT_ALIEN_CHARGE_STOP ) )
{
SetActivity( ACT_ALIEN_CHARGE_STOP );
}
return;
}
else
{
if ( GetEnemy() != NULL )
{
Vector goalDir = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
VectorNormalize( goalDir );
if ( DotProduct( GetOuter()->BodyDirection2D(), goalDir ) < 0.25f )
{
if ( !m_bDecidedNotToStop )
{
// We've missed the target. Randomly decide not to stop, which will cause
// the guard to just try and swing around for another pass.
if ( RandomFloat() >= m_flRetryChargeChance )
{
m_iChargeMisses++;
if ( HaveSequenceForActivity( ACT_ALIEN_CHARGE_STOP ) )
{
SetActivity( ACT_ALIEN_CHARGE_STOP );
}
m_bDecidedNotToStop = false;
}
else
{
m_bDecidedNotToStop = true;
}
}
}
else
{
m_bDecidedNotToStop = false;
}
}
}
}
// Steer towards our target
float idealYaw;
if ( GetEnemy() == NULL )
{
idealYaw = GetMotor()->GetIdealYaw();
}
else
{
idealYaw = GetOuter()->CalcIdealYaw( GetEnemy()->GetAbsOrigin() );
}
// Add in our steering offset
idealYaw += ChargeSteer();
// Turn to face
GetMotor()->SetIdealYawAndUpdate( idealYaw );
// See if we're going to run into anything soon
ChargeLookAhead();
// Let our animations simply move us forward. Keep the result
// of the movement so we know whether we've hit our target.
AIMoveTrace_t moveTrace;
if ( GetOuter()->AutoMovement( GetEnemy(), &moveTrace ) == false )
{
// Only stop if we hit the world
if ( HandleChargeImpact( moveTrace.vEndPosition, moveTrace.pObstruction ) )
{
// If we're starting up, this is an error
if ( eActivity == ACT_ALIEN_CHARGE_START )
{
TaskFail( "Unable to make initial movement of charge\n" );
return;
}
// Crash unless we're trying to stop already
if ( eActivity != ACT_ALIEN_CHARGE_STOP && HaveSequenceForActivity( ACT_ALIEN_CHARGE_STOP ) )
{
if ( ( moveTrace.fStatus == AIMR_BLOCKED_WORLD && moveTrace.vHitNormal == vec3_origin )
|| !HaveSequenceForActivity( ACT_ALIEN_CHARGE_CRASH ) )
{
SetActivity( ACT_ALIEN_CHARGE_STOP );
}
else
{
SetActivity( ACT_ALIEN_CHARGE_CRASH );
}
}
}
else if ( moveTrace.pObstruction )
{
// If we hit an antlion, don't stop, but kill it
if ( moveTrace.pObstruction->Classify() == CLASS_ANTLION )
{
if ( FClassnameIs( moveTrace.pObstruction, "npc_antlionguard" ) )
{
// Crash unless we're trying to stop already
if ( eActivity != ACT_ALIEN_CHARGE_STOP && HaveSequenceForActivity( ACT_ALIEN_CHARGE_STOP ) )
{
SetActivity( ACT_ALIEN_CHARGE_STOP );
}
}
else
{
ApplyChargeDamage( moveTrace.pObstruction, moveTrace.pObstruction->GetHealth() );
}
}
}
}
}
break;
default:
BaseClass::RunTask( pTask );
break;
}
}
//------------------------------------------------------------------------------
// Purpose: routine called to select what schedule we want to run
// Output : returns the schedule id of the schedule we want to run
//------------------------------------------------------------------------------
int CAI_ASW_ChargeBehavior::SelectSchedule()
{
return SCHED_CHARGE_DO;
}
//------------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::HandleBehaviorEvent( CBaseEntity *pInflictor, BehaviorEvent_t eEvent, int nParm )
{
#if 0
switch( eEvent )
{
}
#endif
}
void CAI_ASW_ChargeBehavior::ChargeDamage( CBaseEntity *pTarget )
{
float flMinDamage = ASWGameRules()->ModifyAlienDamageBySkillLevel( m_flMinDamage );
float flMaxDamage = ASWGameRules()->ModifyAlienDamageBySkillLevel( m_flMaxDamage );
flMinDamage = ( flMinDamage < 1.0f ? 1.0f : flMinDamage );
flMaxDamage = ( flMaxDamage < 1.0f ? 1.0f : flMaxDamage );
ApplyChargeDamage( pTarget, RandomFloat( flMinDamage, flMaxDamage ) );
}
//-----------------------------------------------------------------------------
// Purpose: Calculate & apply damage & force for a charge to a target.
// Done outside of the guard because we need to do this inside a trace filter.
//-----------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::ApplyChargeDamage( CBaseEntity *pTarget, float flDamage )
{
Vector attackDir = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() );
VectorNormalize( attackDir );
Vector offset = RandomVector( -32, 32 ) + pTarget->WorldSpaceCenter();
// Generate enough force to make a 75kg guy move away at 700 in/sec
Vector vecForce = attackDir * ImpulseScale( 75, 700 );
// Deal the damage
CTakeDamageInfo info( GetOuter(), GetOuter(), vecForce, offset, flDamage, DMG_CLUB );
pTarget->TakeDamage( info );
}
//-----------------------------------------------------------------------------
// Purpose: A simple trace filter class to skip small moveable physics objects
//-----------------------------------------------------------------------------
class CTraceFilterCharge : public CTraceFilter
{
public:
// It does have a base, but we'll never network anything below here..
DECLARE_CLASS_NOBASE( CTraceFilterCharge );
CTraceFilterCharge( const IHandleEntity *passentity, int collisionGroup, float minMass )
: m_pPassEnt(passentity), m_collisionGroup(collisionGroup), m_minMass(minMass)
{
}
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
{
if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
return false;
if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) )
return false;
// Don't test if the game code tells us we should ignore this collision...
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
if ( pEntity )
{
if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
return false;
if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
return false;
// don't test small moveable physics objects (unless it's an NPC)
if ( !pEntity->IsNPC() && pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
{
IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
Assert(pPhysics);
if ( pPhysics->IsMoveable() && pPhysics->GetMass() < m_minMass )
return false;
}
// TODO: If we hit certain alien types, don't stop, but kill them?
// if ( pEntity->Classify() == CLASS_ANTLION )
// {
// CBaseEntity *pGuard = (CBaseEntity*)EntityFromEntityHandle( m_pPassEnt );
// ApplyChargeDamage( pGuard, pEntity, pEntity->GetHealth() );
// return false;
// }
}
return true;
}
private:
const IHandleEntity *m_pPassEnt;
int m_collisionGroup;
float m_minMass;
};
//-----------------------------------------------------------------------------
inline void TraceHull_Charge( const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
int collisionGroup, trace_t *ptr, float minMass )
{
Ray_t ray;
ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
CTraceFilterCharge traceFilter( ignore, collisionGroup, minMass );
enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
}
//-----------------------------------------------------------------------------
// Purpose: Return true if our charge target is right in front of the guard
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CAI_ASW_ChargeBehavior::EnemyIsRightInFrontOfMe( CBaseEntity **pEntity )
{
if ( !GetEnemy() )
return false;
if ( (GetEnemy()->WorldSpaceCenter() - WorldSpaceCenter()).LengthSqr() < (156*156) )
{
Vector vecLOS = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
vecLOS.z = 0;
VectorNormalize( vecLOS );
Vector vBodyDir = GetOuter()->BodyDirection2D();
if ( DotProduct( vecLOS, vBodyDir ) > 0.8 )
{
// He's in front of me, and close. Make sure he's not behind a wall.
trace_t tr;
UTIL_TraceLine( WorldSpaceCenter(), GetEnemy()->EyePosition(), MASK_SOLID, GetOuter(), COLLISION_GROUP_NONE, &tr );
if ( tr.m_pEnt == GetEnemy() )
{
*pEntity = tr.m_pEnt;
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: While charging, look ahead and see if we're going to run into anything.
// If we are, start the gesture so it looks like we're anticipating the hit.
//-----------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::ChargeLookAhead( void )
{
trace_t tr;
Vector vecForward;
GetOuter()->GetVectors( &vecForward, NULL, NULL );
Vector vecTestPos = GetAbsOrigin() + ( vecForward * GetOuter()->m_flGroundSpeed * 0.75 );
Vector testHullMins = GetHullMins();
testHullMins.z += (GetOuter()->StepHeight() * 2);
TraceHull_Charge( GetAbsOrigin(), vecTestPos, testHullMins, GetHullMaxs(), MASK_SHOT_HULL, GetOuter(), COLLISION_GROUP_NONE, &tr, GetMass() * 0.5 );
//NDebugOverlay::Box( tr.startpos, testHullMins, GetHullMaxs(), 0, 255, 0, true, 0.1f );
//NDebugOverlay::Box( vecTestPos, testHullMins, GetHullMaxs(), 255, 0, 0, true, 0.1f );
if ( tr.fraction != 1.0 && HaveSequenceForActivity( ACT_ALIEN_CHARGE_ANTICIPATION ) )
{
// Start playing the hit animation
GetOuter()->AddGesture( ACT_ALIEN_CHARGE_ANTICIPATION );
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles the guard charging into something. Returns true if it hit the world.
//-----------------------------------------------------------------------------
bool CAI_ASW_ChargeBehavior::HandleChargeImpact( Vector vecImpact, CBaseEntity *pEntity )
{
// Cause a shock wave from this point which will disrupt nearby physics objects
//ImpactShock( vecImpact, 128, 350 );
// Did we hit anything interesting?
if ( !pEntity || pEntity->IsWorld() )
{
// Robin: Due to some of the finicky details in the motor, the guard will hit
// the world when it is blocked by our enemy when trying to step up
// during a moveprobe. To get around this, we see if the enemy's within
// a volume in front of the guard when we hit the world, and if he is,
// we hit him anyway.
EnemyIsRightInFrontOfMe( &pEntity );
// Did we manage to find him? If not, increment our charge miss count and abort.
if ( pEntity->IsWorld() )
{
m_iChargeMisses++;
return true;
}
}
// Hit anything we don't like
if ( GetOuter()->IRelationType( pEntity ) == D_HT && ( GetOuter()->GetNextAttack() < gpGlobals->curtime ) )
{
GetOuter()->EmitSound( "NPC_AntlionGuard.Shove" );
if ( !GetOuter()->IsPlayingGesture( ACT_ALIEN_CHARGE_HIT ) && HaveSequenceForActivity( ACT_ALIEN_CHARGE_HIT ) )
{
GetOuter()->RestartGesture( ACT_ALIEN_CHARGE_HIT );
}
ChargeDamage( pEntity );
Vector vecImpulse = ( GetOuter()->BodyDirection2D() * 400 ) + Vector( 0, 0, 200 );
CASW_Marine *pMarine = CASW_Marine::AsMarine( pEntity );
if ( pMarine )
{
pMarine->Knockdown( GetOuter(), vecImpulse );
}
else
{
pEntity->ApplyAbsVelocityImpulse( vecImpulse );
}
if ( !pEntity->IsAlive() && GetEnemy() == pEntity )
{
GetOuter()->SetEnemy( NULL );
}
GetOuter()->SetNextAttack( gpGlobals->curtime + 2.0f );
if ( HaveSequenceForActivity( ACT_ALIEN_CHARGE_STOP ) )
{
SetActivity( ACT_ALIEN_CHARGE_STOP );
}
// We've hit something, so clear our miss count
m_iChargeMisses = 0;
return false;
}
// Hit something we don't hate. If it's not moveable, crash into it.
if ( pEntity->GetMoveType() == MOVETYPE_NONE || pEntity->GetMoveType() == MOVETYPE_PUSH )
return true;
// If it's a vphysics object that's too heavy, crash into it too.
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
{
IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
if ( pPhysics )
{
if ( (!pPhysics->IsMoveable() || pPhysics->GetMass() > GetMass() * 0.5f ) )
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : float
//-----------------------------------------------------------------------------
float CAI_ASW_ChargeBehavior::ChargeSteer( void )
{
trace_t tr;
Vector testPos, steer, forward, right;
QAngle angles;
const float testLength = GetOuter()->m_flGroundSpeed * 0.15f;
//Get our facing
GetOuter()->GetVectors( &forward, &right, NULL );
steer = forward;
const float faceYaw = UTIL_VecToYaw( forward );
//Offset right
VectorAngles( forward, angles );
angles[YAW] += 45.0f;
AngleVectors( angles, &forward );
//Probe out
testPos = GetAbsOrigin() + ( forward * testLength );
//Offset by step height
Vector testHullMins = GetHullMins();
testHullMins.z += (GetOuter()->StepHeight() * 2);
//Probe
TraceHull_Charge( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, GetOuter(), COLLISION_GROUP_NONE, &tr, GetMass() * 0.5f );
//Debug info
if ( asw_debug_charging.GetInt() == 1 )
{
if ( tr.fraction == 1.0f )
{
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
}
else
{
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
}
}
//Add in this component
steer += ( right * 0.5f ) * ( 1.0f - tr.fraction );
//Offset left
angles[YAW] -= 90.0f;
AngleVectors( angles, &forward );
//Probe out
testPos = GetAbsOrigin() + ( forward * testLength );
// Probe
TraceHull_Charge( GetAbsOrigin(), testPos, testHullMins, GetHullMaxs(), MASK_SOLID_BRUSHONLY, GetOuter(), COLLISION_GROUP_NONE, &tr, GetMass() * 0.5f );
//Debug
if ( asw_debug_charging.GetInt() == 1 )
{
if ( tr.fraction == 1.0f )
{
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 0, 255, 0, 8, 0.1f );
}
else
{
NDebugOverlay::BoxDirection( GetAbsOrigin(), testHullMins, GetHullMaxs() + Vector(testLength,0,0), forward, 255, 0, 0, 8, 0.1f );
}
}
//Add in this component
steer -= ( right * 0.5f ) * ( 1.0f - tr.fraction );
//Debug
if ( asw_debug_charging.GetInt() == 1 )
{
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steer * 512.0f ), 255, 255, 0, true, 0.1f );
NDebugOverlay::Cross3D( GetAbsOrigin() + ( steer * 512.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 255, 0, true, 0.1f );
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( GetOuter()->BodyDirection3D() * 256.0f ), 255, 0, 255, true, 0.1f );
NDebugOverlay::Cross3D( GetAbsOrigin() + ( GetOuter()->BodyDirection3D() * 256.0f ), Vector(2,2,2), -Vector(2,2,2), 255, 0, 255, true, 0.1f );
}
return UTIL_AngleDiff( UTIL_VecToYaw( steer ), faceYaw );
}
float CAI_ASW_ChargeBehavior::GetMass()
{
if ( GetOuter()->VPhysicsGetObject() )
{
return GetOuter()->VPhysicsGetObject()->GetMass();
}
return 100.0f;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CAI_ASW_ChargeBehavior::OnStartSchedule( int scheduleType )
{
// Only go into this schedule/state once and make.
if ( scheduleType == SCHED_CHARGE_DO )
{
SetBehaviorParam( m_FinishedChargeParm, 0 );
}
BaseClass::OnStartSchedule( scheduleType );
}
AI_BEGIN_CUSTOM_SCHEDULE_PROVIDER( CAI_ASW_ChargeBehavior )
DECLARE_TASK( TASK_CHARGE_MOVE )
DECLARE_ACTIVITY( ACT_ALIEN_CHARGE_START )
DECLARE_ACTIVITY( ACT_ALIEN_CHARGE_STOP )
DECLARE_ACTIVITY( ACT_ALIEN_CHARGE_CRASH )
DECLARE_ACTIVITY( ACT_ALIEN_CHARGE_RUN )
DECLARE_ACTIVITY( ACT_ALIEN_CHARGE_ANTICIPATION )
DECLARE_ACTIVITY( ACT_ALIEN_CHARGE_HIT )
DEFINE_SCHEDULE
(
SCHED_CHARGE_DO,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_CHARGE_MOVE 0"
""
" Interrupts"
" COND_TASK_FAILED"
);
AI_END_CUSTOM_SCHEDULE_PROVIDER()
#include "tier0/memdbgoff.h"