// Our Swarm Drone - the basic angry fighting alien #include "cbase.h" #include "asw_alien_shover.h" #include "ai_hint.h" #include "ai_memory.h" #include "ai_moveprobe.h" #include "npcevent.h" #include "IEffects.h" #include "ndebugoverlay.h" #include "soundent.h" #include "soundenvelope.h" #include "ai_squad.h" #include "ai_network.h" #include "ai_pathfinder.h" #include "ai_navigator.h" #include "ai_senses.h" #include "ai_blended_movement.h" #include "physics_prop_ragdoll.h" #include "iservervehicle.h" #include "player_pickup.h" #include "props.h" #include "antlion_dust.h" #include "decals.h" #include "prop_combine_ball.h" #include "eventqueue.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define ALIEN_SHOVER_MAX_OBJECTS 128 #define ALIEN_SHOVER_MIN_OBJECT_MASS 8 #define ALIEN_SHOVER_OBJECTFINDING_FOV 0.7 // stats for shove attack #define ALIEN_SHOVER_MELEE1_RANGE 156.0f #define ALIEN_SHOVER_MELEE1_CONE 0.7f #define ALIEN_SWAT_DELAY 5.0f #define SHOVER_PHYSICS_SEARCH_DEPTH 100 #define SHOVER_PLAYER_MAX_SWAT_DIST 1000 #define SHOVER_FARTHEST_PHYSICS_OBJECT 40.0*12.0 #define SHOVER_PHYSOBJ_SWATDIST 100 ConVar asw_debug_aliens("asw_debug_aliens", "0", FCVAR_CHEAT); ConVar asw_alien_shover_speed("asw_alien_shover_speed", "50", 0, "Speed of a 75kg object thrown by an alien"); Activity ACT_ALIEN_SHOVER_SHOVE_PHYSOBJECT; Activity ACT_ALIEN_SHOVER_ROAR; // Anim events int AE_ALIEN_SHOVER_SHOVE_PHYSOBJECT; int AE_ALIEN_SHOVER_SHOVE; int AE_ALIEN_SHOVER_ROAR; CASW_Alien_Shover::CASW_Alien_Shover( void )// : CASW_Alien() { } LINK_ENTITY_TO_CLASS( asw_alien_shover, CASW_Alien_Shover ); BEGIN_DATADESC( CASW_Alien_Shover ) DEFINE_FIELD( m_hShoveTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_hOldTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_hLastFailedPhysicsTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_hPhysicsTarget, FIELD_EHANDLE ), DEFINE_FIELD( m_vecPhysicsTargetStartPos, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecPhysicsHitPosition, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_flPhysicsCheckTime, FIELD_TIME ), DEFINE_FIELD( m_flNextSwat, FIELD_TIME ), DEFINE_FIELD( m_hObstructor, FIELD_EHANDLE ), DEFINE_FIELD( m_bCanRoar, FIELD_BOOLEAN ), DEFINE_FIELD( m_flNextRoarTime, FIELD_TIME ), DEFINE_INPUTFUNC( FIELD_STRING, "SetShoveTarget", InputSetShoveTarget ), END_DATADESC() //============================================================================================== // ALIEN SHOVER PHYSICS DAMAGE TABLE //============================================================================================== static impactentry_t alienShoverLinearTable[] = { { 100*100, 10 }, { 250*250, 25 }, { 350*350, 50 }, { 500*500, 75 }, { 1000*1000,100 }, }; static impactentry_t alienShoverAngularTable[] = { { 50* 50, 10 }, { 100*100, 25 }, { 150*150, 50 }, { 200*200, 75 }, }; impactdamagetable_t gAlienShoverImpactDamageTable = { alienShoverLinearTable, alienShoverAngularTable, ARRAYSIZE(alienShoverLinearTable), ARRAYSIZE(alienShoverAngularTable), 200*200,// minimum linear speed squared 180*180,// minimum angular speed squared (360 deg/s to cause spin/slice damage) 15, // can't take damage from anything under 15kg 10, // anything less than 10kg is "small" 5, // never take more than 1 pt of damage from anything under 15kg 128*128,// <15kg objects must go faster than 36 in/s to do damage 45, // large mass in kg 2, // large mass scale (anything over 500kg does 4X as much energy to read from damage table) 1, // large mass falling scale 0, // my min velocity }; //----------------------------------------------------------------------------- // Purpose: // Output : const impactdamagetable_t //----------------------------------------------------------------------------- const impactdamagetable_t &CASW_Alien_Shover::GetPhysicsImpactDamageTable( void ) { return gAlienShoverImpactDamageTable; } void CASW_Alien_Shover::Spawn() { m_flPhysicsCheckTime = 0; m_flNextSwat = gpGlobals->curtime; m_hShoveTarget = NULL; m_hPhysicsTarget = NULL; m_hLastFailedPhysicsTarget = NULL; m_bCanRoar = true; m_flNextRoarTime = 0; PrecacheScriptSound("ASW_Drone.Shove"); BaseClass::Spawn(); } void CASW_Alien_Shover::Activate( void ) { BaseClass::Activate(); // Find all nearby physics objects and add them to the list of objects we will sense CBaseEntity *pObject = NULL; while ( ( pObject = gEntList.FindEntityInSphere( pObject, WorldSpaceCenter(), 2500 ) ) != NULL ) { // Can't throw around debris if ( pObject->GetCollisionGroup() == COLLISION_GROUP_DEBRIS ) continue; // We can only throw a few types of things if ( !FClassnameIs( pObject, "prop_physics" ) && !FClassnameIs( pObject, "func_physbox" ) ) continue; // Ensure it's mass is within range IPhysicsObject *pPhysObj = pObject->VPhysicsGetObject(); if( ( pPhysObj == NULL ) || ( pPhysObj->GetMass() > GetMaxShoverObjectMass() ) || ( pPhysObj->GetMass() < ALIEN_SHOVER_MIN_OBJECT_MASS ) ) continue; // Tell the AI sensing list that we want to consider this g_AI_SensedObjectsManager.AddEntity( pObject ); if ( asw_debug_aliens.GetInt() == 5 ) { Msg("Alien Shover: Added prop with model '%s' to sense list.\n", STRING(pObject->GetModelName()) ); pObject->m_debugOverlays |= OVERLAY_BBOX_BIT; } } } //----------------------------------------------------------------------------- // Purpose: Our enemy is unreachable. Select a schedule. //----------------------------------------------------------------------------- int CASW_Alien_Shover::SelectUnreachableSchedule( void ) { // Look for a physics objects nearby m_hLastFailedPhysicsTarget = NULL; UpdatePhysicsTarget( true ); if ( HasCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ) ) { //Msg("Alien shoving phys object from selectunreachableschedule\n"); return SCHED_ALIEN_SHOVER_PHYSICS_ATTACK; } // Otherwise, roar at the player if ( m_bCanRoar && HasCondition(COND_SEE_ENEMY) && m_flNextRoarTime < gpGlobals->curtime ) { m_flNextRoarTime = gpGlobals->curtime + RandomFloat( 20,40 ); return SCHED_ALIEN_SHOVER_ROAR; } return -1; } int CASW_Alien_Shover::SelectCombatSchedule( void ) { //Physics target if ( HasCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ) ) { //Msg("Alien shoving physics object from selectcombatschedule\n"); return SCHED_ALIEN_SHOVER_PHYSICS_ATTACK; } // Attack if we can if ( HasCondition(COND_CAN_MELEE_ATTACK1) ) return SCHED_MELEE_ATTACK1; // See if we can bash what's in our way, or roar if ( HasCondition( COND_ENEMY_UNREACHABLE ) ) { int iUnreach = SelectUnreachableSchedule(); if (iUnreach != -1) return iUnreach; } return BaseClass::SelectSchedule(); } int CASW_Alien_Shover::SelectSchedule( void ) { // Debug physics object finding if ( 0 ) //asw_debug_aliens.GetInt() == 3 ) { m_flPhysicsCheckTime = 0; UpdatePhysicsTarget( false ); return SCHED_IDLE_STAND; } // See if we need to clear a path to our enemy if ( HasCondition( COND_ENEMY_OCCLUDED ) || HasCondition( COND_ENEMY_UNREACHABLE ) ) { CBaseEntity *pBlocker = GetEnemyOccluder(); if ( ( pBlocker != NULL ) && FClassnameIs( pBlocker, "prop_physics" ) ) { m_hPhysicsTarget = pBlocker; //Msg("Alien shoving physics object from selectschedule as enemy is occluded\n"); return SCHED_ALIEN_SHOVER_PHYSICS_ATTACK; } } //Only do these in combat states if ( m_NPCState == NPC_STATE_COMBAT && GetEnemy() ) return SelectCombatSchedule(); return BaseClass::SelectSchedule(); } void CASW_Alien_Shover::Shove( void ) { if ( GetNextAttack() > gpGlobals->curtime ) return; CBaseEntity *pHurt = NULL; CBaseEntity *pTarget; pTarget = ( m_hShoveTarget == NULL ) ? GetEnemy() : m_hShoveTarget.Get(); if ( pTarget == NULL ) { m_hShoveTarget = NULL; return; } //Always damage bullseyes if we're scripted to hit them if ( pTarget->Classify() == CLASS_BULLSEYE ) { pTarget->TakeDamage( CTakeDamageInfo( this, this, 1.0f, DMG_CRUSH ) ); m_hShoveTarget = NULL; return; } //float damage = ( pTarget->IsPlayer() ) ? sk_antlionguard_dmg_shove.GetFloat() : 250; float damage = 250.0f; // If the target's still inside the shove cone, ensure we hit him Vector vecForward, vecEnd; AngleVectors( GetAbsAngles(), &vecForward ); float flDistSqr = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).LengthSqr(); Vector2D v2LOS = ( pTarget->WorldSpaceCenter() - WorldSpaceCenter() ).AsVector2D(); Vector2DNormalize(v2LOS); float flDot = DotProduct2D (v2LOS, vecForward.AsVector2D() ); if ( flDistSqr < (ALIEN_SHOVER_MELEE1_RANGE*ALIEN_SHOVER_MELEE1_RANGE) && flDot >= ALIEN_SHOVER_MELEE1_CONE ) { vecEnd = pTarget->WorldSpaceCenter(); } else { vecEnd = WorldSpaceCenter() + ( BodyDirection3D() * ALIEN_SHOVER_MELEE1_RANGE ); } // Use the melee trace to ensure we hit everything there trace_t tr; CTakeDamageInfo dmgInfo( this, this, damage, DMG_SLASH ); CTraceFilterMelee traceFilter( this, COLLISION_GROUP_NONE, &dmgInfo, 1.0, true ); Ray_t ray; ray.Init( WorldSpaceCenter(), vecEnd, Vector(-16,-16,-16), Vector(16,16,16) ); enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr ); pHurt = tr.m_pEnt; // Knock things around //ImpactShock( tr.endpos, 100.0f, 250.0f ); if ( pHurt ) { Vector traceDir = ( tr.endpos - tr.startpos ); VectorNormalize( traceDir ); // Generate enough force to make a 75kg guy move away at 600 in/sec Vector vecForce = traceDir * ImpulseScale( 75, asw_alien_shover_speed.GetFloat() ); CTakeDamageInfo info( this, this, vecForce, tr.endpos, damage, DMG_CLUB ); pHurt->TakeDamage( info ); m_hShoveTarget = NULL; EmitSound( "ASW_Drone.Shove" ); // If the player, throw him around if ( pHurt->IsPlayer() ) { //Punch the view pHurt->ViewPunch( QAngle(20,0,-20) ); //Shake the screen UTIL_ScreenShake( pHurt->GetAbsOrigin(), 100.0, 1.5, 1.0, 2, SHAKE_START ); //Red damage indicator color32 red = {128,0,0,128}; UTIL_ScreenFade( pHurt, red, 1.0f, 0.1f, FFADE_IN ); Vector forward, up; AngleVectors( GetLocalAngles(), &forward, NULL, &up ); pHurt->ApplyAbsVelocityImpulse( forward * 400 + up * 150 ); } else { CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pHurt ); if ( pVictim ) { pVictim->ApplyAbsVelocityImpulse( BodyDirection2D() * 400 + Vector( 0, 0, 250 ) ); } m_hShoveTarget = NULL; } } } void CASW_Alien_Shover::HandleAnimEvent( animevent_t *pEvent ) { int nEvent = pEvent->Event(); if ( nEvent == AE_ALIEN_SHOVER_SHOVE_PHYSOBJECT ) { if ( m_hPhysicsTarget == NULL ) { // Disrupt other objects near us anyway ImpactShock( WorldSpaceCenter(), 150, 250.0f ); return; } //Setup the throw velocity IPhysicsObject *physObj = m_hPhysicsTarget->VPhysicsGetObject(); Vector targetDir = ( GetEnemy()->GetAbsOrigin() - m_hPhysicsTarget->WorldSpaceCenter() ); float targetDist = VectorNormalize( targetDir ); // Must still be close enough to our target if ( UTIL_DistApprox( WorldSpaceCenter(), m_hPhysicsTarget->WorldSpaceCenter() ) > 300 ) { m_hPhysicsTarget = NULL; return; } if ( targetDist < 512 ) targetDist = 512; if ( targetDist > 1024 ) targetDist = 1024; targetDir *= targetDist * 3 * physObj->GetMass(); //FIXME: Scale by skill targetDir[2] += physObj->GetMass() * 350.0f; //Display dust Vector vecRandom = RandomVector( -1, 1); VectorNormalize( vecRandom ); g_pEffects->Dust( m_hPhysicsTarget->WorldSpaceCenter(), vecRandom, 64.0f, 32 ); // If it's being held by the player, break that bond Pickup_ForcePlayerToDropThisObject( m_hPhysicsTarget ); EmitSound( "ASW_Drone.Shove" ); //Send it flying AngularImpulse angVel( random->RandomFloat(-180, 180), 100, random->RandomFloat(-360, 360) ); physObj->ApplyForceCenter( targetDir ); physObj->AddVelocity( NULL, &angVel ); //Clear the state information, we're done ClearCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); ClearCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET_INVALID ); // Disrupt other objects near it ImpactShock( m_hPhysicsTarget->WorldSpaceCenter(), 150, 250.0f, m_hPhysicsTarget ); m_hPhysicsTarget = NULL; m_flPhysicsCheckTime = gpGlobals->curtime + ALIEN_SWAT_DELAY; return; } if ( nEvent == AE_ALIEN_SHOVER_SHOVE ) { EmitSound("NPC_AntlionGuard.StepLight", pEvent->eventtime ); Shove(); return; } if ( nEvent == AE_ALIEN_SHOVER_ROAR ) { EmitSound( "NPC_AntlionGuard.Roar" ); return; } BaseClass::HandleAnimEvent( pEvent ); } void CASW_Alien_Shover::StartTask( const Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_ALIEN_SHOVER_SHOVE_PHYSOBJECT: { if ( ( m_hPhysicsTarget == NULL ) || ( GetEnemy() == NULL ) ) { TaskFail( "Tried to shove a NULL physics target!\n" ); break; } //Get the direction and distance to our thrown object Vector dirToTarget = ( m_hPhysicsTarget->WorldSpaceCenter() - WorldSpaceCenter() ); float distToTarget = VectorNormalize( dirToTarget ); dirToTarget.z = 0; //Validate it's still close enough to shove //FIXME: Real numbers if ( distToTarget > 256.0f ) { m_hLastFailedPhysicsTarget = NULL; m_hPhysicsTarget = NULL; TaskFail( "Shove target moved\n" ); break; } //Validate its offset from our facing float targetYaw = UTIL_VecToYaw( dirToTarget ); float offset = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( GetLocalAngles().y ) ); if ( fabs( offset ) > 55 ) { m_hLastFailedPhysicsTarget = NULL; m_hPhysicsTarget = NULL; TaskFail( "Shove target off-center\n" ); break; } //Blend properly //SetPoseParameter( "throw", offset ); RemoveAllGestures(); //Start playing the animation SetActivity( ACT_ALIEN_SHOVER_SHOVE_PHYSOBJECT ); } break; case TASK_ALIEN_SHOVER_FIND_PHYSOBJECT: { // Force the antlion guard to find a physobject m_flPhysicsCheckTime = 0; UpdatePhysicsTarget( true, 1024 ); if ( m_hPhysicsTarget ) { TaskComplete(); } else { TaskFail( "Failed to find a physobject.\n" ); } } break; case TASK_ALIEN_SHOVER_GET_PATH_TO_PHYSOBJECT: { Vector vecGoalPos; Vector vecDir; if (!m_hPhysicsTarget.IsValid()) { AI_NavGoal_t goal( GetAbsOrigin() ); GetNavigator()->SetGoal(goal); TaskComplete(); break; } vecDir = GetLocalOrigin() - m_hPhysicsTarget->GetLocalOrigin(); VectorNormalize(vecDir); vecDir.z = 0; AI_NavGoal_t goal( m_hPhysicsTarget->WorldSpaceCenter() ); goal.pTarget = m_hPhysicsTarget; GetNavigator()->SetGoal( goal ); if ( asw_debug_aliens.GetInt() == 1 ) { NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 0, 255, 0, true, 2.0f ); NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 0, 255, 0, true, 2.0f ); NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 0, 255, 0, true, 2.0f ); } TaskComplete(); /* if ( ( m_hPhysicsTarget == NULL ) || ( GetEnemy() == NULL ) ) { TaskFail( "Tried to find a path to NULL physics target!\n" ); break; } Vector vecGoalPos; Vector vecDir; vecDir = GetLocalOrigin() - m_hPhysicsTarget->GetLocalOrigin(); VectorNormalize(vecDir); vecDir.z = 0; AI_NavGoal_t goal( m_hPhysicsTarget->WorldSpaceCenter() ); goal.pTarget = m_hPhysicsTarget; //GetNavigator()->SetGoal( goal ); //TaskComplete(); //Vector vecGoalPos = m_vecPhysicsHitPosition; //AI_NavGoal_t goal( GOALTYPE_LOCATION, vecGoalPos, ACT_RUN ); if ( GetNavigator()->SetGoal( goal ) ) { if ( asw_debug_aliens.GetInt() == 1 ) { NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 0, 255, 0, true, 2.0f ); NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 0, 255, 0, true, 2.0f ); NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 0, 255, 0, true, 2.0f ); } // Face the enemy GetNavigator()->SetArrivalDirection( GetEnemy() ); TaskComplete(); } else { if ( asw_debug_aliens.GetInt() == 1 ) { NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 255, 0, 0, true, 2.0f ); NDebugOverlay::Line( vecGoalPos, m_hPhysicsTarget->WorldSpaceCenter(), 255, 0, 0, true, 2.0f ); NDebugOverlay::Line( m_hPhysicsTarget->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 255, 0, 0, true, 2.0f ); } m_hLastFailedPhysicsTarget = m_hPhysicsTarget; m_hPhysicsTarget = NULL; TaskFail( "Unable to navigate to physics attack target!\n" ); break; } */ } break; case TASK_ALIEN_SHOVER_OPPORTUNITY_THROW: { // If we've got some live antlions, look for a physics object to throw if ( RandomFloat(0,1) > 0.3 ) { m_hLastFailedPhysicsTarget = NULL; UpdatePhysicsTarget( true ); if ( HasCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ) ) { //Msg("Alien shoving from opportunity throw\n"); SetSchedule( SCHED_ALIEN_SHOVER_PHYSICS_ATTACK ); } } TaskComplete(); } break; default: BaseClass::StartTask(pTask); break; } } void CASW_Alien_Shover::RunTask( const Task_t *pTask ) { switch (pTask->iTask) { case TASK_ALIEN_SHOVER_SHOVE_PHYSOBJECT: if ( IsActivityFinished() ) { TaskComplete(); } break; default: BaseClass::RunTask(pTask); break; } } void CASW_Alien_Shover::InputSetShoveTarget( inputdata_t &inputdata ) { if ( IsAlive() == false ) return; CBaseEntity *pTarget = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller ); if ( pTarget == NULL ) { Warning( "**Alien Shover %s cannot find shove target %s\n", GetClassname(), inputdata.value.String() ); m_hShoveTarget = NULL; return; } m_hShoveTarget = pTarget; } void CASW_Alien_Shover::UpdatePhysicsTarget( bool bAllowFartherObjects, float flRadius ) { if ( GetEnemy() == NULL ) return; /*if ( m_hPhysicsTarget != NULL ) { //Check to see if it's moved too much since we first picked it up if ( UTIL_DistApprox( m_hPhysicsTarget->WorldSpaceCenter(), m_vecPhysicsTargetStartPos ) > 256.0f ) { ClearCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); SetCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET_INVALID ); } else { SetCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); ClearCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET_INVALID ); } if ( asw_debug_aliens.GetInt() == 3 ) { NDebugOverlay::Cross3D( m_hPhysicsTarget->WorldSpaceCenter(), -Vector(32,32,32), Vector(32,32,32), 255, 255, 255, true, 0.1f ); } return; }*/ if ( m_flPhysicsCheckTime <= gpGlobals->curtime ) FindNearestPhysicsObject(GetMaxShoverObjectMass()); // max ma //m_hPhysicsTarget = FindPhysicsObjectTarget( GetEnemy(), flRadius, ALIEN_SHOVER_OBJECTFINDING_FOV, bAllowFartherObjects ); return; if ( m_hPhysicsTarget != NULL ) { SetCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); m_vecPhysicsTargetStartPos = m_hPhysicsTarget->WorldSpaceCenter(); m_hLastFailedPhysicsTarget = NULL; // We must steer around this obstacle until we've thrown it, this stops us from // shoving it out of the way while we travel there m_hPhysicsTarget->SetNavIgnore(); } m_flPhysicsCheckTime = gpGlobals->curtime + 2.0f; } //----------------------------------------------------------------------------- // Purpose: Push and sweep away small mass items //----------------------------------------------------------------------------- void CASW_Alien_Shover::SweepPhysicsDebris( void ) { CBaseEntity *pList[ALIEN_SHOVER_MAX_OBJECTS]; CBaseEntity *pObject; IPhysicsObject *pPhysObj; Vector vecDelta(128,128,8); int i; if ( asw_debug_aliens.GetInt() == 1 ) { NDebugOverlay::Box( GetAbsOrigin(), vecDelta, -vecDelta, 255, 0, 0, true, 0.1f ); } int count = UTIL_EntitiesInBox( pList, ALIEN_SHOVER_MAX_OBJECTS, GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, 0 ); for( i = 0, pObject = pList[0]; i < count; i++, pObject = pList[i] ) { if ( pObject == NULL ) continue; // Don't ignore our shoving target if ( pObject == m_hPhysicsTarget ) continue; pPhysObj = pObject->VPhysicsGetObject(); if( pPhysObj == NULL || pPhysObj->GetMass() > GetMaxShoverObjectMass() ) continue; if ( FClassnameIs( pObject, "prop_physics" ) == false ) continue; pObject->SetNavIgnore(); } } void CASW_Alien_Shover::PrescheduleThink( void ) { BaseClass::PrescheduleThink(); // Don't do anything after death if ( m_NPCState == NPC_STATE_DEAD ) return; // Check our current physics target if ( m_hPhysicsTarget != NULL ) { if ( asw_debug_aliens.GetInt() == 1 ) { NDebugOverlay::Cross3D( m_hPhysicsTarget->WorldSpaceCenter(), Vector(15,15,15), -Vector(15,15,15), 0, 255, 0, true, 0.1f ); } } // Automatically update our physics target when chasing enemies if ( IsCurSchedule( SCHED_CHASE_ENEMY ) ) { UpdatePhysicsTarget( false ); } else if ( !IsCurSchedule( SCHED_ALIEN_SHOVER_PHYSICS_ATTACK ) ) { ClearCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); m_hPhysicsTarget = NULL; } //SweepPhysicsDebris(); } //----------------------------------------------------------------------------- // Purpose: Return the point at which the alien wants to stand on to knock the physics object at the enemy //----------------------------------------------------------------------------- Vector CASW_Alien_Shover::GetPhysicsHitPosition( CBaseEntity *pObject, Vector &vecTrajectory, float &flClearDistance ) { Assert( GetEnemy() ); // Get the trajectory we want to knock the object along vecTrajectory = GetEnemy()->WorldSpaceCenter() - pObject->WorldSpaceCenter(); VectorNormalize( vecTrajectory ); vecTrajectory.z = 0; // Get the distance we want to be from the object when we hit it IPhysicsObject *pPhys = pObject->VPhysicsGetObject(); Vector extent = physcollision->CollideGetExtent( pPhys->GetCollide(), pObject->GetAbsOrigin(), pObject->GetAbsAngles(), -vecTrajectory ); flClearDistance = ( extent - pObject->WorldSpaceCenter() ).Length() + CollisionProp()->BoundingRadius(); // asw rem + 32.0f; return (pObject->WorldSpaceCenter() + ( vecTrajectory * -flClearDistance )); } // zombie style search for a phys object bool CASW_Alien_Shover::FindNearestPhysicsObject( int iMaxMass ) { CBaseEntity *pList[ SHOVER_PHYSICS_SEARCH_DEPTH ]; CBaseEntity *pNearest = NULL; float flDist; IPhysicsObject *pPhysObj; int i; Vector vecDirToEnemy; Vector vecDirToObject; if ( !GetEnemy() ) { // Can't swat, or no enemy, so no swat. m_hPhysicsTarget = NULL; return false; } vecDirToEnemy = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); float dist = VectorNormalize(vecDirToEnemy); vecDirToEnemy.z = 0; if( dist > SHOVER_PLAYER_MAX_SWAT_DIST ) { // Player is too far away. Don't bother // trying to swat anything at them until // they are closer. return false; } float flNearestDist = MIN( dist, SHOVER_FARTHEST_PHYSICS_OBJECT * 0.5 ); Vector vecDelta( flNearestDist, flNearestDist, GetHullHeight() * 2.0 ); class CShoverSwatEntitiesEnum : public CFlaggedEntitiesEnum { public: CShoverSwatEntitiesEnum( CBaseEntity **pList, int listMax, int iMaxMass ) : CFlaggedEntitiesEnum( pList, listMax, 0 ), m_iMaxMass( iMaxMass ) { } virtual IterationRetval_t EnumElement( IHandleEntity *pHandleEntity ) { CBaseEntity *pEntity = gEntList.GetBaseEntity( pHandleEntity->GetRefEHandle() ); if ( pEntity && pEntity->VPhysicsGetObject() && pEntity->VPhysicsGetObject()->GetMass() <= m_iMaxMass && pEntity->VPhysicsGetObject()->IsAsleep() && pEntity->VPhysicsGetObject()->IsMoveable() ) { return CFlaggedEntitiesEnum::EnumElement( pHandleEntity ); } return ITERATION_CONTINUE; } int m_iMaxMass; }; CShoverSwatEntitiesEnum swatEnum( pList, SHOVER_PHYSICS_SEARCH_DEPTH, iMaxMass ); int count = UTIL_EntitiesInBox( GetAbsOrigin() - vecDelta, GetAbsOrigin() + vecDelta, &swatEnum ); // magically know where they are Vector vecZombieKnees; CollisionProp()->NormalizedToWorldSpace( Vector( 0.5f, 0.5f, 0.25f ), &vecZombieKnees ); for( i = 0 ; i < count ; i++ ) { pPhysObj = pList[ i ]->VPhysicsGetObject(); Assert( !( !pPhysObj || pPhysObj->GetMass() > iMaxMass || !pPhysObj->IsAsleep() ) ); Vector center = pList[ i ]->WorldSpaceCenter(); flDist = UTIL_DistApprox2D( GetAbsOrigin(), center ); if( flDist >= flNearestDist ) continue; // This object is closer... but is it between the player and the zombie? vecDirToObject = pList[ i ]->WorldSpaceCenter() - GetAbsOrigin(); VectorNormalize(vecDirToObject); vecDirToObject.z = 0; if( DotProduct( vecDirToEnemy, vecDirToObject ) < 0.8 ) continue; if( flDist >= UTIL_DistApprox2D( center, GetEnemy()->GetAbsOrigin() ) ) continue; // don't swat things where the highest point is under my knees // NOTE: This is a rough test; a more exact test is going to occur below if ( (center.z + pList[i]->BoundingRadius()) < vecZombieKnees.z ) continue; // don't swat things that are over my head. if( center.z > EyePosition().z ) continue; vcollide_t *pCollide = modelinfo->GetVCollide( pList[i]->GetModelIndex() ); if (!pCollide) continue; Vector objMins, objMaxs; physcollision->CollideGetAABB( &objMins, &objMaxs, pCollide->solids[0], pList[i]->GetAbsOrigin(), pList[i]->GetAbsAngles() ); if ( objMaxs.z < vecZombieKnees.z ) continue; if ( !FVisible( pList[i] ) ) continue; // Make this the last check, since it makes a string. // Don't swat server ragdolls! if ( FClassnameIs( pList[ i ], "physics_prop_ragdoll" ) ) continue; if ( FClassnameIs( pList[ i ], "prop_ragdoll" ) ) continue; // The object must also be closer to the zombie than it is to the enemy pNearest = pList[ i ]; flNearestDist = flDist; } m_hPhysicsTarget = pNearest; if( m_hPhysicsTarget == NULL ) { return false; } else { return true; } } // antlion style search for a phys object CBaseEntity *CASW_Alien_Shover::FindPhysicsObjectTarget( CBaseEntity *pTarget, float radius, float targetCone, bool allowFartherObjects ) { CBaseEntity *pNearest = NULL; // If we're allowed to look for farther objects, find the nearest object to the guard. // Otherwise, find the nearest object to the vector to the target. float flNearestDist = -1.0; if ( allowFartherObjects ) { flNearestDist = radius; } Vector vecDirToTarget = pTarget->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize( vecDirToTarget ); vecDirToTarget.z = 0; bool bDebug = asw_debug_aliens.GetInt() == 3; if ( bDebug ) { if ( m_hLastFailedPhysicsTarget ) { NDebugOverlay::Cross3D( m_hLastFailedPhysicsTarget->WorldSpaceCenter(), -Vector(32,32,32), Vector(32,32,32) , 255, 255, 0, true, 1.0f ); } } // Traipse through the sensed object list AISightIter_t iter; CBaseEntity *pObject; for ( pObject = GetSenses()->GetFirstSeenEntity( &iter, SEEN_MISC ); pObject; pObject = GetSenses()->GetNextSeenEntity( &iter ) ) { // If we couldn't shove this object last time, don't try again if ( pObject == m_hLastFailedPhysicsTarget ) continue; IPhysicsObject *pPhysObj = pObject->VPhysicsGetObject(); if ( !pPhysObj ) continue; // Ignore motion disabled props if ( !pPhysObj->IsMoveable() ) continue; // Ignore physics objects that are too low to really be noticed by the player Vector vecAbsMins, vecAbsMaxs; pObject->CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs ); if ( fabs(vecAbsMaxs.z - vecAbsMins.z) < 28 ) continue; // Ignore objects moving too fast Vector velocity; pPhysObj->GetVelocity( &velocity, NULL ); if ( velocity.LengthSqr() > (16*16) ) continue; Vector center = pObject->WorldSpaceCenter(); Vector vecDirToObject = pObject->WorldSpaceCenter() - GetAbsOrigin(); VectorNormalize( vecDirToObject ); vecDirToObject.z = 0; float flDist = 0; if ( !allowFartherObjects ) { // Validate our cone of sight if ( DotProduct( vecDirToTarget, vecDirToObject ) < targetCone ) { if ( bDebug ) { NDebugOverlay::Cross3D( center, Vector(15,15,15), -Vector(15,15,15), 255, 0, 255, true, 1.0f ); } continue; } // Object must be closer than our target if ( UTIL_DistApprox2D( GetAbsOrigin(), center ) > UTIL_DistApprox2D( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() ) ) { if ( bDebug ) { NDebugOverlay::Cross3D( center, Vector(15,15,15), -Vector(15,15,15), 0, 255, 255, true, 1.0f ); } continue; } // Must be closer to the line towards the target than a previously valid object flDist = DotProduct( vecDirToTarget, vecDirToObject ); if ( flDist < flNearestDist ) { if ( bDebug ) { NDebugOverlay::Cross3D( center, Vector(15,15,15), -Vector(15,15,15), 255, 0, 0, true, 1.0f ); } continue; } } else { // Must be closer than the nearest phys object flDist = UTIL_DistApprox2D( GetAbsOrigin(), center ); if ( flDist > flNearestDist ) { if ( bDebug ) { NDebugOverlay::Cross3D( center, Vector(15,15,15), -Vector(15,15,15), 255, 0, 0, true, 1.0f ); } continue; } } // Check for a clear shove path (roughly) trace_t tr; UTIL_TraceLine( pObject->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); // See how close to our target we got if ( ( tr.endpos - GetEnemy()->WorldSpaceCenter() ).LengthSqr() > (256*256) ) continue; // Get the position we want to be at to swing at the object float flClearDistance; Vector vecTrajectory; Vector vecHitPosition = GetPhysicsHitPosition( pObject, vecTrajectory, flClearDistance ); Vector vecObjectPosition = pObject->WorldSpaceCenter(); if ( bDebug ) { NDebugOverlay::Box( vecObjectPosition, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 255, 0, 0, true, 1.0f ); NDebugOverlay::Box( vecHitPosition, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 0, 255, 0, true, 1.0f ); } // Can we move to the spot behind the prop? UTIL_TraceHull( vecHitPosition, vecHitPosition, WorldAlignMins(), WorldAlignMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.startsolid || tr.allsolid || (tr.m_pEnt && tr.m_pEnt != pObject) ) { if ( bDebug ) { NDebugOverlay::Box( vecHitPosition, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 8, 1.0f ); NDebugOverlay::Line( vecObjectPosition, vecHitPosition, 255, 0, 0, true, 1.0f ); } // Can we get at it at from an angle on the side of it we're on? Vector vecUp(0,0,1); Vector vecRight; CrossProduct( vecUp, vecTrajectory, vecRight ); // Is the guard to the right? or the left? Vector vecToGuard = ( WorldSpaceCenter() - vecObjectPosition ); VectorNormalize( vecToGuard ); if ( DotProduct( vecRight, vecToGuard ) > 0 ) { vecHitPosition = vecHitPosition + (vecRight * 64) + (vecTrajectory * 64); } else { vecHitPosition = vecHitPosition - (vecRight * 64) + (vecTrajectory * 64); } if ( asw_debug_aliens.GetInt() == 4 ) { NDebugOverlay::Box( vecHitPosition, NAI_Hull::Mins( HULL_MEDIUM ), NAI_Hull::Maxs( HULL_MEDIUM ), 0, 255, 0, true, 1.0f ); NDebugOverlay::Line( vecObjectPosition, vecHitPosition, 255, 0, 0, true, 1.0f ); } // Now try and move from the side position UTIL_TraceHull( vecHitPosition, vecHitPosition, WorldAlignMins(), WorldAlignMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.startsolid || tr.allsolid || (tr.m_pEnt && tr.m_pEnt != pObject) ) { if ( asw_debug_aliens.GetInt() == 4 ) { NDebugOverlay::Box( vecHitPosition, WorldAlignMins(), WorldAlignMaxs(), 255, 0, 0, 8, 1.0f ); NDebugOverlay::Line( vecObjectPosition, vecHitPosition, 255, 0, 0, true, 1.0f ); } continue; } } // Take this as the best object so far pNearest = pObject; flNearestDist = flDist; m_vecPhysicsHitPosition = vecHitPosition; if ( bDebug ) { NDebugOverlay::Cross3D( center, Vector(15,15,15), -Vector(15,15,15), 255, 255, 0, true, 0.5f ); } } if ( pNearest && bDebug ) { NDebugOverlay::Cross3D( pNearest->WorldSpaceCenter(), Vector(30,30,30), -Vector(30,30,30), 255, 255, 255, true, 0.5f ); } return pNearest; } void CASW_Alien_Shover::ImpactShock( const Vector &origin, float radius, float magnitude, CBaseEntity *pIgnored ) { // Also do a local physics explosion to push objects away float adjustedDamage, flDist; Vector vecSpot; float falloff = 1.0f / 2.5f; CBaseEntity *pEntity = NULL; // Find anything within our radius while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, origin, radius ) ) != NULL ) { // Don't affect the ignored target if ( pEntity == pIgnored ) continue; if ( pEntity == this ) continue; // UNDONE: Ask the object if it should get force if it's not MOVETYPE_VPHYSICS? if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || ( pEntity->VPhysicsGetObject() && pEntity->IsPlayer() == false ) ) { vecSpot = pEntity->BodyTarget( GetAbsOrigin() ); // decrease damage for an ent that's farther from the bomb. flDist = ( GetAbsOrigin() - vecSpot ).Length(); if ( radius == 0 || flDist <= radius ) { adjustedDamage = flDist * falloff; adjustedDamage = magnitude - adjustedDamage; if ( adjustedDamage < 1 ) { adjustedDamage = 1; } CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST ); CalculateExplosiveDamageForce( &info, (vecSpot - GetAbsOrigin()), GetAbsOrigin() ); pEntity->VPhysicsTakeDamage( info ); } } } } int CASW_Alien_Shover::TranslateSchedule( int scheduleType ) { switch( scheduleType ) { case SCHED_CHASE_ENEMY: { return SCHED_SHOVER_CHASE_ENEMY; } break; case SCHED_CHASE_ENEMY_FAILED: { int baseType = BaseClass::TranslateSchedule(scheduleType); if ( baseType != SCHED_CHASE_ENEMY_FAILED ) return baseType; int iUnreach = SelectUnreachableSchedule(); if (iUnreach != -1) return iUnreach; } break; case SCHED_ALIEN_SHOVER_PHYSICS_ATTACK: // If the object is far away, move and swat it. If it's close, just swat it. if (random->RandomFloat() < 0.5f) { if( DistToPhysicsEnt() > SHOVER_PHYSOBJ_SWATDIST ) { return SCHED_ALIEN_SHOVER_PHYSICS_ATTACK_MOVE; } else { return SCHED_ALIEN_SHOVER_PHYSICS_ATTACK; } } else { if( DistToPhysicsEnt() > SHOVER_PHYSOBJ_SWATDIST ) { return SCHED_ALIEN_SHOVER_PHYSICS_ATTACKITEM_MOVE; } else { return SCHED_ALIEN_ATTACKITEM; } } break; } return BaseClass::TranslateSchedule( scheduleType ); } void CASW_Alien_Shover::GatherConditions( void ) { BaseClass::GatherConditions(); if( m_NPCState == NPC_STATE_COMBAT ) { UpdatePhysicsTarget( false ); } if( (m_hPhysicsTarget != NULL) && gpGlobals->curtime >= m_flNextSwat && HasCondition( COND_SEE_ENEMY ) && m_AlienOrders == AOT_None) // don't try and throw physics objects if we have specific alien orders to follow { SetCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); } else { ClearCondition( COND_ALIEN_SHOVER_PHYSICS_TARGET ); } } int CASW_Alien_Shover::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) { // If we can swat physics objects, see if we can swat our obstructor if ( IsPathTaskFailure( taskFailCode ) && m_hObstructor != NULL && m_hObstructor->VPhysicsGetObject() && m_hObstructor->VPhysicsGetObject()->GetMass() < 100 ) { m_hPhysicsTarget = m_hObstructor; m_hObstructor = NULL; //Msg("Alien shoving phys object from fail schedule\n"); return SCHED_ALIEN_ATTACKITEM; } m_hObstructor = NULL; return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode ); } bool CASW_Alien_Shover::OnInsufficientStopDist( AILocalMoveGoal_t *pMoveGoal, float distClear, AIMoveResult_t *pResult ) { if ( pMoveGoal->directTrace.fStatus == AIMR_BLOCKED_ENTITY && gpGlobals->curtime >= m_flNextSwat ) { //Msg("Alien setting obstructor\n"); m_hObstructor = pMoveGoal->directTrace.pObstruction; } return false; } float CASW_Alien_Shover::DistToPhysicsEnt( void ) { //return ( GetLocalOrigin() - m_hPhysicsEnt->GetLocalOrigin() ).Length(); if ( m_hPhysicsTarget != NULL ) return UTIL_DistApprox2D( GetAbsOrigin(), m_hPhysicsTarget->WorldSpaceCenter() ); return SHOVER_PHYSOBJ_SWATDIST + 1; } AI_BEGIN_CUSTOM_NPC( npc_alien_shover, CASW_Alien_Shover ) //Tasks DECLARE_TASK( TASK_ALIEN_SHOVER_GET_PATH_TO_PHYSOBJECT ) DECLARE_TASK( TASK_ALIEN_SHOVER_SHOVE_PHYSOBJECT ) DECLARE_TASK( TASK_ALIEN_SHOVER_OPPORTUNITY_THROW ) DECLARE_TASK( TASK_ALIEN_SHOVER_FIND_PHYSOBJECT ) //Activities DECLARE_ACTIVITY( ACT_ALIEN_SHOVER_SHOVE_PHYSOBJECT ) DECLARE_ACTIVITY( ACT_ALIEN_SHOVER_ROAR ) //Adrian: events go here DECLARE_ANIMEVENT( AE_ALIEN_SHOVER_SHOVE_PHYSOBJECT ) DECLARE_ANIMEVENT( AE_ALIEN_SHOVER_SHOVE ) DECLARE_ANIMEVENT( AE_ALIEN_SHOVER_ROAR ) DECLARE_CONDITION( COND_ALIEN_SHOVER_PHYSICS_TARGET ) DECLARE_CONDITION( COND_ALIEN_SHOVER_PHYSICS_TARGET_INVALID ) DEFINE_SCHEDULE ( SCHED_ALIEN_ATTACKITEM, " Tasks" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK1 0" " " " Interrupts" " COND_ENEMY_DEAD" " COND_NEW_ENEMY" ) DEFINE_SCHEDULE ( SCHED_ALIEN_SHOVER_PHYSICS_ATTACKITEM_MOVE, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" " TASK_ALIEN_SHOVER_GET_PATH_TO_PHYSOBJECT 0" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_FACE_ENEMY 0" " TASK_MELEE_ATTACK1 0" "" " Interrupts" " COND_TASK_FAILED" " COND_ENEMY_DEAD" " COND_LOST_ENEMY" " COND_ALIEN_SHOVER_PHYSICS_TARGET_INVALID" ) //================================================== // SCHED_ALIEN_SHOVER_PHYSICS_ATTACK //================================================== DEFINE_SCHEDULE ( SCHED_ALIEN_SHOVER_PHYSICS_ATTACK_MOVE, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" " TASK_ALIEN_SHOVER_GET_PATH_TO_PHYSOBJECT 0" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_FACE_ENEMY 0" " TASK_ALIEN_SHOVER_SHOVE_PHYSOBJECT 0" "" " Interrupts" " COND_TASK_FAILED" " COND_ENEMY_DEAD" " COND_LOST_ENEMY" " COND_ALIEN_SHOVER_PHYSICS_TARGET_INVALID" ) DEFINE_SCHEDULE ( SCHED_ALIEN_SHOVER_PHYSICS_ATTACK, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY" " TASK_FACE_ENEMY 0" " TASK_ALIEN_SHOVER_SHOVE_PHYSOBJECT 0" "" " Interrupts" " COND_ENEMY_DEAD" " COND_LOST_ENEMY" ) //================================================== // SCHED_FORCE_ALIEN_SHOVER_PHYSICS_ATTACK //================================================== DEFINE_SCHEDULE ( SCHED_FORCE_ALIEN_SHOVER_PHYSICS_ATTACK, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ALIEN_SHOVER_CANT_ATTACK" " TASK_ALIEN_SHOVER_FIND_PHYSOBJECT 0" " TASK_SET_SCHEDULE SCHEDULE:SCHED_ALIEN_SHOVER_PHYSICS_ATTACK" "" " Interrupts" ) //================================================== // SCHED_ANTLIONGUARD_ROAR //================================================== DEFINE_SCHEDULE ( SCHED_ALIEN_SHOVER_ROAR, " Tasks" " TASK_STOP_MOVING 0" " TASK_FACE_ENEMY 0" " TASK_PLAY_SEQUENCE ACTIVITY:ACT_ALIEN_SHOVER_ROAR" " " " Interrupts" ) DEFINE_SCHEDULE ( SCHED_ALIEN_SHOVER_CANT_ATTACK, " Tasks" " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck " TASK_GET_PATH_TO_RANDOM_NODE 200" " TASK_WALK_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" " TASK_WAIT_PVS 0" "" " Interrupts" " COND_GIVE_WAY" " COND_NEW_ENEMY" ) DEFINE_SCHEDULE ( SCHED_SHOVER_CHASE_ENEMY, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" //" TASK_SET_TOLERANCE_DISTANCE 24" " TASK_GET_CHASE_PATH_TO_ENEMY 300" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" //" TASK_FACE_ENEMY 0" " " " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_ENEMY_UNREACHABLE" " COND_CAN_RANGE_ATTACK1" " COND_CAN_MELEE_ATTACK1" " COND_CAN_RANGE_ATTACK2" " COND_CAN_MELEE_ATTACK2" " COND_TOO_CLOSE_TO_ATTACK" " COND_TASK_FAILED" " COND_ALIEN_SHOVER_PHYSICS_TARGET" ) AI_END_CUSTOM_NPC()