2543 lines
79 KiB
C++
2543 lines
79 KiB
C++
// Our Swarm Drone - the basic angry fighting alien
|
|
|
|
#include "cbase.h"
|
|
#include "ai_hint.h"
|
|
#include "ai_squad.h"
|
|
#include "ai_moveprobe.h"
|
|
#include "ai_route.h"
|
|
#include "npcevent.h"
|
|
#include "gib.h"
|
|
#include "entitylist.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "antlion_dust.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "globalstate.h"
|
|
#include "movevars_shared.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "vehicle_base.h"
|
|
#include "mapentities.h"
|
|
#include "antlion_maker.h"
|
|
#include "npc_antlion.h"
|
|
#include "decals.h"
|
|
#include "asw_drone_advanced.h"
|
|
#include "asw_player.h"
|
|
#include "asw_fx_shared.h"
|
|
#include "asw_door.h"
|
|
#include "asw_door_padding.h"
|
|
#include "asw_drone_navigator.h"
|
|
#include "asw_drone_movement.h"
|
|
#include "asw_util_shared.h"
|
|
#include "asw_gamerules.h"
|
|
#include "asw_marine.h"
|
|
#include "asw_weapon.h"
|
|
#include "asw_marine_speech.h"
|
|
#include "ammodef.h"
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar asw_drone_melee_range("asw_drone_melee_range", "60.0", FCVAR_CHEAT, "Range of the drone's melee attack");
|
|
ConVar asw_drone_start_melee_range("asw_drone_start_melee_range", "100.0", FCVAR_CHEAT, "Range at which the drone starts his melee attack");
|
|
|
|
#define ASW_DRONE_MELEE1_START_ATTACK_RANGE asw_drone_start_melee_range.GetFloat()
|
|
#define ASW_DRONE_MELEE1_RANGE asw_drone_melee_range.GetFloat()
|
|
|
|
extern ConVar ai_sequence_debug;
|
|
|
|
//todo: 10 seems quite low, given the time delay between swings
|
|
// should do this damage when bumping (maybe a bit less?) and extra damage on each of the 3 swipes of the anim
|
|
ConVar sk_asw_drone_damage( "sk_asw_drone_damage", "15.0", FCVAR_CHEAT, "Damage per swipe from the Drone");
|
|
ConVar asw_drone_speedboost( "asw_drone_speedboost", "1.0",FCVAR_CHEAT , "boost speed for the alien drones" );
|
|
// 0.32 is speedboost equal to movement when movement speedboost is 1.0
|
|
ConVar asw_drone_override_speedboost( "asw_drone_override_speedboost", "0.32",FCVAR_CHEAT , "boost speed for the alien drones when in override mode" );
|
|
// note: UNUSED - drone doesn't do automovement during melee, he does override move
|
|
ConVar asw_drone_auto_speed_scale("asw_drone_auto_speed_scale", "0.5", FCVAR_CHEAT, "Speed scale for the drones while melee attacking");
|
|
ConVar asw_drone_health("asw_drone_health", "40", FCVAR_CHEAT, "How much health the Swarm drones have");
|
|
|
|
// these settings make the drones quite undodgeable and more lethal
|
|
//ConVar asw_drone_yaw_speed("asw_drone_yaw_speed", "32.0", FCVAR_CHEAT, "How fast the swarm drone can turn");
|
|
//ConVar asw_drone_yaw_speed_attacking("asw_drone_yaw_speed_attacking", "16.0", FCVAR_CHEAT, "How fast the swarm drone can turn while doing a melee attack");
|
|
// these settings mean you can dodge aside when a drone starts attacking - makes for better gameplay?
|
|
ConVar asw_drone_yaw_speed("asw_drone_yaw_speed", "32.0", FCVAR_CHEAT, "How fast the swarm drone can turn");
|
|
ConVar asw_drone_yaw_speed_attackprep("asw_drone_yaw_speed_attackprep", "64.0", FCVAR_CHEAT, "How fast the swarm drone can turn while starting his melee attack");
|
|
ConVar asw_drone_yaw_speed_attacking("asw_drone_yaw_speed_attacking", "8.0", FCVAR_CHEAT, "How fast the swarm drone can turn while doing a melee attack");
|
|
ConVar asw_drone_acceleration("asw_drone_acceleration", "5", FCVAR_CHEAT, "How fast the swarm drone accelerates, as a multiplier on his ideal speed");
|
|
ConVar asw_drone_smooth_speed("asw_drone_smooth_speed", "200", FCVAR_CHEAT, "How fast the swarm drone smooths his current velocity into the ideal, when using overidden movement");
|
|
ConVar asw_drone_override_move("asw_drone_override_move", "0", FCVAR_CHEAT, "Enable to make Swarm drones use custom override movement to chase their enemy");
|
|
ConVar asw_drone_override_attack("asw_drone_override_attack", "1", FCVAR_CHEAT, "Enable to make Swarm drones use custom override movement to attack their enemy");
|
|
ConVar asw_drone_friction("asw_drone_friction", "0.1", FCVAR_CHEAT, "Velocity loss due to friction");
|
|
ConVar asw_drone_show_facing("asw_drone_show_facing", "0", FCVAR_CHEAT, "Show which way the drone is facing");
|
|
ConVar asw_drone_gib_chance("asw_drone_gib_chance", "0.075", FCVAR_CHEAT, "Chance of drone gibbing instead of ragdoll");
|
|
ConVar asw_drone_show_override("asw_drone_show_override", "0", FCVAR_CHEAT, "Show a yellow arrow if drone is using override movement");
|
|
ConVar asw_drone_attacks("asw_drone_attacks", "1", FCVAR_CHEAT, "Whether the drones attack or not");
|
|
ConVar asw_debug_drone("asw_debug_drone", "0", FCVAR_CHEAT, "Enable drone debug messages");
|
|
ConVar asw_drone_door_distance("asw_drone_door_distance", "60", FCVAR_CHEAT, "How near to the door a drone needs to be to bash it");
|
|
ConVar asw_drone_door_distance_min("asw_drone_door_distance_min", "40", FCVAR_CHEAT, "Nearest a drone can be to a door when attacking");
|
|
ConVar asw_marine_view_cone_cost("asw_marine_view_cone_cost", "5", FCVAR_CHEAT, "Extra pathing cost if a node is visible to a marine");
|
|
ConVar asw_drone_zig_zag_length("asw_drone_zig_zag_length", "144", FCVAR_CHEAT, "Length of drone zig zagging");
|
|
ConVar asw_drone_zig_zagging("asw_drone_zig_zagging", "0", FCVAR_CHEAT, "If set, aliens will try to zig zag up to their enemy instead of approaching directly");
|
|
ConVar asw_drone_melee_force("asw_drone_melee_force", "1.67", FCVAR_CHEAT, "Force of the drone's melee attack");
|
|
ConVar asw_drone_touch_damage( "asw_drone_touch_damage", "0",FCVAR_CHEAT , "Damage caused by drones on touch" );
|
|
ConVar asw_new_drone("asw_new_drone", "1", FCVAR_CHEAT, "Set to 1 to use the new drone model");
|
|
extern ConVar asw_debug_alien_damage;
|
|
extern ConVar asw_alien_hurt_speed;
|
|
extern ConVar asw_alien_stunned_speed;
|
|
extern ConVar asw_springcol;
|
|
extern ConVar asw_drone_death_force_pitch;
|
|
|
|
float CASW_Drone_Advanced::s_fNextTooCloseChatterTime = 0;
|
|
|
|
// drone anim events
|
|
int AE_DRONE_WALK_FOOTSTEP;
|
|
int AE_DRONE_FOOTSTEP_SOFT;
|
|
int AE_DRONE_FOOTSTEP_HEAVY;
|
|
int AE_DRONE_MELEE_HIT1;
|
|
int AE_DRONE_MELEE_HIT2;
|
|
int AE_DRONE_MELEE1_SOUND;
|
|
int AE_DRONE_MELEE2_SOUND;
|
|
int AE_DRONE_MOUTH_BLEED;
|
|
int AE_DRONE_ALERT_SOUND;
|
|
int AE_DRONE_SHADOW_ON;
|
|
|
|
int ACT_DRONE_RUN_ATTACKING;
|
|
int ACT_DRONE_WALLPOUND;
|
|
|
|
enum
|
|
{
|
|
SCHED_DRONE_BASH_DOOR = LAST_ASW_ALIEN_JUMPER_SHARED_SCHEDULE,
|
|
SCHED_DRONE_CHASE_ENEMY,
|
|
SCHED_DRONE_OVERRIDE_MOVE,
|
|
SCHED_DRONE_DOOR_WAIT,
|
|
SCHED_DRONE_BASH_CLOSE_DOOR,
|
|
SCHED_DRONE_CIRCLE_ENEMY,
|
|
SCHED_DRONE_CIRCLE_ENEMY_FAILED,
|
|
SCHED_DRONE_STANDOFF,
|
|
LAST_ASW_DRONE_SHARED_SCHEDULE,
|
|
};
|
|
|
|
CUtlVector<CASW_Drone_Advanced*> g_DroneList;
|
|
static CASW_Drone_Movement g_DroneGameMovement;
|
|
CASW_Drone_Movement *g_pDroneMovement = &g_DroneGameMovement;
|
|
|
|
CASW_Drone_Advanced::CASW_Drone_Advanced( void )
|
|
: m_DurationDoorBash( 2)
|
|
// : CASW_Alien()
|
|
{
|
|
g_DroneList.AddToTail(this);
|
|
if ( asw_new_drone.GetBool() )
|
|
{
|
|
m_pszAlienModelName = SWARM_NEW_DRONE_MODEL;
|
|
}
|
|
else
|
|
{
|
|
m_pszAlienModelName = SWARM_DRONE_MODEL;
|
|
}
|
|
m_flNextSmallFlinchTime = 0.0f;
|
|
m_nAlienCollisionGroup = ASW_COLLISION_GROUP_ALIEN;
|
|
m_iDeadBodyGroup = 2;
|
|
//m_debugOverlays |= (OVERLAY_TEXT_BIT | OVERLAY_BBOX_BIT);
|
|
}
|
|
|
|
CASW_Drone_Advanced::~CASW_Drone_Advanced()
|
|
{
|
|
g_DroneList.FindAndRemove(this);
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS( asw_drone, CASW_Drone_Advanced );
|
|
LINK_ENTITY_TO_CLASS( asw_drone_jumper, CASW_Drone_Advanced );
|
|
|
|
BEGIN_DATADESC( CASW_Drone_Advanced )
|
|
DEFINE_FIELD( m_hBlockingDoor, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_flDoorBashYaw, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_vecSavedVelocity, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_flSavedSpeed, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_fLastLostLOSTime, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_vecLastGoodPosition, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_bPerformingOverride, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bJumper, FIELD_BOOLEAN ),
|
|
DEFINE_EMBEDDED( m_DurationDoorBash ),
|
|
DEFINE_FIELD( m_bFailedOverrideMove, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fFailedOverrideTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_bUsingSmallDoorBashHull, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_iDoorPos, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_bLastSoftLOS, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fLastTouchHurtTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_bDoneAlienCloseChatter, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_vecEnemyStandoffPosition, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_bHasAttacked, FIELD_BOOLEAN ),
|
|
END_DATADESC()
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CASW_Drone_Advanced, DT_ASW_Drone_Advanced )
|
|
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
|
|
SendPropEHandle( SENDINFO( m_hAimTarget ) ),
|
|
END_SEND_TABLE()
|
|
|
|
|
|
CAI_Navigator *CASW_Drone_Advanced::CreateNavigator()
|
|
{
|
|
//return BaseClass::CreateNavigator();
|
|
return new CASW_Drone_Navigator( this );
|
|
}
|
|
|
|
|
|
void CASW_Drone_Advanced::Spawn( void )
|
|
{
|
|
BaseClass::Spawn();
|
|
|
|
//m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT | OVERLAY_TASK_TEXT_BIT | OVERLAY_TEXT_BIT;
|
|
|
|
m_FlinchActivity = ACT_INVALID;
|
|
m_nDeathStyle = RandomFloat() < asw_drone_gib_chance.GetFloat() ? kDIE_INSTAGIB : kDIE_RAGDOLLFADE;
|
|
|
|
// randomize the parts on the drone
|
|
if ( asw_new_drone.GetBool() )
|
|
{
|
|
//claws
|
|
SetBodygroup ( 1, RandomInt (0, 2 ) );
|
|
SetBodygroup ( 2, RandomInt (0, 2 ) );
|
|
SetBodygroup ( 3, RandomInt (0, 2 ) );
|
|
SetBodygroup ( 4, RandomInt (0, 2 ) );
|
|
// body
|
|
if ( RandomFloat() < .5 )
|
|
{
|
|
SetBodygroup ( 0, RandomInt (0, 1 ) );
|
|
}
|
|
// bones
|
|
if ( RandomFloat() < .25)
|
|
{
|
|
SetBodygroup ( 5, RandomInt (0, 1 ) );
|
|
}
|
|
}
|
|
if (FClassnameIs(this, "asw_drone_jumper"))
|
|
{
|
|
m_bJumper = true;
|
|
}
|
|
else
|
|
{
|
|
m_bJumper = false;
|
|
m_bDisableJump = true;
|
|
CapabilitiesRemove( bits_CAP_MOVE_JUMP );
|
|
}
|
|
|
|
SetHullType(HULL_MEDIUMBIG);
|
|
|
|
//SetCollisionBounds(Vector(-26,-26,0), Vector(26,26,72));
|
|
UTIL_SetSize(this, Vector(-17,-17,0), Vector(17,17,69));
|
|
|
|
//UseClientSideAnimation();
|
|
|
|
SetHealthByDifficultyLevel();
|
|
|
|
CapabilitiesAdd( bits_CAP_MOVE_CLIMB | bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 ); // removed: bits_CAP_MOVE_JUMP
|
|
CapabilitiesAdd( bits_CAP_MOVE_SHOOT );
|
|
|
|
m_fFailedOverrideTime = -100.0f;
|
|
m_iDoorPos = 0;
|
|
m_bUsingSmallDoorBashHull = false;
|
|
SetPoseParameter( "idle_move", 0 );
|
|
}
|
|
|
|
void CASW_Drone_Advanced::Precache( void )
|
|
{
|
|
PrecacheScriptSound( "ASW_Drone.Land" );
|
|
PrecacheScriptSound( "ASW_Drone.Pain" );
|
|
PrecacheScriptSound( "ASW_Drone.Alert" );
|
|
PrecacheScriptSound( "ASW_Drone.Death" );
|
|
PrecacheScriptSound( "ASW_Drone.Attack" );
|
|
PrecacheScriptSound( "ASW_Drone.Swipe" );
|
|
|
|
PrecacheScriptSound( "ASW_Drone.GibSplatHeavy" );
|
|
PrecacheScriptSound( "ASW_Drone.GibSplat" );
|
|
PrecacheScriptSound( "ASW_Drone.GibSplatQuiet" );
|
|
PrecacheScriptSound( "ASW_Drone.DeathFireSizzle" );
|
|
|
|
PrecacheModel( "models/aliens/drone/ragdoll_tail.mdl" );
|
|
PrecacheModel( "models/aliens/drone/ragdoll_uparm.mdl" );
|
|
PrecacheModel( "models/aliens/drone/ragdoll_uparm_r.mdl" );
|
|
PrecacheModel( "models/aliens/drone/ragdoll_leg_r.mdl" );
|
|
PrecacheModel( "models/aliens/drone/ragdoll_leg.mdl" );
|
|
PrecacheModel( "models/aliens/drone/gib_torso.mdl" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
void CASW_Drone_Advanced::AlertSound()
|
|
{
|
|
EmitSound("ASW_Drone.Alert");
|
|
}
|
|
|
|
void CASW_Drone_Advanced::PainSound( const CTakeDamageInfo &info )
|
|
{
|
|
if (gpGlobals->curtime > m_fNextPainSound)
|
|
{
|
|
EmitSound("ASW_Drone.Pain");
|
|
m_fNextPainSound = gpGlobals->curtime + 0.5f;
|
|
}
|
|
}
|
|
|
|
void CASW_Drone_Advanced::DeathSound( const CTakeDamageInfo &info )
|
|
{
|
|
// if we are playing a fancy death animation, don't play death sounds from code
|
|
// all death sounds are played from anim events inside the fancy death animation
|
|
if ( m_nDeathStyle == kDIE_FANCY )
|
|
return;
|
|
|
|
EmitSound( "ASW_Drone.Death" );
|
|
|
|
if ( m_nDeathStyle == kDIE_INSTAGIB )
|
|
EmitSound( "ASW_Drone.GibSplatHeavy" );
|
|
else if ( m_nDeathStyle == kDIE_TUMBLEGIB || m_nDeathStyle == kDIE_RAGDOLLFADE )
|
|
EmitSound( "ASW_Drone.GibSplatQuiet" );
|
|
else
|
|
{
|
|
if ( m_bOnFire )
|
|
EmitSound( "ASW_Drone.DeathFireSizzle" );
|
|
else
|
|
EmitSound( "ASW_Drone.GibSplat" );
|
|
}
|
|
}
|
|
|
|
float CASW_Drone_Advanced::GetIdealSpeed() const
|
|
{
|
|
float boost = asw_drone_speedboost.GetFloat();
|
|
if (IsPerformingOverrideMove())
|
|
{
|
|
boost = asw_drone_override_speedboost.GetFloat();
|
|
}
|
|
|
|
switch (ASWGameRules()->GetSkillLevel())
|
|
{
|
|
case 4: boost *= asw_alien_speed_scale_insane.GetFloat(); break;
|
|
case 3: boost *= asw_alien_speed_scale_hard.GetFloat(); break;
|
|
case 2: boost *= asw_alien_speed_scale_normal.GetFloat(); break;
|
|
default: boost *= asw_alien_speed_scale_easy.GetFloat(); break;
|
|
}
|
|
|
|
float flFreezeSpeedScale = 1.0f - m_flFrozen;
|
|
flFreezeSpeedScale = clamp<float>( flFreezeSpeedScale, 0.0f, 1.0f );
|
|
|
|
return boost * BaseClass::GetIdealSpeed() * m_flPlaybackRate * flFreezeSpeedScale;
|
|
}
|
|
|
|
float CASW_Drone_Advanced::GetIdealAccel( ) const
|
|
{
|
|
// our drone goes FASTER
|
|
return GetIdealSpeed() * asw_drone_acceleration.GetFloat();
|
|
}
|
|
|
|
float CASW_Drone_Advanced::MaxYawSpeed( void )
|
|
{
|
|
float fSlowScale = ShouldMoveSlow() ? 0.5f : 1.0f;
|
|
if (GetActivity() == ACT_MELEE_ATTACK1) // slow turning when actually swiping
|
|
return asw_drone_yaw_speed_attacking.GetFloat() * fSlowScale;
|
|
|
|
if ( IsCurSchedule( SCHED_ASW_ALIEN_MELEE_ATTACK1 ) || IsCurSchedule( SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1 ) ) // faster turning while trying to face our enemy
|
|
return asw_drone_yaw_speed_attackprep.GetFloat() * fSlowScale;
|
|
|
|
//fSlowScale *= ( 1.0f - m_flFrozen );
|
|
|
|
return asw_drone_yaw_speed.GetFloat() * fSlowScale;
|
|
}
|
|
|
|
// allow the NPC to modify automovement
|
|
bool CASW_Drone_Advanced::ModifyAutoMovement( Vector &vecNewPos )
|
|
{
|
|
// melee auto movement on the drones seems way too fast
|
|
float fFactor = asw_drone_auto_speed_scale.GetFloat();
|
|
if ( ShouldMoveSlow() )
|
|
{
|
|
if ( m_bElectroStunned.Get() )
|
|
{
|
|
fFactor *= asw_alien_stunned_speed.GetFloat() * 0.1f;
|
|
}
|
|
else
|
|
{
|
|
fFactor *= asw_alien_hurt_speed.GetFloat() * 0.1f;
|
|
}
|
|
}
|
|
Vector vecRelPos = vecNewPos - GetAbsOrigin();
|
|
vecRelPos *= fFactor;
|
|
vecNewPos = GetAbsOrigin() + vecRelPos;
|
|
return true;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::IsNavigationUrgent()
|
|
{
|
|
return BaseClass::IsNavigationUrgent();
|
|
}
|
|
|
|
float CASW_Drone_Advanced::GetSequenceGroundSpeed( int iSequence )
|
|
{
|
|
return BaseClass::GetSequenceGroundSpeed(iSequence);
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::OverrideMove( float flInterval )
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
// override to keep drone still
|
|
SetAbsVelocity( vec3_origin );
|
|
GetMotor()->SetMoveVel( vec3_origin );
|
|
m_vecSavedVelocity = vec3_origin;
|
|
m_flSavedSpeed = 0.0f;
|
|
return true;
|
|
}
|
|
|
|
if ( BaseClass::OverrideMove( flInterval ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( m_lifeState == LIFE_ALIVE && ( m_NPCState != NPC_STATE_SCRIPT ) )
|
|
{
|
|
if (asw_drone_override_move.GetBool() && IsPerformingOverrideMove() && !FailedOverrideMove())
|
|
{
|
|
// try to move us, set the failed flag if we don't move far enough, so the more reliable pathed movement can take over
|
|
m_bFailedOverrideMove = !MoveExecute_Alive( flInterval );
|
|
if (m_bFailedOverrideMove)
|
|
{
|
|
m_fFailedOverrideTime = gpGlobals->curtime;
|
|
TaskFail(FAIL_NO_ROUTE_BLOCKED);
|
|
}
|
|
return true;
|
|
}
|
|
else if (asw_drone_override_attack.GetBool() && IsMeleeAttacking())
|
|
{
|
|
MoveExecute_Alive( flInterval );
|
|
return true;
|
|
}
|
|
}
|
|
m_vecSavedVelocity = GetAbsVelocity();
|
|
m_flSavedSpeed = m_vecSavedVelocity.Length2D();
|
|
//SetPoseParameter( "idle_move", 0 );
|
|
m_bFailedOverrideMove = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::IsPerformingOverrideMove() const
|
|
{
|
|
return m_bPerformingOverride;
|
|
}
|
|
|
|
#define ASW_FAILED_OVERRIDE_MOVE_TIME 5.0f
|
|
bool CASW_Drone_Advanced::FailedOverrideMove() const
|
|
{
|
|
if ( ( gpGlobals->curtime - m_fFailedOverrideTime ) < ASW_FAILED_OVERRIDE_MOVE_TIME )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::IsMoving( void )
|
|
{
|
|
return BaseClass::IsMoving() || IsPerformingOverrideMove();
|
|
}
|
|
|
|
#define ASW_FAILED_MOVE_FRACTION 0.1f
|
|
bool CASW_Drone_Advanced::MoveExecute_Alive(float flInterval)
|
|
{
|
|
if (!GetEnemy())
|
|
return false; // if we don't have a target, we shouldn't be doing override move!
|
|
// remove any blended layers from pathed movement
|
|
RemoveAllGestures();
|
|
|
|
// save our falling velocity, then remove it from our calcs
|
|
float fZMovement = m_vecSavedVelocity.z;
|
|
m_vecSavedVelocity.z = 0;
|
|
|
|
// turn us to face our enemy
|
|
Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
|
|
VectorNormalize(dir);
|
|
float flTargetYaw = UTIL_VecToYaw(dir);
|
|
if (GetGroundEntity() && GetGroundEntity()->Classify() == CLASS_ASW_MARINE) // if we're standing on a marine's head, just move forward
|
|
flTargetYaw = GetAbsAngles().y;
|
|
GetMotor()->SetIdealYawAndUpdate(flTargetYaw);
|
|
|
|
// rotate our movement vector a bit too, to stop the slideyness
|
|
float fMovementYaw = VecToYaw(m_vecSavedVelocity);
|
|
float fNewYaw = ASW_ClampYaw(MaxYawSpeed() * 5, fMovementYaw, flTargetYaw, flInterval);
|
|
//Msg("current = %f target = %f new = %f\n", fMovementYaw, flTargetYaw, fNewYaw);
|
|
//NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), fMovementYaw, 64, 16, 0, 0, 255, 0, true, 0.1f );
|
|
//NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), fNewYaw, 64, 16, 128, 128, 255, 0, true, 0.1f );
|
|
//NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flTargetYaw, 64, 16, 255, 0, 0, 0, true, 0.1f );
|
|
//Vector temp = m_vecSavedVelocity;
|
|
//VectorYawRotate(temp, -UTIL_AngleDiff(fNewYaw, fMovementYaw), m_vecSavedVelocity);
|
|
|
|
// find our ideal(max) speed at full run
|
|
//SetPoseParameter( "idle_move", 0 );
|
|
float fIdealSpeed = GetIdealSpeed();
|
|
if (fIdealSpeed <= 0)
|
|
{
|
|
#ifdef INFESTED_DLL
|
|
if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
|
|
{
|
|
DevMsg("MoveExecute_Alive calling SetActivity(ACT_RUN)");
|
|
}
|
|
#endif
|
|
SetActivity(ACT_RUN);
|
|
fIdealSpeed = GetIdealSpeed();
|
|
}
|
|
|
|
// speed up/down depending on facing
|
|
float fYawDifference = abs(UTIL_AngleDiff(fNewYaw, flTargetYaw));
|
|
float accn = 1;
|
|
if (fYawDifference > 45)
|
|
{
|
|
accn = -1;
|
|
}
|
|
else if (fYawDifference > 15)
|
|
{
|
|
accn = 0;
|
|
}
|
|
float fSpeed = m_vecSavedVelocity.Length();
|
|
float fAfterSpeed = clamp(fSpeed + accn * GetIdealAccel() * flInterval, 0, fIdealSpeed);
|
|
|
|
m_vecSavedVelocity = UTIL_YawToVector(fNewYaw) * fAfterSpeed;
|
|
|
|
if (asw_debug_drone.GetBool())
|
|
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), fNewYaw, 64, 16, 128, 128, 255, 0, true, 0.1f );
|
|
|
|
Vector vecRequestedMovement = m_vecSavedVelocity;
|
|
|
|
// check for alien pushaway forces
|
|
m_bPushed = false;
|
|
if ( asw_springcol.GetBool() && !IsPerformingOverrideMove() && CanBePushedAway() )
|
|
{
|
|
SetupPushawayVector();
|
|
vecRequestedMovement += m_vecLastPush;
|
|
|
|
// cap it again
|
|
float fLength = vecRequestedMovement.Length();
|
|
//Msg("Speed = %f ", fLength2D);
|
|
if (fIdealSpeed <= 0)
|
|
{
|
|
vecRequestedMovement.Init();
|
|
}
|
|
else
|
|
{
|
|
if (fLength > fIdealSpeed)
|
|
{
|
|
float fSlowDown = fIdealSpeed / fLength;
|
|
vecRequestedMovement *= fSlowDown;
|
|
//Msg("Slowdown = %f", fSlowDown);
|
|
}
|
|
}
|
|
}
|
|
|
|
// do the movement
|
|
Vector vecOriginalPos = GetAbsOrigin();
|
|
vecRequestedMovement.z = fZMovement;
|
|
m_vecSavedVelocity.z = fZMovement;
|
|
bool bFailedToMove = false;
|
|
if (GetTask() && (GetTask()->iTask == TASK_MELEE_ATTACK1)) // do a non plane sliding movement for attacking
|
|
{
|
|
if ( WalkMove( vecRequestedMovement * flInterval, MASK_NPCSOLID ) == false )
|
|
{
|
|
//Attempt a half-step
|
|
if ( WalkMove( (vecRequestedMovement*0.5f) * flInterval, MASK_NPCSOLID) == false )
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
|
|
}
|
|
}
|
|
}
|
|
else // do sliding movement for normal chasing
|
|
{
|
|
CMoveData MoveData;
|
|
MoveData.m_vecVelocity = vecRequestedMovement;
|
|
MoveData.SetAbsOrigin( GetAbsOrigin() );
|
|
Vector oldPos = GetAbsOrigin();
|
|
g_pDroneMovement->ProcessMovement(this, &MoveData, flInterval);
|
|
UTIL_SetOrigin(this, MoveData.GetAbsOrigin());
|
|
Vector movediff = GetAbsOrigin() - oldPos;
|
|
float fRequestedMovementLength = (vecRequestedMovement.Length() * flInterval);
|
|
float fFractionMoved = 0;
|
|
if (fRequestedMovementLength > 0)
|
|
{
|
|
fFractionMoved = movediff.Length() / fRequestedMovementLength;
|
|
if (fFractionMoved <= ASW_FAILED_MOVE_FRACTION)
|
|
{
|
|
// todo: don't set this if the thing blocking us was another drone?
|
|
bFailedToMove = true;
|
|
if (asw_debug_drone.GetBool())
|
|
Msg("Drone failed to move (%f/%f) fraction: %f abs=%f,%f,%f\n",
|
|
fFractionMoved * fRequestedMovementLength, fRequestedMovementLength, fFractionMoved,
|
|
vecRequestedMovement.x, vecRequestedMovement.y, vecRequestedMovement.z);
|
|
}
|
|
}
|
|
}
|
|
|
|
// find out how much we actually moved
|
|
Vector vecMoveDifference = GetAbsOrigin() - vecOriginalPos;
|
|
vecRequestedMovement = vecMoveDifference / flInterval;
|
|
|
|
// set our saved speed to the actual direction moved, and speed too if it's lower
|
|
float fOriginalRequestSpeed = m_vecSavedVelocity.Length();
|
|
m_vecSavedVelocity = vecRequestedMovement;
|
|
float fNewSpeed = m_vecSavedVelocity.Length();
|
|
if (fNewSpeed > fOriginalRequestSpeed)
|
|
m_vecSavedVelocity *= (fOriginalRequestSpeed/fNewSpeed);
|
|
m_vecSavedVelocity.z = vecMoveDifference.z;
|
|
|
|
// make sure our motor knows we're travelling at this speed, so override movement smoothly disengages
|
|
SetAbsVelocity(m_vecSavedVelocity);
|
|
GetMotor()->SetMoveVel(m_vecSavedVelocity);
|
|
|
|
if ( CheckStuck() && m_vecLastGoodPosition != vec3_origin )
|
|
{
|
|
if ( asw_debug_drone.GetBool() )
|
|
{
|
|
NDebugOverlay::Line( GetAbsOrigin(), m_vecLastGoodPosition, 255, 0, 0, true, 0.1f );
|
|
}
|
|
SetAbsOrigin(m_vecLastGoodPosition);
|
|
}
|
|
else
|
|
{
|
|
if ( asw_debug_drone.GetBool() )
|
|
{
|
|
NDebugOverlay::Line( GetAbsOrigin(), m_vecLastGoodPosition, 0, 255, 0, true, 0.1f );
|
|
}
|
|
}
|
|
return !bFailedToMove;
|
|
}
|
|
|
|
|
|
void CASW_Drone_Advanced::NPCThink()
|
|
{
|
|
if (!CheckStuck())
|
|
{
|
|
m_vecLastGoodPosition = GetAbsOrigin();
|
|
}
|
|
|
|
m_bPerformingOverride = (GetTask() &&
|
|
( (GetTask()->iTask == TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE) ||
|
|
(GetTask()->iTask == TASK_MELEE_ATTACK1) ) );
|
|
|
|
BaseClass::NPCThink();
|
|
|
|
m_bPerformingOverride = (GetTask() &&
|
|
( (GetTask()->iTask == TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE) ||
|
|
(GetTask()->iTask == TASK_MELEE_ATTACK1) ) );
|
|
|
|
if (asw_drone_show_override.GetBool())
|
|
{
|
|
float flYaw = GetAbsAngles().y;
|
|
if (IsPerformingOverrideMove())
|
|
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flYaw, 64, 16, 255, 255, 0, 0, true, 0.1f );
|
|
else
|
|
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flYaw, 64, 16, 0, 0, 255, 0, true, 0.1f );
|
|
|
|
}
|
|
else if (asw_drone_show_facing.GetBool())
|
|
{
|
|
float flYaw = GetAbsAngles().y;
|
|
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flYaw, 64, 16, 128, 128, 255, 0, true, 0.1f );
|
|
}
|
|
|
|
float fSpeed = GetAbsVelocity().Length();
|
|
static float fPathingSpeed = 0;
|
|
static float fOverrideSpeed = 0;
|
|
if (IsPerformingOverrideMove() && GetActivity() == ACT_RUN && GetGroundEntity() && GetAbsVelocity().z == 0)
|
|
{
|
|
if (fSpeed > fOverrideSpeed)
|
|
{
|
|
fOverrideSpeed = fSpeed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fSpeed > fPathingSpeed && fSpeed<400)
|
|
{
|
|
fPathingSpeed = fSpeed;
|
|
}
|
|
}
|
|
|
|
m_hAimTarget = GetEnemy();
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::IsMeleeAttacking()
|
|
{
|
|
return BaseClass::IsMeleeAttacking();
|
|
}
|
|
|
|
//ConVar asw_drone_waypoint_push_dist( "asw_drone_waypoint_push_dist", "40.0f", FCVAR_CHEAT, "Distance from next waypoint under which drones will not be pushed away by soft collision." );
|
|
|
|
bool CASW_Drone_Advanced::CanBePushedAway()
|
|
{
|
|
// if doing stationary, well telegraphed melee attacks then don't get pushed away during them
|
|
//bool bMeleeAttack = ( IsCurSchedule( SCHED_ASW_ALIEN_MELEE_ATTACK1, false ) || IsCurSchedule( SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1, false ) );
|
|
//|| IsCurSchedule( SCHED_DRONE_POUNCE ) );
|
|
bool bMeleeAttack = IsMeleeAttacking();
|
|
|
|
if ( GetTask() && (GetTask()->iTask == TASK_DRONE_ATTACK_DOOR) )
|
|
return false;
|
|
|
|
if ( !asw_drone_override_attack.GetBool() && bMeleeAttack )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( GetNavType() != NAV_GROUND && GetNavType() != NAV_CRAWL )
|
|
return false;
|
|
|
|
if ( GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DOWN || GetActivity() == ACT_CLIMB_DISMOUNT )
|
|
return false;
|
|
|
|
CAI_Path *pPath = GetNavigator()->GetPath();
|
|
if ( pPath )
|
|
{
|
|
AI_Waypoint_t *pWaypoint = pPath->GetCurWaypoint();
|
|
if ( pWaypoint )
|
|
{
|
|
if ( pWaypoint->NavType() == NAV_JUMP || pWaypoint->NavType() == NAV_CLIMB )
|
|
return false;
|
|
|
|
// float flDistSqr = pWaypoint->GetPos().DistToSqr( GetAbsOrigin() );
|
|
// if ( flDistSqr < asw_drone_waypoint_push_dist.GetFloat() )
|
|
// {
|
|
// return false;
|
|
// }
|
|
}
|
|
}
|
|
|
|
return BaseClass::CanBePushedAway();
|
|
}
|
|
|
|
// gib the drone if he's not already gibbed and below a certain amount of health (i.e. marines shot a burning body)
|
|
int CASW_Drone_Advanced::OnTakeDamage_Dead( const CTakeDamageInfo &info )
|
|
{
|
|
int result = BaseClass::OnTakeDamage_Dead(info);
|
|
if (m_iHealth <= -20 && info.GetDamageType() != DMG_BURN)
|
|
{
|
|
Event_Gibbed( info );
|
|
result = 0;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int CASW_Drone_Advanced::MeleeAttack1Conditions( float flDot, float flDist )
|
|
{
|
|
#if 1 //NOTENOTE: Use predicted position melee attacks
|
|
|
|
float flPrDist, flPrDot;
|
|
Vector vecPrPos;
|
|
Vector2D vec2DPrDir;
|
|
|
|
//Get our likely position in one half second
|
|
//UTIL_PredictedPosition( GetEnemy(), 0.5f, &vecPrPos );
|
|
if (!GetEnemy())
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
vecPrPos = GetEnemy()->GetAbsOrigin();
|
|
|
|
//Get the predicted distance and direction
|
|
flPrDist = ( vecPrPos - GetAbsOrigin() ).Length();
|
|
vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D();
|
|
|
|
Vector vecBodyDir = BodyDirection2D();
|
|
|
|
Vector2D vec2DBodyDir = vecBodyDir.AsVector2D();
|
|
|
|
flPrDot = DotProduct2D ( vec2DPrDir, vec2DBodyDir );
|
|
|
|
if (!asw_drone_attacks.GetBool())
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
// have to get close to attack if stunned
|
|
float fAttackRange = m_bElectroStunned ? ASW_DRONE_MELEE1_START_ATTACK_RANGE * 0.7f : ASW_DRONE_MELEE1_START_ATTACK_RANGE;
|
|
if ( flPrDist > fAttackRange )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
//if ( flPrDot < 0.5f )
|
|
if ( flPrDot < 0 ) // try generous way
|
|
return COND_NOT_FACING_ATTACK;
|
|
|
|
#else
|
|
|
|
if ( flDot < 0.5f )
|
|
return COND_NOT_FACING_ATTACK;
|
|
|
|
float flAdjustedDist = ASW_DRONE_MELEE1_START_ATTACK_RANGE;
|
|
|
|
if ( GetEnemy() )
|
|
{
|
|
CBasePlayer *pPlayer = ToBasePlayer( GetEnemy() );
|
|
|
|
if ( pPlayer && pPlayer->IsInAVehicle() )
|
|
{
|
|
flAdjustedDist *= 2.0f;
|
|
}
|
|
}
|
|
|
|
if ( flDist > flAdjustedDist )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
trace_t tr;
|
|
AI_TraceHull( WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), -Vector(8,8,8), Vector(8,8,8), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( tr.fraction < 1.0f )
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
return COND_CAN_MELEE_ATTACK1;
|
|
}
|
|
|
|
// disable pouncing for the moment
|
|
int CASW_Drone_Advanced::MeleeAttack2Conditions( float flDot, float flDist )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::HandleAnimEvent( animevent_t *pEvent )
|
|
{
|
|
int nEvent = pEvent->Event();
|
|
|
|
if ( nEvent == AE_DRONE_WALK_FOOTSTEP )
|
|
{
|
|
//EmitSound( "NPC_Antlion.Footstep", pEvent->eventtime );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_FOOTSTEP_SOFT )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_FOOTSTEP_HEAVY )
|
|
{
|
|
//EmitSound( "NPC_Antlion.FootstepHeavy", pEvent->eventtime );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_MELEE_HIT1 )
|
|
{
|
|
float fDamage = MAX(3.0f, ASWGameRules()->ModifyAlienDamageBySkillLevel(sk_asw_drone_damage.GetFloat()));
|
|
MeleeAttack( ASW_DRONE_MELEE1_RANGE, fDamage, QAngle( 20.0f, 0.0f, -12.0f ), Vector( -250.0f, 1.0f, 1.0f ) );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_MELEE_HIT2 )
|
|
{
|
|
float fDamage = MAX(3.0f, ASWGameRules()->ModifyAlienDamageBySkillLevel(sk_asw_drone_damage.GetFloat()));
|
|
MeleeAttack( ASW_DRONE_MELEE1_RANGE, fDamage, QAngle( 20.0f, 0.0f, 0.0f ), Vector( -350.0f, 1.0f, 1.0f ) );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_MELEE1_SOUND )
|
|
{
|
|
EmitSound( "ASW_Drone.Attack" );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_MELEE2_SOUND )
|
|
{
|
|
EmitSound( "ASW_Drone.Attack" );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_MOUTH_BLEED )
|
|
{
|
|
Vector vecOrigin, vecDir;
|
|
if (GetAttachment( LookupAttachment("mouth") , vecOrigin, &vecDir ))
|
|
UTIL_ASW_BloodDrips( vecOrigin+vecDir*3, vecDir, BLOOD_COLOR_RED, 6 );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_ALERT_SOUND )
|
|
{
|
|
EmitSound( "ASW_Drone.Alert" );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_DRONE_SHADOW_ON)
|
|
{
|
|
RemoveEffects( EF_NOSHADOW );
|
|
return;
|
|
}
|
|
|
|
if ( nEvent == AE_ASW_FOOTSTEP || nEvent == AE_MARINE_FOOTSTEP )
|
|
{
|
|
// footsteps are played clientside
|
|
return;
|
|
}
|
|
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
|
|
void CASW_Drone_Advanced::StartTouch( CBaseEntity *pOther )
|
|
{
|
|
BaseClass::StartTouch( pOther );
|
|
|
|
CASW_Marine *pMarine = CASW_Marine::AsMarine( pOther );
|
|
if (pMarine)
|
|
{
|
|
int iTouchDamage = asw_drone_touch_damage.GetInt();
|
|
if (GetActivity() == ACT_CLIMB_UP || GetActivity() == ACT_CLIMB_DISMOUNT)
|
|
{
|
|
// if we touched the marine while climbing up, deliver a big dose of melee damage to him
|
|
iTouchDamage = 20;
|
|
EmitSound( "ASW_Drone.Attack" );
|
|
|
|
// shove him a bit too
|
|
Vector vecDir = pMarine->WorldSpaceCenter() - WorldSpaceCenter();
|
|
//vecDir.z = 0.0; // planar
|
|
VectorNormalize( vecDir );
|
|
vecDir *= 200.0f;
|
|
pMarine->ApplyAbsVelocityImpulse(vecDir);
|
|
|
|
// we'll fall down already now?
|
|
}
|
|
// don't hurt him if he was hurt recently
|
|
if (m_fLastTouchHurtTime + 0.6f > gpGlobals->curtime || iTouchDamage <= 0)
|
|
{
|
|
//Msg("already hurt recently: %f (cur=%f\n)", m_fLastTouchHurtTime, gpGlobals->curtime);
|
|
return;
|
|
}
|
|
// hurt the marine
|
|
Vector vecForceDir = ( pMarine->GetAbsOrigin() - GetAbsOrigin() );
|
|
float fTouchDamage = ASWGameRules()->ModifyAlienDamageBySkillLevel(iTouchDamage);
|
|
CTakeDamageInfo info( this, this, fTouchDamage, DMG_SLASH );
|
|
CalculateMeleeDamageForce( &info, vecForceDir, pMarine->GetAbsOrigin() );
|
|
pMarine->TakeDamage( info );
|
|
//Msg("HURTING MARINE: %f (cur=%f\n)", m_fLastTouchHurtTime, gpGlobals->curtime);
|
|
m_fLastTouchHurtTime = gpGlobals->curtime;
|
|
}
|
|
}
|
|
|
|
void CASW_Drone_Advanced::MeleeAttack( float distance, float damage, QAngle &viewPunch, Vector &shove )
|
|
{
|
|
Vector vecForceDir;
|
|
|
|
m_bHasAttacked = true;
|
|
|
|
// Always hurt bullseyes for now
|
|
if ( ( GetEnemy() != NULL ) && ( GetEnemy()->Classify() == CLASS_BULLSEYE ) )
|
|
{
|
|
vecForceDir = (GetEnemy()->GetAbsOrigin() - GetAbsOrigin());
|
|
CTakeDamageInfo info( this, this, damage, DMG_SLASH );
|
|
CalculateMeleeDamageForce( &info, vecForceDir, GetEnemy()->GetAbsOrigin() );
|
|
GetEnemy()->TakeDamage( info );
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pHurt = CheckTraceHullAttack( distance, -Vector(16,16,32), Vector(16,16,32), damage, DMG_SLASH, asw_drone_melee_force.GetFloat() );
|
|
|
|
if ( pHurt )
|
|
{
|
|
vecForceDir = ( pHurt->WorldSpaceCenter() - WorldSpaceCenter() );
|
|
|
|
// Play a random attack hit sound
|
|
EmitSound( "ASW_Drone.Attack" );
|
|
}
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::CorpseGib( const CTakeDamageInfo &info )
|
|
{
|
|
CEffectData data;
|
|
|
|
m_LagCompensation.UndoLaggedPosition();
|
|
|
|
data.m_vOrigin = WorldSpaceCenter();
|
|
data.m_vNormal = data.m_vOrigin - info.GetDamagePosition();
|
|
VectorNormalize( data.m_vNormal );
|
|
|
|
data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
|
|
data.m_flScale = clamp( data.m_flScale, 1, 3 );
|
|
data.m_nColor = m_nSkin;
|
|
data.m_fFlags = IsOnFire() ? ASW_GIBFLAG_ON_FIRE : 0;
|
|
|
|
//DispatchEffect( "DroneGib", data );
|
|
|
|
//CSoundEnt::InsertSound( SOUND_PHYSICS_DANGER, GetAbsOrigin(), 256, 0.5f, this );
|
|
//EmitSound( "ASW_Drone.Death" );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::BuildScheduleTestBits( void )
|
|
{
|
|
//Don't allow any modifications when scripted
|
|
if ( m_NPCState == NPC_STATE_SCRIPT )
|
|
return;
|
|
|
|
//Make sure we interrupt a run schedule if we can jump
|
|
if ( IsCurSchedule(SCHED_CHASE_ENEMY) )
|
|
{
|
|
SetCustomInterruptCondition( COND_ENEMY_UNREACHABLE );
|
|
}
|
|
|
|
if( GetFlags() & FL_ONGROUND )
|
|
{
|
|
if ( GetEnemy() == NULL )
|
|
{
|
|
SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
|
|
}
|
|
}
|
|
|
|
BaseClass::BuildScheduleTestBits();
|
|
}
|
|
|
|
void CASW_Drone_Advanced::GatherEnemyConditions( CBaseEntity *pEnemy )
|
|
{
|
|
// Do the base class
|
|
BaseClass::GatherEnemyConditions( pEnemy );
|
|
|
|
// If we're not already too far away, check again
|
|
//TODO: Check to make sure we don't already have a condition set that removes the need for this
|
|
if ( HasCondition( COND_ENEMY_UNREACHABLE ) == false )
|
|
{
|
|
Vector predPosition;
|
|
UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPosition );
|
|
|
|
Vector predDir = ( predPosition - GetAbsOrigin() );
|
|
float predLength = VectorNormalize( predDir );
|
|
|
|
// See if we'll be outside our effective target range
|
|
if ( predLength > 2000 ) // m_flEludeDistance
|
|
{
|
|
Vector predVelDir = ( predPosition - GetEnemy()->GetAbsOrigin() );
|
|
float predSpeed = VectorNormalize( predVelDir );
|
|
|
|
// See if the enemy is moving mostly away from us
|
|
if ( ( predSpeed > 512.0f ) && ( DotProduct( predVelDir, predDir ) > 0.0f ) )
|
|
{
|
|
// Mark the enemy as eluded
|
|
ClearEnemyMemory();
|
|
Msg("Drone lost enemy in CASW_Drone_Advanced::GatherEnemyConditions\n");
|
|
SetEnemy( NULL );
|
|
SetIdealState( NPC_STATE_ALERT );
|
|
SetCondition( COND_ENEMY_UNREACHABLE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::OverrideMoveFacing( const AILocalMoveGoal_t &move, float flInterval )
|
|
{
|
|
// FIXME: this will break scripted sequences that walk when they have an enemy
|
|
|
|
if ( GetEnemy() && GetNavigator()->GetMovementActivity() == ACT_RUN )
|
|
{
|
|
Vector vecEnemyLKP = GetEnemyLKP();
|
|
|
|
// Only start facing when we're close enough
|
|
if ( UTIL_DistApprox( vecEnemyLKP, GetAbsOrigin() ) < 192 )
|
|
{
|
|
AddFacingTarget( GetEnemy(), vecEnemyLKP, 1.0, 0.2 );
|
|
}
|
|
}
|
|
else // otherwise face wher we're going
|
|
{
|
|
AddFacingTarget(move.target, 1.0, 0.2);
|
|
}
|
|
|
|
return BaseClass::OverrideMoveFacing( move, flInterval );
|
|
}
|
|
|
|
Activity CASW_Drone_Advanced::NPC_TranslateActivity( Activity eNewActivity )
|
|
{
|
|
//if (eNewActivity == ACT_RUN)
|
|
//{
|
|
//eNewActivity = ( Activity ) ACT_DRONE_RUN_ATTACKING;
|
|
//return eNewActivity;
|
|
//}
|
|
//if ( eNewActivity == ACT_CLIMB_DOWN )
|
|
//return ACT_CLIMB_UP;
|
|
|
|
return BaseClass::NPC_TranslateActivity(eNewActivity);
|
|
}
|
|
|
|
void CASW_Drone_Advanced::RunTaskOverlay()
|
|
{/*
|
|
if ( IsCurTaskContinuousMove() )
|
|
{
|
|
Activity curActivity = GetNavigator()->GetMovementActivity();
|
|
Activity newActivity = curActivity;
|
|
|
|
switch( curActivity )
|
|
{
|
|
case ACT_WALK:
|
|
newActivity = ACT_WALK_AIM;
|
|
break;
|
|
case ACT_RUN:
|
|
newActivity = ACT_RUN_AIM;
|
|
break;
|
|
}
|
|
|
|
if ( curActivity != newActivity )
|
|
{
|
|
GetNavigator()->SetMovementActivity( newActivity );
|
|
}
|
|
}*/
|
|
BaseClass::RunTaskOverlay();
|
|
}
|
|
/*
|
|
void CASW_Drone_Advanced::ReachedEndOfSequence()
|
|
{
|
|
BaseClass::ReachedEndOfSequence();
|
|
|
|
if (GetTask()->iTask == TASK_MELEE_ATTACK1 && GetActivity() == ACT_DRONE_RUN_ATTACKING)
|
|
{
|
|
Msg("Making task complete early!\n");
|
|
TaskComplete();
|
|
SetActivity( ACT_RUN );
|
|
}
|
|
}*/
|
|
|
|
void CASW_Drone_Advanced::RunTask( const Task_t *pTask )
|
|
{
|
|
// make sure the drone is small when bashing a door, or trying to get to a door
|
|
SetSmallDoorBashHull(pTask->iTask == TASK_DRONE_ATTACK_DOOR || pTask->iTask == TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT);
|
|
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT:
|
|
{
|
|
// see if we're near enough to the door
|
|
if (LinearDistanceToDoor() <= asw_drone_door_distance.GetFloat())
|
|
TaskComplete();
|
|
|
|
bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
|
|
if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
//if (fTimeExpired)
|
|
//Msg("Finished TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT with fTimeExpired\n");
|
|
//else
|
|
//Msg("Finished TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT with run no goal in runtask\n");
|
|
//TaskComplete();
|
|
//GetNavigator()->StopMoving(); // Stop moving
|
|
//SetSchedule(SCHED_DRONE_DOOR_WAIT);
|
|
TaskFail("Can't get to door");
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TASK_DRONE_WAIT_FACE_ENEMY:
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
|
|
Vector vecEnemyLKP = vec3_origin;
|
|
bool bUpdateYaw = false;
|
|
if ( GetEnemy() )
|
|
{
|
|
vecEnemyLKP = GetEnemyLKP();
|
|
bUpdateYaw = !FInAimCone( vecEnemyLKP );
|
|
}
|
|
|
|
if ( bUpdateYaw )
|
|
{
|
|
GetMotor()->SetIdealYawToTargetAndUpdate( vecEnemyLKP , AI_KEEP_YAW_SPEED );
|
|
}
|
|
|
|
// stop waiting if our enemy moved
|
|
if ( !GetEnemy() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else if ( ( vecEnemyLKP - m_vecEnemyStandoffPosition ).Length() > 120.0f )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else if ( IsWaitFinished() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE:
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
|
|
bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
|
|
if (fTimeExpired || !GetEnemy() || m_lifeState == LIFE_DEAD || GetHealth() <= 0)
|
|
{
|
|
TaskComplete();
|
|
//GetNavigator()->StopMoving(); // Stop moving
|
|
}
|
|
break;
|
|
}
|
|
case TASK_DRONE_DOOR_WAIT:
|
|
{
|
|
CASW_Door *pDoor = m_hBlockingDoor.Get();
|
|
if ( !pDoor || pDoor->m_bDoorFallen
|
|
|| ( GetEnemy() && !IsBehindDoor( GetEnemy() ) && GetAlienOrders() != AOT_MoveToIgnoringMarines ) ) // or enemy is no longer behind the door
|
|
{
|
|
//if (!pDoor && entindex()==45)
|
|
//Msg("Drone completing door wait since no door\n");
|
|
//else if (entindex() == 45)
|
|
//Msg("Drone completing door wait since door health is 0\n");
|
|
|
|
m_iDoorPos = 0;
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TASK_DRONE_ATTACK_DOOR:
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
//if ( IsActivityFinished() )
|
|
//{
|
|
// periodically check if the door is still ok to bash
|
|
if ( m_DurationDoorBash.Expired() )
|
|
{
|
|
if (!ValidBlockingDoor())
|
|
{
|
|
TaskComplete();
|
|
}
|
|
m_DurationDoorBash.Reset();
|
|
}
|
|
//else
|
|
//ResetIdealActivity( SelectDoorBash() );
|
|
//}
|
|
break;
|
|
}
|
|
case TASK_MELEE_ATTACK1:
|
|
{
|
|
BaseClass::RunTask(pTask);
|
|
|
|
if (TaskIsComplete())
|
|
{
|
|
//m_flSpeed = GetIdealSpeed();
|
|
#ifdef INFESTED_DLL
|
|
if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
|
|
{
|
|
DevMsg("RunTask TASK_MELEE_ATTACK1 calling SetActivity(ACT_RUN)");
|
|
}
|
|
#endif
|
|
SetActivity( ACT_RUN );
|
|
GetNavigator()->ClearGoal();
|
|
m_flSavedSpeed = GetIdealSpeed();
|
|
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
BaseClass::RunTask(pTask);
|
|
}
|
|
}
|
|
|
|
//if (!HasCondition(COND_NPC_FREEZE) && !IsCurSchedule(SCHED_NPC_FREEZE))
|
|
//{
|
|
if (GetActivity() == ACT_RUN || GetActivity() == ACT_DRONE_RUN_ATTACKING
|
|
|| GetActivity() == ACT_MELEE_ATTACK1)
|
|
m_flPlaybackRate = 1.25f;
|
|
else
|
|
m_flPlaybackRate = 1.0f;
|
|
//}
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::ShouldGib( const CTakeDamageInfo &info )
|
|
{
|
|
// don't gib if we burnt to death
|
|
//if (info.GetDamageType() & DMG_BURN)
|
|
//return false;
|
|
|
|
//if (info.GetDamageType() & DMG_ALWAYSGIB)
|
|
//return true;
|
|
|
|
//return m_bGibber;
|
|
|
|
// force ragdoll so (gibbers will gib clientside from the ragdoll)
|
|
return false;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
CTakeDamageInfo newInfo(info);
|
|
|
|
// scale up the force if we're shot by a marine, to make our ragdolling more interesting
|
|
if (newInfo.GetAttacker() && newInfo.GetAttacker()->Classify() == CLASS_ASW_MARINE)
|
|
{
|
|
// scale based on the weapon used
|
|
if (info.GetAmmoType() == GetAmmoDef()->Index("ASW_R")
|
|
|| info.GetAmmoType() == GetAmmoDef()->Index("ASW_AG")
|
|
|| info.GetAmmoType() == GetAmmoDef()->Index("ASW_P"))
|
|
newInfo.ScaleDamageForce(22.0f);
|
|
else if (info.GetAmmoType() == GetAmmoDef()->Index("ASW_PDW")
|
|
|| info.GetAmmoType() == GetAmmoDef()->Index("ASW_SG"))
|
|
newInfo.ScaleDamageForce(30.0f);
|
|
else if (info.GetAmmoType() == GetAmmoDef()->Index("ASW_ASG"))
|
|
newInfo.ScaleDamageForce(35.0f);
|
|
|
|
// tilt the angle up a bit?
|
|
Vector vecForceDir = newInfo.GetDamageForce();
|
|
float force = vecForceDir.NormalizeInPlace();
|
|
QAngle angForce;
|
|
VectorAngles(vecForceDir, angForce);
|
|
angForce[PITCH] += asw_drone_death_force_pitch.GetFloat();
|
|
AngleVectors(angForce, &vecForceDir);
|
|
vecForceDir *= force;
|
|
newInfo.SetDamageForce(vecForceDir);
|
|
}
|
|
|
|
trace_t tr;
|
|
UTIL_TraceLine( GetAbsOrigin() + Vector( 0, 0, 16 ), GetAbsOrigin() - Vector( 0, 0, 64 ), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
|
|
UTIL_DecalTrace( &tr, "GreenBloodBig" );
|
|
|
|
BaseClass::Event_Killed( newInfo );
|
|
}
|
|
|
|
// === Door Bashing stuff ========
|
|
|
|
// does a rough check to see if we have a clear direct path to the target entity
|
|
// (this is used to determine if we should use override movement, or pathed)
|
|
bool CASW_Drone_Advanced::HasOverridePathTo(CBaseEntity* pEnt)
|
|
{
|
|
trace_t tr;
|
|
//UTIL_TraceLine( EyePosition(), GetEnemy()->WorldSpaceCenter(), MASK_SOLID, this, GetCollisionGroup(), &tr );
|
|
CDroneTraceFilterLOS traceFilter( this, GetCollisionGroup() );
|
|
UTIL_TraceLine( EyePosition(), pEnt->WorldSpaceCenter(), MASK_OPAQUE_AND_NPCS, &traceFilter, &tr );
|
|
if (tr.m_pEnt != pEnt && tr.fraction < 1.0f) // centre of the alien can't see the centre of the enemy
|
|
return false;
|
|
|
|
// check our corners
|
|
Vector vecOffset(0,0,0);
|
|
vecOffset.x = GetHullMins().x;
|
|
vecOffset.y = GetHullMins().y;
|
|
UTIL_TraceLine( WorldSpaceCenter() + vecOffset, pEnt->WorldSpaceCenter() + vecOffset, MASK_OPAQUE_AND_NPCS, &traceFilter, &tr );
|
|
if (tr.m_pEnt != pEnt && tr.fraction < 1.0f) // centre of the alien can't see this corner
|
|
return false;
|
|
|
|
vecOffset.x = GetHullMaxs().x;
|
|
UTIL_TraceLine( WorldSpaceCenter() + vecOffset, pEnt->WorldSpaceCenter() + vecOffset, MASK_OPAQUE_AND_NPCS, &traceFilter, &tr );
|
|
if (tr.m_pEnt != pEnt && tr.fraction < 1.0f) // centre of the alien can't see this corner
|
|
return false;
|
|
|
|
vecOffset.x = GetHullMins().x;
|
|
vecOffset.y = GetHullMaxs().y;
|
|
UTIL_TraceLine( WorldSpaceCenter() + vecOffset, pEnt->WorldSpaceCenter() + vecOffset, MASK_OPAQUE_AND_NPCS, &traceFilter, &tr );
|
|
if (tr.m_pEnt != pEnt && tr.fraction < 1.0f) // centre of the alien can't see this corner
|
|
return false;
|
|
|
|
vecOffset.x = GetHullMaxs().x;
|
|
UTIL_TraceLine( WorldSpaceCenter() + vecOffset, pEnt->WorldSpaceCenter() + vecOffset, MASK_OPAQUE_AND_NPCS, &traceFilter, &tr );
|
|
if (tr.m_pEnt != pEnt && tr.fraction < 1.0f) // centre of the alien can't see this corner
|
|
return false;
|
|
|
|
//AIMoveTrace_t moveTrace;
|
|
//unsigned testFlags = AITGM_IGNORE_FLOOR;
|
|
//GetMoveProbe()->TestGroundMove( GetLocalOrigin(), GetEnemy()->WorldSpaceCenter(), MASK_OPAQUE_AND_NPCS, testFlags, &moveTrace );
|
|
//if ( !IsMoveBlocked(moveTrace.fStatus) ||
|
|
//( moveTrace.flTotalDist - moveTrace.flDistObstructed < GetEnemy()->BoundingRadius() * 1.5 )
|
|
//)
|
|
return true;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::CheckStuck()
|
|
{
|
|
trace_t tr;
|
|
Ray_t ray;
|
|
ray.Init( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
|
|
UTIL_TraceRay( ray, GetAITraceMask(), this, GetCollisionGroup(), &tr );
|
|
// if ( (traceresult.contents & MASK_NPCSOLID) && traceresult.m_pEnt )
|
|
// {
|
|
// return true;
|
|
// }
|
|
if ( tr.startsolid || tr.fraction < 1.0f || tr.allsolid )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::GatherConditions( void )
|
|
{
|
|
BaseClass::GatherConditions();
|
|
|
|
bool bHadBlocked = HasCondition(COND_DRONE_BLOCKED_BY_DOOR);
|
|
|
|
AI_Waypoint_t *pWaypoint = NULL;
|
|
if ( GetNavigator()->GetPath() )
|
|
{
|
|
pWaypoint = GetNavigator()->GetPath()->GetCurWaypoint();
|
|
}
|
|
bool bOnLadder = ( ( pWaypoint != NULL ) && ( pWaypoint->NavType() == NAV_CLIMB ) );
|
|
|
|
// ignore enemy conditions when climbing on a ladder so that we don't fall off
|
|
if ( bOnLadder && IsCurSchedule( SCHED_DRONE_CHASE_ENEMY, false ) )
|
|
{
|
|
static int chaseConditionsToClear[] =
|
|
{
|
|
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_DRONE_GAINED_LOS,
|
|
COND_HEAVY_DAMAGE,
|
|
COND_ALIEN_SHOVER_PHYSICS_TARGET,
|
|
};
|
|
|
|
ClearConditions( chaseConditionsToClear, ARRAYSIZE( chaseConditionsToClear ) );
|
|
}
|
|
|
|
// check if we have a clear view of our enemy, to do simple chasing movement instead of pathed movement
|
|
bool bLOS = false;
|
|
if (GetEnemy())
|
|
{
|
|
// Don't report having line of sight while climbing. This prevents the
|
|
// alien from falling off ladders due to being interrupted by COND_DRONE_GAINED_LOS.
|
|
if ( !bOnLadder )
|
|
{
|
|
Vector diff = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
|
|
float dist = diff.Length2D();
|
|
if (dist < 1000)
|
|
{
|
|
//Vector eyediff = GetEnemy()->WorldspaceCenter() - EyePosition();
|
|
bLOS = HasOverridePathTo(GetEnemy());
|
|
}
|
|
}
|
|
}
|
|
ClearCondition(COND_DRONE_GAINED_LOS);
|
|
if (!bLOS && m_bLastSoftLOS)
|
|
{
|
|
ClearCondition(COND_DRONE_LOS);
|
|
SetCondition(COND_DRONE_LOST_LOS);
|
|
m_fLastLostLOSTime = gpGlobals->curtime;
|
|
}
|
|
else if (bLOS)
|
|
{
|
|
if (!m_bLastSoftLOS)
|
|
{
|
|
SetCondition(COND_DRONE_GAINED_LOS);
|
|
//Msg("%f COND_DRONE_GAINED_LOS\n", gpGlobals->curtime);
|
|
}
|
|
SetCondition(COND_DRONE_LOS);
|
|
ClearCondition(COND_DRONE_LOST_LOS);
|
|
}
|
|
else
|
|
{
|
|
ClearCondition( COND_DRONE_LOS );
|
|
ClearCondition( COND_DRONE_LOST_LOS );
|
|
}
|
|
m_bLastSoftLOS = HasCondition( COND_DRONE_LOS );
|
|
|
|
static int conditionsToClear[] =
|
|
{
|
|
COND_DRONE_BLOCKED_BY_DOOR,
|
|
COND_DRONE_DOOR_OPENED,
|
|
};
|
|
|
|
ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) );
|
|
|
|
if ( m_hBlockingDoor == NULL ||
|
|
( m_hBlockingDoor->IsDoorOpen() ||
|
|
m_hBlockingDoor->IsDoorOpening() || m_hBlockingDoor->m_bDoorFallen ) )
|
|
{
|
|
ClearCondition( COND_DRONE_BLOCKED_BY_DOOR );
|
|
if ( m_hBlockingDoor != NULL )
|
|
{
|
|
SetCondition( COND_DRONE_DOOR_OPENED );
|
|
m_hBlockingDoor = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!bHadBlocked)
|
|
{
|
|
if (asw_debug_drone.GetBool())
|
|
Msg("Setting door blocked condition\n");
|
|
}
|
|
SetCondition( COND_DRONE_BLOCKED_BY_DOOR );
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
|
|
int CASW_Drone_Advanced::SelectSchedule( void )
|
|
{
|
|
if ( HasCondition(COND_NEW_ENEMY) )
|
|
{
|
|
// if we have a new enemy, upgrade our sight range, so we don't lose him so easily while chasing
|
|
SetDistLook( 1768.0f );
|
|
SetDistSwarmSense(1200.0f);
|
|
}
|
|
|
|
// check for jumping off marine heads
|
|
if (GetGroundEntity() && GetGroundEntity()->Classify() == CLASS_ASW_MARINE && DoJumpOffHead())
|
|
return SCHED_ASW_ALIEN_JUMP;
|
|
|
|
//Msg("Drone Selecting schedule\n");
|
|
if ( HasCondition( COND_DRONE_BLOCKED_BY_DOOR ) && m_hBlockingDoor != NULL )
|
|
{
|
|
//Msg("%d: possible selecting bash schedule\n", entindex());
|
|
ClearCondition( COND_DRONE_BLOCKED_BY_DOOR );
|
|
if ( ValidBlockingDoor() )
|
|
{
|
|
//Msg(" yes i did\n");
|
|
return SCHED_DRONE_BASH_DOOR;
|
|
}
|
|
//Msg("Clearing door as it's not a valid blocking door\n");
|
|
m_hBlockingDoor = NULL;
|
|
}
|
|
|
|
int nSched = SelectFlinchSchedule_ASW();
|
|
if ( nSched != SCHED_NONE )
|
|
return nSched;
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
int CASW_Drone_Advanced::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode )
|
|
{
|
|
if ( HasCondition( COND_DRONE_BLOCKED_BY_DOOR ) && m_hBlockingDoor != NULL )
|
|
{
|
|
ClearCondition( COND_DRONE_BLOCKED_BY_DOOR );
|
|
if ( ValidBlockingDoor()) // && failedSchedule != SCHED_DRONE_BASH_DOOR )
|
|
{
|
|
if (asw_debug_drone.GetBool())
|
|
Msg("selecting bash from fail schedule\n");
|
|
return SCHED_DRONE_BASH_DOOR;
|
|
}
|
|
//Msg("Clearing door in schedule failure because valid=%d failed=%d\n", ValidBlockingDoor(), (int) failedSchedule);
|
|
m_hBlockingDoor = NULL;
|
|
}
|
|
|
|
return BaseClass::SelectFailSchedule( failedSchedule, failedTask, taskFailCode );
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::ValidBlockingDoor()
|
|
{
|
|
if (m_hBlockingDoor == NULL)
|
|
return false;
|
|
|
|
if (m_hBlockingDoor->IsDoorOpen() || m_hBlockingDoor->IsDoorOpening())
|
|
return false;
|
|
|
|
if (m_hBlockingDoor->m_bDoorFallen)
|
|
return false;
|
|
|
|
// not a valid door if our enemy isn't behind it
|
|
if ( GetAlienOrders() != AOT_MoveToIgnoringMarines && ( !GetEnemy() || !IsBehindDoor( GetEnemy() ) ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::StartTask( const Task_t *pTask )
|
|
{
|
|
switch( pTask->iTask )
|
|
{
|
|
case TASK_UNBURROW:
|
|
BaseClass::StartTask( pTask );
|
|
m_fFailedOverrideTime = gpGlobals->curtime; // stop us doing an override move immediately
|
|
break;
|
|
case TASK_MELEE_ATTACK1:
|
|
{
|
|
// check for making a marine shout out in fear
|
|
if (!m_bDoneAlienCloseChatter && gpGlobals->curtime > s_fNextTooCloseChatterTime)
|
|
{
|
|
CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(GetEnemy());
|
|
if (pMarine)
|
|
{
|
|
pMarine->GetMarineSpeech()->Chatter(CHATTER_ALIEN_TOO_CLOSE);
|
|
m_bDoneAlienCloseChatter = true;
|
|
s_fNextTooCloseChatterTime = gpGlobals->curtime + random->RandomInt(10, 30);
|
|
}
|
|
}
|
|
BaseClass::StartTask( pTask );
|
|
break;
|
|
}
|
|
case TASK_SMALL_FLINCH:
|
|
case TASK_BIG_FLINCH:
|
|
{
|
|
Remember(bits_MEMORY_FLINCHED);
|
|
Activity flinch = GetFlinchActivity( false, false );
|
|
SetIdealActivity( flinch );
|
|
if ( flinch == ACT_ALIEN_FLINCH_SMALL )
|
|
m_flNextFlinchTime = gpGlobals->curtime + 0.45f;
|
|
else if ( flinch == ACT_ALIEN_FLINCH_MEDIUM )
|
|
m_flNextFlinchTime = gpGlobals->curtime + 0.8f;
|
|
else if ( flinch == ACT_ALIEN_FLINCH_BIG )
|
|
m_flNextFlinchTime = gpGlobals->curtime + 1.25f;
|
|
break;
|
|
}
|
|
case TASK_DRONE_YAW_TO_DOOR:
|
|
{
|
|
AssertMsg( m_hBlockingDoor != NULL, "Expected condition handling to break schedule before landing here" );
|
|
if ( m_hBlockingDoor != NULL )
|
|
{
|
|
SetDoorBashYaw();
|
|
GetMotor()->SetIdealYaw( m_flDoorBashYaw );
|
|
}
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_DRONE_GET_PATH_TO_DOOR:
|
|
{
|
|
Vector vecGoalPos;
|
|
Vector vecDir;
|
|
|
|
float fDistToDoor = LinearDistanceToDoor();
|
|
// if we have no door to bash then just set a path to our current loc
|
|
if (!m_hBlockingDoor.IsValid() || fDistToDoor <= asw_drone_door_distance.GetFloat())
|
|
{
|
|
AI_NavGoal_t goal( GetAbsOrigin() );
|
|
GetNavigator()->SetGoal(goal);
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
|
|
vecDir = GetLocalOrigin() - m_hBlockingDoor->GetLocalOrigin();
|
|
VectorNormalize(vecDir);
|
|
vecDir.z = 0;
|
|
|
|
Vector vecDoorPos = m_hBlockingDoor->GetAbsOrigin();
|
|
// head to the sides of the door?
|
|
if (m_iDoorPos == 1)
|
|
{
|
|
Vector vecSide;
|
|
QAngle angFacing = m_hBlockingDoor->GetAbsAngles();
|
|
angFacing[YAW] += 90;
|
|
AngleVectors(angFacing, &vecSide);
|
|
vecDoorPos += vecSide * 50.0f;
|
|
}
|
|
else if (m_iDoorPos == 2)
|
|
{
|
|
Vector vecSide;
|
|
QAngle angFacing = m_hBlockingDoor->GetAbsAngles();
|
|
angFacing[YAW] += 90;
|
|
AngleVectors(angFacing, &vecSide);
|
|
vecDoorPos -= vecSide * 50.0f;
|
|
}
|
|
|
|
m_iDoorPos = m_iDoorPos + 1;
|
|
|
|
AI_NavGoal_t goal( m_hBlockingDoor->WorldSpaceCenter() );
|
|
|
|
goal.pTarget = m_hBlockingDoor;
|
|
GetNavigator()->SetGoal( goal );
|
|
|
|
if ( asw_debug_drone.GetInt() == 1 )
|
|
{
|
|
NDebugOverlay::Cross3D( vecGoalPos, Vector(8,8,8), -Vector(8,8,8), 0, 255, 0, true, 2.0f );
|
|
NDebugOverlay::Line( vecGoalPos, m_hBlockingDoor->WorldSpaceCenter(), 0, 255, 0, true, 2.0f );
|
|
NDebugOverlay::Line( m_hBlockingDoor->WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), 0, 255, 0, true, 2.0f );
|
|
}
|
|
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
case TASK_DRONE_GET_CIRCLE_PATH:
|
|
{
|
|
CBaseEntity *pEnemy = GetEnemy();
|
|
if (!pEnemy)
|
|
{
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
return;
|
|
}
|
|
// We want to move to a different location around our enemy, because the route from here to him is blocked
|
|
// (even with triangulation). We could pick a random spot around him, but that would probably cause lots
|
|
// of drones to bump into each other. Let's try and make them move anticlockwise around the enemy
|
|
|
|
// rotate our position around the enemy by a random amount
|
|
Vector vecDiff = GetAbsOrigin() - GetEnemy()->GetAbsOrigin();
|
|
float dist = vecDiff.Length();
|
|
float fYaw = UTIL_VecToYaw(vecDiff);
|
|
fYaw -= random->RandomFloat(20.0f, 40.0f);
|
|
QAngle newDir(0, fYaw, 0);
|
|
Vector vecNewPos;
|
|
AngleVectors(newDir, &vecNewPos);
|
|
vecNewPos *= dist * (random->RandomFloat(0.8f, 1.2f));
|
|
vecNewPos += GetEnemy()->GetAbsOrigin();
|
|
|
|
// do a trace towards our new picked pos and stop at an obstruction, just to make it less likely we're
|
|
// sending the AI into a solid object
|
|
trace_t tr;
|
|
UTIL_TraceHull( GetAbsOrigin() + Vector(0, 0, 20),
|
|
vecNewPos + Vector(0, 0, 20),
|
|
Vector(-5, -5, -5),
|
|
Vector(5, 5, 5),
|
|
MASK_NPCSOLID_BRUSHONLY,
|
|
this,
|
|
COLLISION_GROUP_NONE,
|
|
&tr );
|
|
|
|
Vector vecCirclePos;
|
|
if (tr.fraction == 1.0)
|
|
vecCirclePos = vecNewPos;
|
|
else if (tr.fraction > 0)
|
|
vecCirclePos = tr.endpos;
|
|
else
|
|
{
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
|
|
AI_NavGoal_t goal( vecCirclePos );
|
|
|
|
TranslateNavGoal( pEnemy, goal.dest );
|
|
|
|
if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
DevWarning( 2, "TASK_DRONE_GET_CIRCLE_PATH failed!!\n" );
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
break;
|
|
}
|
|
case TASK_DRONE_WAIT_FACE_ENEMY:
|
|
SetWait( pTask->flTaskData );
|
|
m_vecEnemyStandoffPosition = GetEnemyLKP();
|
|
break;
|
|
case TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT:
|
|
{
|
|
// see if we're near enough to the door
|
|
if (LinearDistanceToDoor() <= asw_drone_door_distance.GetFloat())
|
|
TaskComplete();
|
|
|
|
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
//Msg("%d: Finished TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT with no goal\n", entindex());
|
|
TaskComplete();
|
|
GetNavigator()->ClearGoal(); // Clear residual state
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TASK_DRONE_ATTACK_DOOR:
|
|
{
|
|
// check we're near enough to bash the door
|
|
int door_dist = LinearDistanceToDoor();
|
|
if ( door_dist > asw_drone_door_distance.GetFloat())
|
|
TaskFail("Too far from door");
|
|
else if (door_dist < asw_drone_door_distance_min.GetFloat())
|
|
{
|
|
//MoveAwayFromDoor();
|
|
}
|
|
m_DurationDoorBash.Reset();
|
|
ResetIdealActivity( SelectDoorBash() );
|
|
break;
|
|
}
|
|
case TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE:
|
|
{
|
|
if (!GetEnemy() || m_lifeState == LIFE_DEAD || GetHealth()<=0)
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
#ifdef INFESTED_DLL
|
|
if (ai_sequence_debug.GetBool() == true && (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT))
|
|
{
|
|
DevMsg("StartTask TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE calling SetActivity(ACT_RUN)");
|
|
}
|
|
#endif
|
|
SetActivity(ACT_RUN);
|
|
// note: Override move will kick in while we're doing this task and run us straight at our enemy
|
|
}
|
|
break;
|
|
}
|
|
case TASK_DRONE_DOOR_WAIT:
|
|
{
|
|
if (ValidBlockingDoor() && LinearDistanceToDoor() <= asw_drone_door_distance.GetFloat())
|
|
{
|
|
//Msg("Drone wait finishing to do a bash!\n");
|
|
TaskComplete();
|
|
SetSchedule(SCHED_DRONE_BASH_CLOSE_DOOR);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
BaseClass::StartTask( pTask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
|
|
bool CASW_Drone_Advanced::OnObstructionPreSteer( AILocalMoveGoal_t *pMoveGoal,
|
|
float distClear,
|
|
AIMoveResult_t *pResult )
|
|
{
|
|
if ( pMoveGoal->directTrace.pObstruction )
|
|
{
|
|
//Msg("Drone blocked by %s\n", pMoveGoal->directTrace.pObstruction->GetClassname());
|
|
// check if we collide with a door or door padding
|
|
CASW_Door *pDoor = dynamic_cast<CASW_Door *>( pMoveGoal->directTrace.pObstruction );
|
|
if (!pDoor)
|
|
{
|
|
CASW_Door_Padding *pPadding = dynamic_cast<CASW_Door_Padding *>( pMoveGoal->directTrace.pObstruction );
|
|
if (pPadding)
|
|
pDoor = dynamic_cast<CASW_Door *>( pPadding->GetParent() );
|
|
}
|
|
if ( pDoor && OnObstructingASWDoor( pMoveGoal, pDoor, distClear, pResult ) )
|
|
{
|
|
//NDebugOverlay::HorzArrow(WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 5, 255,255,0,255,true, 1.0f);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
CASW_Drone_Advanced *pDrone = dynamic_cast<CASW_Drone_Advanced *>( pMoveGoal->directTrace.pObstruction );
|
|
if (pDrone && pDrone->m_hBlockingDoor.Get() != NULL)
|
|
{
|
|
if (OnObstructingASWDoor( pMoveGoal, pDrone->m_hBlockingDoor.Get(), distClear, pResult ) )
|
|
{
|
|
//NDebugOverlay::HorzArrow(WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 5, 255,255,0,255,true, 1.0f);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//if (ValidBlockingDoor())
|
|
//NDebugOverlay::HorzArrow(WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 5, 0,0,255,255,true, 1.0f);
|
|
//else
|
|
//NDebugOverlay::HorzArrow(WorldSpaceCenter(), pMoveGoal->directTrace.pObstruction->WorldSpaceCenter(), 5, 255,0,0,255,true, 1.0f);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::OnObstructingASWDoor( AILocalMoveGoal_t *pMoveGoal, CASW_Door *pDoor,
|
|
float distClear, AIMoveResult_t *pResult )
|
|
{
|
|
if ( pMoveGoal->maxDist < distClear )
|
|
return false;
|
|
|
|
// don't start bashing a door if we're already trying to bash a door
|
|
if (GetTask() && GetTask()->iTask == TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT)
|
|
return false;
|
|
|
|
// By default, NPCs don't know how to open doors
|
|
if ( pDoor->IsDoorClosed() || pDoor->IsDoorClosing() )
|
|
{
|
|
if ( distClear < 0.1 )
|
|
{
|
|
*pResult = AIMR_BLOCKED_ENTITY;
|
|
}
|
|
else
|
|
{
|
|
pMoveGoal->maxDist = distClear;
|
|
*pResult = AIMR_OK;
|
|
}
|
|
|
|
if ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin )
|
|
{
|
|
if (asw_debug_drone.GetBool())
|
|
Msg("OnObstructingASWDoor setting m_hBlockingDoor, sched = %s\n", GetCurSchedule()->GetName());
|
|
m_hBlockingDoor = pDoor;
|
|
//m_flDoorBashYaw = UTIL_VecToYaw( pMoveGoal->directTrace.vHitNormal * -1 );
|
|
SetDoorBashYaw();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::ShouldJump( void )
|
|
{
|
|
if (!m_bJumper)
|
|
return false;
|
|
|
|
return BaseClass::ShouldJump();
|
|
}
|
|
|
|
// translate our chase schedule into our version which is interrupted by a blocked door
|
|
int CASW_Drone_Advanced::TranslateSchedule( int scheduleType )
|
|
{
|
|
int i = BaseClass::TranslateSchedule(scheduleType);
|
|
if (i == SCHED_SHOVER_CHASE_ENEMY)
|
|
{
|
|
// decide whether to go with pathed movement or straight override running at the enemy
|
|
if (HasCondition(COND_DRONE_LOS) && gpGlobals->curtime - m_fLastLostLOSTime > 2.0f
|
|
&& asw_drone_override_move.GetBool() && (!FailedOverrideMove() || IsMeleeAttacking()))
|
|
i = SCHED_DRONE_OVERRIDE_MOVE;
|
|
else
|
|
{
|
|
i = SCHED_DRONE_CHASE_ENEMY;
|
|
}
|
|
}
|
|
if (i == SCHED_DRONE_DOOR_WAIT && m_hBlockingDoor.Get() && !m_hBlockingDoor.Get()->m_bDoorFallen)
|
|
{
|
|
if (m_iDoorPos < 3)
|
|
{
|
|
//Msg("Translated to door bash (doorpos=%d)\n", m_iDoorPos);
|
|
return SCHED_DRONE_BASH_DOOR;
|
|
}
|
|
}
|
|
if (i == SCHED_CHASE_ENEMY_FAILED && GetEnemy() && HasCondition(COND_DRONE_LOS))
|
|
{
|
|
return SCHED_DRONE_CIRCLE_ENEMY;
|
|
}
|
|
if (i == SCHED_STANDOFF)
|
|
return SCHED_DRONE_STANDOFF;
|
|
|
|
return i;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
//---------------------------------------------------------
|
|
|
|
Activity CASW_Drone_Advanced::SelectDoorBash()
|
|
{
|
|
//if ( random->RandomInt( 1, 3 ) == 1 )
|
|
//return ACT_MELEE_ATTACK1;
|
|
return (Activity)ACT_DRONE_WALLPOUND;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::IsCurTaskContinuousMove()
|
|
{
|
|
const Task_t* pTask = GetTask();
|
|
if( !pTask )
|
|
return true;
|
|
|
|
switch( pTask->iTask )
|
|
{
|
|
case TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE:
|
|
case TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT:
|
|
return true;
|
|
break;
|
|
|
|
default:
|
|
return BaseClass::IsCurTaskContinuousMove();
|
|
break;
|
|
}
|
|
}
|
|
|
|
int CASW_Drone_Advanced::DrawDebugTextOverlays()
|
|
{
|
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
NDebugOverlay::EntityText( entindex(), text_offset, CFmtStr( "m_takedamage = %d", m_takedamage ), 0 );
|
|
text_offset++;
|
|
if (GetEnemy())
|
|
{
|
|
if (HasCondition(COND_CAN_MELEE_ATTACK1))
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("VVV CAN ATTACK VV"),0);
|
|
else
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("--- CAN'T ATTACK ---"),0);
|
|
text_offset++;
|
|
}
|
|
if (CapabilitiesGet() & bits_CAP_MOVE_JUMP)
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("Can Jump!"),0);
|
|
else
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("Cannot jump."),0);
|
|
text_offset++;
|
|
|
|
if (HasCondition(COND_DRONE_LOS))
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("Has COND_DRONE_LOS"),0);
|
|
else
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("No COND_DRONE_LOS"),0);
|
|
text_offset++;
|
|
if (HasCondition(COND_DRONE_GAINED_LOS))
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("Has COND_DRONE_GAINED_LOS"),0);
|
|
else
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("No COND_DRONE_GAINED_LOS"),0);
|
|
text_offset++;
|
|
|
|
if (GetTask() && ( (GetTask()->iTask == TASK_DRONE_DOOR_WAIT) || (GetTask()->iTask == TASK_DRONE_ATTACK_DOOR)))
|
|
{
|
|
if (GetEnemy() && !IsBehindDoor(GetEnemy()))
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("Enemy behind door"),0);
|
|
else
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr("Not behind door or no enemy"),0);
|
|
text_offset++;
|
|
}
|
|
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "Local org = %f %f %f", VectorExpand( GetLocalOrigin() ) ),0);
|
|
text_offset++;
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "Abs org = %f %f %f", VectorExpand( GetLocalOrigin() ) ),0);
|
|
text_offset++;
|
|
if ( GetParent() )
|
|
{
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "Parent lorg = %f %f %f", VectorExpand( GetParent()->GetLocalOrigin() ) ),0);
|
|
text_offset++;
|
|
NDebugOverlay::EntityText(entindex(),text_offset,CFmtStr( "Parent org = %f %f %f", VectorExpand( GetParent()->GetAbsOrigin() ) ),0);
|
|
text_offset++;
|
|
}
|
|
}
|
|
return text_offset;
|
|
}
|
|
|
|
bool CASW_Drone_Advanced::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost )
|
|
{
|
|
float multiplier = 1;
|
|
if ( moveType == bits_CAP_MOVE_JUMP )
|
|
{
|
|
*pCost *= 10.0f; // discourage jumping
|
|
}
|
|
else if ( moveType == bits_CAP_MOVE_CLIMB )
|
|
{
|
|
float delta = vecEnd.z - vecStart.z;
|
|
multiplier = ( delta > 0 ) ? 0.5 : 4.0; // make it really expensive to climb down
|
|
*pCost *= multiplier;
|
|
}
|
|
|
|
// increase cost of marines can see vecend?
|
|
//if (UTIL_ASW_MarineViewCone(vecEnd))
|
|
//multiplier *= asw_marine_view_cone_cost.GetFloat();
|
|
|
|
return ( multiplier != 1 );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Custom trace filter used for NPC LOS traces
|
|
//-----------------------------------------------------------------------------
|
|
CDroneTraceFilterLOS::CDroneTraceFilterLOS( IHandleEntity *pHandleEntity, int collisionGroup ) :
|
|
CTraceFilterSimple( pHandleEntity, collisionGroup )
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CDroneTraceFilterLOS::ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
|
|
{
|
|
CBaseEntity *pEntity = (CBaseEntity *)pServerEntity;
|
|
|
|
if ( !pEntity->BlocksLOS() )
|
|
return false;
|
|
|
|
if (pEntity->Classify() == CLASS_ASW_DRONE)
|
|
return false;
|
|
|
|
if (pEntity->Classify() == CLASS_ASW_SHIELDBUG)
|
|
return false;
|
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pServerEntity, contentsMask );
|
|
}
|
|
|
|
// only shock damage counts as heavy (and thus causes a flinch even during normal running)
|
|
bool CASW_Drone_Advanced::IsHeavyDamage( const CTakeDamageInfo &info )
|
|
{
|
|
// explosions always cause a flinch
|
|
if (( info.GetDamageType() & DMG_BLAST ) != 0 )
|
|
return true;
|
|
|
|
// dots never cause a flinch
|
|
if (( info.GetDamageType() & DMG_DIRECT ) != 0 )
|
|
return false;
|
|
|
|
// shock damage always causes large flinch
|
|
if (( info.GetDamageType() & DMG_SHOCK ) != 0 )
|
|
{
|
|
m_FlinchActivity = (Activity) ACT_ALIEN_FLINCH_BIG;
|
|
return true;
|
|
}
|
|
|
|
// todo: melee, based on marine's melee skill?
|
|
|
|
// check the attacker's weapon, if this is a marine
|
|
//if (info.GetAttacker())
|
|
//{
|
|
//Msg("Drone attacked by %s\n", info.GetAttacker()->GetClassname());
|
|
//}
|
|
CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(info.GetAttacker());
|
|
if (pMarine)
|
|
{
|
|
if (asw_debug_alien_damage.GetBool())
|
|
Msg("Drone checking damage from a marine, of type: %d\n", info.GetDamageType());
|
|
// if we've been kicked by a marine, store the flinch we should do based on that marine's melee skill
|
|
if ((info.GetDamageType() & DMG_CLUB) != 0)
|
|
{
|
|
int i = pMarine->GetAlienMeleeFlinch();
|
|
if (i == 0)
|
|
m_FlinchActivity = (Activity) ACT_ALIEN_FLINCH_SMALL;
|
|
else if (i == 2)
|
|
m_FlinchActivity = (Activity) ACT_ALIEN_FLINCH_BIG;
|
|
else
|
|
m_FlinchActivity = (Activity) ACT_ALIEN_FLINCH_MEDIUM;
|
|
//Msg("Storing flinch %d (%d)\n", (int) m_FlinchActivity, i);
|
|
return true;
|
|
}
|
|
}
|
|
m_FlinchActivity = ACT_INVALID;
|
|
if (pMarine && pMarine->GetActiveASWWeapon())
|
|
{
|
|
return pMarine->GetActiveASWWeapon()->ShouldAlienFlinch(this, info);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int CASW_Drone_Advanced::SelectFlinchSchedule_ASW()
|
|
{
|
|
//if ( !HasCondition(COND_HEAVY_DAMAGE) && !HasCondition(COND_LIGHT_DAMAGE) )
|
|
//return SCHED_NONE;
|
|
|
|
if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
|
|
return SCHED_NONE;
|
|
|
|
// only light damage flinch if shot during a melee attack
|
|
//if (!HasCondition(COND_HEAVY_DAMAGE) && !(GetTask() && (GetTask()->iTask == TASK_MELEE_ATTACK1)) )
|
|
//return SCHED_NONE;
|
|
if (!HasCondition(COND_HEAVY_DAMAGE)) // only flinch on heavy damage condition (which is set if a particular marine's weapon + skills cause a flinch)
|
|
return SCHED_NONE;
|
|
|
|
if (asw_debug_alien_damage.GetBool())
|
|
Msg("We have COND_HEAVY_DAMAGE, so flinching\n");
|
|
|
|
// Heavy damage. Break out of my current schedule and flinch.
|
|
//Activity iFlinchActivity = GetFlinchActivity( true, false );
|
|
//Msg("Clearing flinch activity (%d) in SelectFlinchSchedule_ASW\n", (int) iFlinchActivity);
|
|
//m_FlinchActivity = ACT_INVALID;
|
|
//if ( HaveSequenceForActivity( iFlinchActivity ) )
|
|
|
|
// assume our aliens have a schedule
|
|
return SCHED_BIG_FLINCH;
|
|
|
|
//return SCHED_NONE;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::CheckFlinches( void )
|
|
{
|
|
// If we're currently flinching, don't allow gesture flinches to be overlaid
|
|
if ( IsCurSchedule( SCHED_BIG_FLINCH ) )
|
|
{
|
|
ClearCondition( COND_LIGHT_DAMAGE );
|
|
ClearCondition( COND_HEAVY_DAMAGE );
|
|
}
|
|
|
|
// If it's been a while since we did a full flinch, forget that we flinched so we'll flinch fully again
|
|
if ( HasMemory(bits_MEMORY_FLINCHED) && gpGlobals->curtime > m_flNextFlinchTime )
|
|
{
|
|
Forget(bits_MEMORY_FLINCHED);
|
|
}
|
|
|
|
// check for gesture flinches
|
|
if ( HasCondition( COND_LIGHT_DAMAGE ) && gpGlobals->curtime > m_flNextSmallFlinchTime )
|
|
{
|
|
if ( HaveSequenceForActivity( (Activity) ACT_ALIEN_FLINCH_GESTURE ) )
|
|
{
|
|
m_flNextSmallFlinchTime = gpGlobals->curtime + RandomFloat( 0.3f, 1.0f );
|
|
RestartGesture( (Activity) ACT_ALIEN_FLINCH_GESTURE );
|
|
}
|
|
}
|
|
}
|
|
|
|
Activity CASW_Drone_Advanced::GetFlinchActivity( bool bHeavyDamage, bool bGesture )
|
|
{
|
|
static int iFlinchCount = 0;
|
|
iFlinchCount++;
|
|
if (asw_debug_alien_damage.GetBool())
|
|
Msg("Drone flinching (total flinch count = %d)\n", iFlinchCount);
|
|
// todo: if we have a flinch damage type set, use that
|
|
if (m_FlinchActivity != ACT_INVALID)
|
|
{
|
|
if (asw_debug_alien_damage.GetBool())
|
|
Msg(" Returning stored melee flinch %d\n", (int) m_FlinchActivity);
|
|
return m_FlinchActivity;
|
|
}
|
|
|
|
//Msg("Returning default small flinch\n");
|
|
//return (Activity) ACT_SMALL_FLINCH;
|
|
// random flinch
|
|
// todo: use longer flinch for bigger melee hits?
|
|
float f = random->RandomFloat();
|
|
if (f < 0.3f)
|
|
return (Activity) ACT_ALIEN_FLINCH_SMALL;
|
|
else if (f < 0.66f)
|
|
return (Activity) ACT_ALIEN_FLINCH_MEDIUM;
|
|
return (Activity) ACT_ALIEN_FLINCH_BIG;
|
|
}
|
|
|
|
void CASW_Drone_Advanced::SetSmallDoorBashHull( bool bSmall )
|
|
{
|
|
// don't change if something else is already messing with our hull
|
|
if (IsUsingSmallHull())
|
|
return;
|
|
|
|
if ( bSmall && !m_bUsingSmallDoorBashHull )
|
|
{
|
|
m_bUsingSmallDoorBashHull = true;
|
|
UTIL_SetSize(this, NAI_Hull::SmallMins(GetHullType()),NAI_Hull::SmallMaxs(GetHullType()));
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
SetupVPhysicsHull();
|
|
}
|
|
}
|
|
else if (!bSmall && m_bUsingSmallDoorBashHull)
|
|
{
|
|
trace_t tr;
|
|
Vector vUpBit = GetAbsOrigin();
|
|
vUpBit.z += 1;
|
|
|
|
// check we have room to grow
|
|
AI_TraceHull( GetAbsOrigin(), vUpBit, GetHullMins(),
|
|
GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
|
|
if ( !tr.startsolid && (tr.fraction == 1.0) )
|
|
{
|
|
m_bUsingSmallDoorBashHull = false;
|
|
UTIL_SetSize(this, GetHullMins(),GetHullMaxs());
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
SetupVPhysicsHull();
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// a rough heuristic to see if our enemy is sitting behind a door
|
|
bool CASW_Drone_Advanced::IsBehindDoor(CBaseEntity *pOther)
|
|
{
|
|
CASW_Door *pDoor = m_hBlockingDoor.Get();
|
|
if (!pOther || !pDoor)
|
|
{
|
|
//Msg("no door or other in behind door test\n");
|
|
return false;
|
|
}
|
|
|
|
Vector diff = pOther->GetAbsOrigin() - GetAbsOrigin();
|
|
Vector doorDiff = pDoor->GetAbsOrigin() - GetAbsOrigin();
|
|
|
|
// if they're nearer than the door, they're probably not behind it
|
|
if (diff.LengthSqr() < doorDiff.LengthSqr())
|
|
{
|
|
//Msg("Other too near in behind door test\n");
|
|
return false;
|
|
}
|
|
|
|
// if they're not in the direction of the door, they're probably not behind it
|
|
VectorNormalize(diff);
|
|
VectorNormalize(doorDiff);
|
|
if (diff.Dot(doorDiff) <= 0)
|
|
{
|
|
//Msg("Other not in door direction in behind door test\n");
|
|
return false;
|
|
}
|
|
|
|
// if we can see him, he's not behind a door
|
|
trace_t tr;
|
|
UTIL_TraceLine(WorldSpaceCenter(), GetEnemy()->WorldSpaceCenter(), MASK_NPCSOLID ,this, GetCollisionGroup(), &tr);
|
|
if (tr.fraction >= 1.0f || tr.m_pEnt == GetEnemy())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// moves us back a bit from the door, so we're not attacking through it
|
|
void CASW_Drone_Advanced::MoveAwayFromDoor()
|
|
{
|
|
CASW_Door *pDoor = m_hBlockingDoor.Get();
|
|
if (!pDoor)
|
|
return;
|
|
|
|
Vector vecPerp;
|
|
QAngle angFacing = pDoor->GetAbsAngles();
|
|
angFacing[YAW] += 90;
|
|
AngleVectors(angFacing, &vecPerp);
|
|
float fDoorWidth = 75.0f;
|
|
Vector vecDoorA = pDoor->GetAbsOrigin() + vecPerp * fDoorWidth;
|
|
Vector vecDoorB = pDoor->GetAbsOrigin() - vecPerp * fDoorWidth;
|
|
//NDebugOverlay::Line( vecDoorA, vecDoorB, 0, 255, 0, true, 2.0f );
|
|
float dist = CalcDistanceToLine2D(GetAbsOrigin().AsVector2D(), vecDoorA.AsVector2D(), vecDoorB.AsVector2D());
|
|
float move_amount = (asw_drone_door_distance_min.GetFloat()) - dist;
|
|
Vector diff = GetAbsOrigin() - pDoor->GetAbsOrigin();
|
|
angFacing[YAW] -=90;
|
|
Vector vecForward;
|
|
AngleVectors(angFacing, &vecForward);
|
|
if (diff.Dot(vecForward) < 0)
|
|
{
|
|
move_amount *= -1;
|
|
}
|
|
vecForward *= move_amount;
|
|
Msg("Moving alien by %f, %f, %f\n", vecForward.x, vecForward.y, vecForward.z);
|
|
SetAbsOrigin(GetAbsOrigin() + vecForward); // todo: check this isn't making us stuck in something?
|
|
}
|
|
|
|
float CASW_Drone_Advanced::LinearDistanceToDoor()
|
|
{
|
|
CASW_Door *pDoor = m_hBlockingDoor.Get();
|
|
if (!pDoor)
|
|
return -1;
|
|
|
|
Vector vecPerp;
|
|
QAngle angFacing = pDoor->GetAbsAngles();
|
|
angFacing[YAW] += 90;
|
|
AngleVectors(angFacing, &vecPerp);
|
|
float fDoorWidth = 75.0f;
|
|
Vector vecDoorA = pDoor->GetAbsOrigin() + vecPerp * fDoorWidth;
|
|
Vector vecDoorB = pDoor->GetAbsOrigin() - vecPerp * fDoorWidth;
|
|
//NDebugOverlay::Line( vecDoorA, vecDoorB, 0, 255, 0, true, 2.0f );
|
|
return CalcDistanceToLine2D(GetAbsOrigin().AsVector2D(), vecDoorA.AsVector2D(), vecDoorB.AsVector2D());
|
|
}
|
|
|
|
void CASW_Drone_Advanced::SetDoorBashYaw()
|
|
{
|
|
CASW_Door *pDoor = m_hBlockingDoor.Get();
|
|
if (!pDoor)
|
|
return;
|
|
|
|
// figure out which side of the door we're on
|
|
Vector vecFacing;
|
|
Vector vecDiff = GetAbsOrigin() - pDoor->GetAbsOrigin();
|
|
AngleVectors(pDoor->GetAbsAngles(), &vecFacing);
|
|
float dot = DotProduct(vecFacing, vecDiff);
|
|
if (dot < 0)
|
|
{
|
|
m_flDoorBashYaw = pDoor->GetAbsAngles()[YAW];
|
|
}
|
|
else
|
|
{
|
|
m_flDoorBashYaw = anglemod(pDoor->GetAbsAngles()[YAW] + 180.0f);
|
|
}
|
|
}
|
|
|
|
void CASW_Drone_Advanced::SetHealthByDifficultyLevel()
|
|
{
|
|
int iHealth = MAX(25, ASWGameRules()->ModifyAlienHealthBySkillLevel(asw_drone_health.GetInt()));
|
|
if (asw_debug_alien_damage.GetBool())
|
|
Msg("Setting drone's initial health to %d\n", iHealth);
|
|
SetHealth(iHealth);
|
|
SetMaxHealth(iHealth);
|
|
SetHitboxSet(0);
|
|
}
|
|
|
|
// if we arrive at our destination, clear our orders
|
|
bool CASW_Drone_Advanced::ShouldClearOrdersOnMovementComplete()
|
|
{
|
|
// don't clear our orders if we're bashing a door while traveling a route
|
|
if ( m_AlienOrders == AOT_MoveToIgnoringMarines && IsCurSchedule( SCHED_DRONE_BASH_DOOR ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return BaseClass::ShouldClearOrdersOnMovementComplete();
|
|
}
|
|
|
|
AI_BEGIN_CUSTOM_NPC( asw_drone_advanced, CASW_Drone_Advanced )
|
|
DECLARE_CONDITION( COND_DRONE_BLOCKED_BY_DOOR )
|
|
DECLARE_CONDITION( COND_DRONE_DOOR_OPENED )
|
|
DECLARE_CONDITION( COND_DRONE_LOS )
|
|
DECLARE_CONDITION( COND_DRONE_LOST_LOS )
|
|
DECLARE_CONDITION( COND_DRONE_GAINED_LOS )
|
|
DECLARE_TASK( TASK_DRONE_YAW_TO_DOOR )
|
|
DECLARE_TASK( TASK_DRONE_ATTACK_DOOR )
|
|
DECLARE_TASK( TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE )
|
|
DECLARE_TASK( TASK_DRONE_GET_PATH_TO_DOOR )
|
|
DECLARE_TASK( TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT )
|
|
DECLARE_TASK( TASK_DRONE_DOOR_WAIT )
|
|
DECLARE_TASK( TASK_DRONE_GET_CIRCLE_PATH )
|
|
DECLARE_TASK( TASK_DRONE_WAIT_FACE_ENEMY )
|
|
// Activities
|
|
DECLARE_ACTIVITY( ACT_DRONE_RUN_ATTACKING )
|
|
DECLARE_ACTIVITY( ACT_DRONE_WALLPOUND )
|
|
|
|
// Events
|
|
DECLARE_ANIMEVENT( AE_DRONE_WALK_FOOTSTEP )
|
|
DECLARE_ANIMEVENT( AE_DRONE_FOOTSTEP_SOFT )
|
|
DECLARE_ANIMEVENT( AE_DRONE_FOOTSTEP_HEAVY )
|
|
DECLARE_ANIMEVENT( AE_DRONE_MELEE_HIT1 )
|
|
DECLARE_ANIMEVENT( AE_DRONE_MELEE_HIT2 )
|
|
DECLARE_ANIMEVENT( AE_DRONE_MELEE1_SOUND )
|
|
DECLARE_ANIMEVENT( AE_DRONE_MELEE2_SOUND )
|
|
DECLARE_ANIMEVENT( AE_DRONE_MOUTH_BLEED )
|
|
DECLARE_ANIMEVENT( AE_DRONE_ALERT_SOUND )
|
|
DECLARE_ANIMEVENT( AE_DRONE_SHADOW_ON )
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_BASH_DOOR,
|
|
|
|
" Tasks"
|
|
//" TASK_SET_ACTIVITY ACTIVITY:ACT_ALIEN_SHOVER_ROAR"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_DRONE_DOOR_WAIT"
|
|
" TASK_DRONE_GET_PATH_TO_DOOR 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_DRONE_WAIT_FOR_DOOR_MOVEMENT 0"
|
|
" TASK_DRONE_YAW_TO_DOOR 0"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_STOP_MOVING 1"
|
|
" TASK_DRONE_ATTACK_DOOR 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_NEW_ENEMY"
|
|
" COND_DRONE_DOOR_OPENED"
|
|
)
|
|
|
|
// attack a door we're already near to
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_BASH_CLOSE_DOOR,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_DRONE_DOOR_WAIT"
|
|
" TASK_DRONE_YAW_TO_DOOR 0"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_STOP_MOVING 1"
|
|
" TASK_DRONE_ATTACK_DOOR 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_NEW_ENEMY"
|
|
" COND_DRONE_DOOR_OPENED"
|
|
)
|
|
|
|
// drone waiting for a door to be bashed open by some other aliens
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_DOOR_WAIT,
|
|
|
|
" Tasks"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_DRONE_DOOR_WAIT 1"
|
|
""
|
|
" Interrupts"
|
|
" COND_DRONE_DOOR_OPENED"
|
|
)
|
|
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_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"
|
|
" COND_DRONE_BLOCKED_BY_DOOR"
|
|
" COND_DRONE_GAINED_LOS"
|
|
" COND_HEAVY_DAMAGE"
|
|
//" COND_DRONE_LOS" // this is needed for override move, but currently seems to be bugged and always on, causing this schedule to fail constantly
|
|
)
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_CIRCLE_ENEMY,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_DRONE_CIRCLE_ENEMY_FAILED"
|
|
//" TASK_SET_TOLERANCE_DISTANCE 24"
|
|
" TASK_DRONE_GET_CIRCLE_PATH 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"
|
|
" COND_DRONE_BLOCKED_BY_DOOR"
|
|
" COND_DRONE_GAINED_LOS"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_CIRCLE_ENEMY_FAILED,
|
|
// much like the default standoff schedule, but the AI is more agressive at starting chasing again
|
|
// the wait is also much shorter, so the drone will try to circle to a new position fairly quickly
|
|
// reattempts circling once this schedule is done
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
|
|
" TASK_DRONE_WAIT_FACE_ENEMY 0.4"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_DRONE_CIRCLE_ENEMY"
|
|
""
|
|
" Interrupts"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_NEW_ENEMY"
|
|
" COND_HEAR_DANGER"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_STANDOFF,
|
|
// much like the default standoff schedule, but the AI is more agressive at starting chasing again
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
|
|
" TASK_DRONE_WAIT_FACE_ENEMY 2"
|
|
""
|
|
" Interrupts"
|
|
" COND_CAN_RANGE_ATTACK1"
|
|
" COND_CAN_RANGE_ATTACK2"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_NEW_ENEMY"
|
|
" COND_HEAR_DANGER"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_DRONE_OVERRIDE_MOVE,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_DRONE_CHASE_ENEMY"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_DRONE_WAIT_FOR_OVERRIDE_MOVE 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_ENEMY_UNREACHABLE"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
" COND_CAN_MELEE_ATTACK2"
|
|
" COND_TASK_FAILED"
|
|
" COND_ALIEN_SHOVER_PHYSICS_TARGET"
|
|
" COND_DRONE_BLOCKED_BY_DOOR"
|
|
" COND_DRONE_LOST_LOS"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
|
|
AI_END_CUSTOM_NPC() |