//========= 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"