You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2544 lines
76 KiB
2544 lines
76 KiB
// 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 5: boost *= asw_alien_speed_scale_insane.GetFloat(); break; |
|
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() |