3195 lines
90 KiB
C++
3195 lines
90 KiB
C++
#include "cbase.h"
|
|
#include "asw_alien.h"
|
|
#include "asw_ai_senses.h"
|
|
#include "asw_game_resource.h"
|
|
#include "asw_gamerules.h"
|
|
#include "asw_mission_manager.h"
|
|
#include "asw_marine_resource.h"
|
|
#include "asw_marine.h"
|
|
#include "asw_trace_filter_melee.h"
|
|
#include "asw_spawner.h"
|
|
#include "ai_waypoint.h"
|
|
#include "ai_moveprobe.h"
|
|
#include "asw_fx_shared.h"
|
|
#include "SoundEmitterSystem/isoundemittersystembase.h"
|
|
#include "EntityFlame.h"
|
|
#include "asw_util_shared.h"
|
|
#include "asw_burning.h"
|
|
#include "asw_door.h"
|
|
#include "asw_gamestats.h"
|
|
#include "asw_pickup_money.h"
|
|
#include "asw_director.h"
|
|
#include "asw_physics_prop_statue.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "asw_ai_behavior.h"
|
|
#include "asw_ai_behavior_combat_stun.h"
|
|
#include "asw_ai_behavior_sleep.h"
|
|
#include "asw_ai_behavior_flinch.h"
|
|
#include "asw_missile_round_shared.h"
|
|
#include "datacache/imdlcache.h"
|
|
#include "asw_tesla_trap.h"
|
|
#include "sendprop_priorities.h"
|
|
#include "asw_spawn_manager.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern ConVar asw_debug_alien_damage;
|
|
extern ConVar ai_show_hull_attacks;
|
|
extern ConVar ai_efficiency_override;
|
|
extern ConVar ai_frametime_limit;
|
|
extern ConVar ai_use_think_optimizations;
|
|
extern ConVar ai_use_efficiency;
|
|
extern ConVar showhitlocation;
|
|
extern ConVar asw_stun_grenade_time;
|
|
extern ConVar asw_drone_zig_zagging;
|
|
extern ConVar asw_draw_awake_ai;
|
|
extern ConVar asw_alien_debug_death_style;
|
|
// asw - how much extra damage to do to burning aliens
|
|
ConVar asw_fire_alien_damage_scale("asw_fire_alien_damage_scale", "3.0", FCVAR_CHEAT );
|
|
ConVar asw_alien_speed_scale_easy("asw_alien_speed_scale_easy", "0");
|
|
ConVar asw_alien_speed_scale_normal("asw_alien_speed_scale_normal", "0");
|
|
ConVar asw_alien_speed_scale_hard("asw_alien_speed_scale_hard", "0");
|
|
ConVar asw_alien_speed_scale_insane("asw_alien_speed_scale_insane", "0");
|
|
ConVar asw_alien_hurt_speed( "asw_alien_hurt_speed", "0.5", FCVAR_CHEAT, "Fraction of speed to use when the alien is hurt after being shot" );
|
|
ConVar asw_alien_stunned_speed( "asw_alien_stunned_speed", "0.3", FCVAR_CHEAT, "Fraction of speed to use when the alien is electrostunned" );
|
|
ConVar asw_drop_money("asw_drop_money", "1", FCVAR_CHEAT, "Do aliens drop money?");
|
|
ConVar asw_alien_money_chance("asw_alien_money_chance", "1.0", FCVAR_CHEAT, "Chance of base aliens dropping money");
|
|
ConVar asw_drone_hurl_chance( "asw_drone_hurl_chance", "0.66666666666666", FCVAR_NONE, "Chance that an alien killed by explosives will hurl towards the camera." );
|
|
ConVar asw_drone_hurl_interval( "asw_drone_hurl_interval", "10", FCVAR_NONE, "Minimum number of seconds that must pass between alien bodies flung at camera." );
|
|
ConVar asw_alien_break_chance ( "asw_alien_break_chance", "0.5", FCVAR_NONE, "chance the alien will break into ragdoll pieces instead of gib");
|
|
ConVar asw_alien_fancy_death_chance ( "asw_alien_fancy_death_chance", "0.5", FCVAR_NONE, "If a drone doesn't instagib, this is the chance the alien plays a death anim before ragdolling" );
|
|
ConVar asw_debug_npcs( "asw_debug_npcs", "0", FCVAR_CHEAT, "Enables debug overlays for various NPCs" );
|
|
ConVar asw_alien_burn_duration( "asw_alien_burn_duration", "5.0f", FCVAR_CHEAT, "Alien burn time" );
|
|
extern ConVar asw_money;
|
|
|
|
// soft alien collision
|
|
ConVar asw_springcol( "asw_springcol", "1", FCVAR_CHEAT, "Use soft alien collision" );
|
|
ConVar asw_springcol_push_cap( "asw_springcol_push_cap", "33.0", FCVAR_CHEAT, "Cap on the total push vector" );
|
|
ConVar asw_springcol_core( "asw_springcol_core", "0.33", FCVAR_CHEAT, "Fraction of the alien's pushaway radius that is a solid capped core" );
|
|
ConVar asw_springcol_radius( "asw_springcol_radius", "50.0", FCVAR_CHEAT, "Radius of the alien's pushaway cylinder" );
|
|
ConVar asw_springcol_force_scale( "asw_springcol_force_scale", "3.0", FCVAR_CHEAT, "Multiplier for each individual push force" );
|
|
ConVar asw_springcol_debug( "asw_springcol_debug", "0", FCVAR_CHEAT, "Display the direction of the pushaway vector. Set to entity index or -1 to show all." );
|
|
|
|
float CASW_Alien::sm_flLastHurlTime = 0;
|
|
|
|
int ACT_MELEE_ATTACK1_HIT;
|
|
int ACT_MELEE_ATTACK2_HIT;
|
|
int AE_ALIEN_MELEE_HIT;
|
|
|
|
int ACT_ALIEN_FLINCH_SMALL;
|
|
int ACT_ALIEN_FLINCH_MEDIUM;
|
|
int ACT_ALIEN_FLINCH_BIG;
|
|
int ACT_ALIEN_FLINCH_GESTURE;
|
|
int ACT_DEATH_FIRE;
|
|
int ACT_DEATH_ELEC;
|
|
int ACT_DIE_FANCY;
|
|
|
|
int ACT_BURROW_IDLE;
|
|
int ACT_BURROW_OUT;
|
|
|
|
LINK_ENTITY_TO_CLASS( asw_alien, CASW_Alien );
|
|
|
|
IMPLEMENT_SERVERCLASS_ST(CASW_Alien, DT_ASW_Alien)
|
|
SendPropExclude ( "DT_BaseEntity", "m_vecOrigin" ),
|
|
SendPropVectorXY( SENDINFO( m_vecOrigin ), CELL_BASEENTITY_ORIGIN_CELL_BITS, SPROP_CELL_COORD_LOWPRECISION | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOriginXY, SENDPROP_NONLOCALPLAYER_ORIGINXY_PRIORITY ),
|
|
SendPropFloat ( SENDINFO_VECTORELEM( m_vecOrigin, 2 ), CELL_BASEENTITY_ORIGIN_CELL_BITS, SPROP_CELL_COORD_LOWPRECISION | SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, CBaseEntity::SendProxy_CellOriginZ, SENDPROP_NONLOCALPLAYER_ORIGINZ_PRIORITY ),
|
|
|
|
SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesX ),
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesY ),
|
|
SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 10, SPROP_CHANGES_OFTEN, CBaseEntity::SendProxy_AnglesZ ),
|
|
|
|
// test
|
|
//SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
|
|
SendPropBool( SENDINFO(m_bElectroStunned) ),// not using ElectroStunned
|
|
//SendPropBool( SENDINFO(m_bElectroShockSmall) ),
|
|
//SendPropBool( SENDINFO(m_bElectroShockBig) ),
|
|
SendPropBool( SENDINFO(m_bOnFire) ),
|
|
SendPropInt( SENDINFO(m_nDeathStyle), CASW_Alien::kDEATHSTYLE_NUM_TRANSMIT_BITS , SPROP_UNSIGNED ),
|
|
SendPropInt (SENDINFO(m_iHealth), ASW_ALIEN_HEALTH_BITS ),
|
|
//SendPropBool(SENDINFO(m_bGibber)),
|
|
END_SEND_TABLE()
|
|
|
|
BEGIN_DATADESC( CASW_Alien )
|
|
DEFINE_KEYFIELD( m_bVisibleWhenAsleep, FIELD_BOOLEAN, "visiblewhenasleep" ),
|
|
DEFINE_KEYFIELD( m_iMoveCloneName, FIELD_STRING, "MoveClone" ),
|
|
DEFINE_KEYFIELD( m_bStartBurrowed, FIELD_BOOLEAN, "startburrowed" ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "BreakWaitForScript", InputBreakWaitForScript ),
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetMoveClone", InputSetMoveClone ),
|
|
DEFINE_FIELD( m_flBurrowTime, FIELD_TIME ),
|
|
DEFINE_FIELD(m_bIgnoreMarines, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bFailedMoveTo, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bElectroStunned, FIELD_BOOLEAN),
|
|
|
|
//DEFINE_FIELD(m_bPerformingZigZag, FIELD_BOOLEAN), // don't store this, let the zig zag be cleared each time
|
|
//DEFINE_FIELD(m_bRunAtChasingPathEnds, FIELD_BOOLEAN), // no need to store currently, it's always true form constructor
|
|
DEFINE_FIELD(m_fNextPainSound, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_fNextStunSound, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_fHurtSlowMoveTime, FIELD_TIME),
|
|
// m_ActBusyBehavior (auto saved by AI)
|
|
DEFINE_FIELD( m_hSpawner, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_AlienOrders, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_vecAlienOrderSpot, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_AlienOrderObject, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_fLastSleepCheckTime, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_bOnFire, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_iNumASWOrderRetries, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_flFreezeResistance, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_flFrozenTime, FIELD_TIME ),
|
|
|
|
// soft alien collision
|
|
DEFINE_FIELD( m_vecLastPushAwayOrigin, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_vecLastPush, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_bPushed, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bHoldoutAlien, FIELD_BOOLEAN ),
|
|
END_DATADESC()
|
|
|
|
IMPLEMENT_AUTO_LIST( IAlienAutoList );
|
|
|
|
#define ASW_ALIEN_SLEEP_CHECK_INTERVAL 1.0f
|
|
|
|
CASW_Alien::CASW_Alien( void ) :
|
|
m_BehaviorParms( DefLessFunc( const CUtlSymbol ) )
|
|
{
|
|
m_bRegisteredAsAwake = false;
|
|
m_pszAlienModelName = NULL;
|
|
m_bRunAtChasingPathEnds = true;
|
|
m_bPerformingZigZag = false;
|
|
m_fNextPainSound = 0;
|
|
m_fNextStunSound = 0;
|
|
|
|
m_fHurtSlowMoveTime = 0;
|
|
m_hSpawner = NULL;
|
|
m_AlienOrders = AOT_None;
|
|
m_vecAlienOrderSpot = vec3_origin;
|
|
m_AlienOrderObject = NULL;
|
|
m_bIgnoreMarines = false;
|
|
m_fLastSleepCheckTime = 0;
|
|
m_bVisibleWhenAsleep = false;
|
|
m_fLastMarineCanSeeTime = -100;
|
|
m_bLastMarineCanSee = false;
|
|
//m_bGibber = false;
|
|
m_bTimeToRagdoll = false;
|
|
m_iDeadBodyGroup = 1;
|
|
m_bNeverRagdoll = false;
|
|
m_bNeverInstagib = false;
|
|
m_nDeathStyle = kDIE_RAGDOLLFADE;
|
|
m_flBaseThawRate = 0.5f;
|
|
m_flFrozenTime = 0.0f;
|
|
|
|
m_UnburrowActivity = (Activity) ACT_BURROW_OUT;
|
|
m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE;
|
|
m_iszUnburrowActivityName = NULL_STRING;
|
|
m_iszUnburrowIdleActivityName = NULL_STRING;
|
|
|
|
m_pPreviousBehavior = NULL;
|
|
m_vecLastPush.Init();
|
|
m_bBehaviorParameterChanged = false;
|
|
|
|
m_nVolleyType = -1;
|
|
m_flRangeAttackStartTime = 0.0f;
|
|
m_flRangeAttackLastUpdateTime = 0.0f;
|
|
m_vecRangeAttackTargetPosition.Init();
|
|
m_bHoldoutAlien = false;
|
|
|
|
m_nAlienCollisionGroup = COLLISION_GROUP_NPC;
|
|
|
|
meleeAttack1.Init( 0.0f, 64.0f, 0.7f, false );
|
|
meleeAttack2.Init( 0.0f, 64.0f, 0.7f, false );
|
|
rangeAttack1.Init( 64.0f, 786.0f, 0.5f, false );
|
|
rangeAttack2.Init( 64.0f, 512.0f, 0.5f, false );
|
|
}
|
|
|
|
CASW_Alien::~CASW_Alien()
|
|
{
|
|
for( int i = 0; i < m_Behaviors.Count(); i++ )
|
|
{
|
|
if ( m_Behaviors[ i ]->IsAllocated() )
|
|
{
|
|
delete m_Behaviors[ i ];
|
|
m_Behaviors.Remove( i );
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::Spawn()
|
|
{
|
|
if ( asw_debug_npcs.GetBool() )
|
|
{
|
|
m_debugOverlays |= OVERLAY_NPC_ROUTE_BIT | OVERLAY_BBOX_BIT | OVERLAY_PIVOT_BIT | OVERLAY_TASK_TEXT_BIT | OVERLAY_TEXT_BIT;
|
|
}
|
|
|
|
ChangeFaction( FACTION_ALIENS );
|
|
|
|
// get pointers to behaviors
|
|
GetBehavior( &m_pFlinchBehavior );
|
|
// Precache.
|
|
Precache();
|
|
SetModel( m_pszAlienModelName );
|
|
|
|
SetHullSizeNormal();
|
|
|
|
// Base spawn.
|
|
BaseClass::Spawn();
|
|
|
|
// By default NPCs are always solid and cannot be stood upon.
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
SetBloodColor( BLOOD_COLOR_GREEN );
|
|
|
|
SetCollisionGroup( m_nAlienCollisionGroup );
|
|
|
|
SetNavType( NAV_GROUND );
|
|
SetMoveType( MOVETYPE_STEP );
|
|
|
|
// Set the initial NPC state.
|
|
m_NPCState = NPC_STATE_NONE;
|
|
m_vecLastPushAwayOrigin = GetAbsOrigin();
|
|
m_fFancyDeathChance = RandomFloat();
|
|
|
|
if ( m_SquadName != NULL_STRING )
|
|
{
|
|
CapabilitiesAdd( bits_CAP_SQUAD );
|
|
}
|
|
|
|
if ( HasSpawnFlags( SF_ANTLION_USE_GROUNDCHECKS ) == false )
|
|
{
|
|
CapabilitiesAdd( bits_CAP_SKIP_NAV_GROUND_CHECK );
|
|
}
|
|
NPCInit();
|
|
m_flDistTooFar = 9999999.0f;
|
|
LookupBurrowActivities();
|
|
|
|
//See if we're supposed to start burrowed
|
|
if ( m_bStartBurrowed )
|
|
{
|
|
AddEffects( EF_NODRAW );
|
|
AddEffects( EF_NOSHADOW );
|
|
AddFlag( FL_NOTARGET );
|
|
m_spawnflags |= SF_NPC_GAG;
|
|
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
m_takedamage = DAMAGE_NO;
|
|
m_vecUnburrowEndPoint = GetAbsOrigin();
|
|
|
|
// set alien to fly and not collide for the duration of his unburrow
|
|
AddFlag( FL_FLY );
|
|
SetGroundEntity( NULL );
|
|
SetCollisionGroup( COLLISION_GROUP_NPC_SCRIPTED );
|
|
|
|
// offset alien's position by the motion in his unburrow sequence
|
|
Activity unburrowActivity = m_UnburrowActivity;
|
|
int nSeq = SelectWeightedSequence( unburrowActivity );
|
|
if ( nSeq != -1 )
|
|
{
|
|
Vector vecLocalDelta;
|
|
GetSequenceLinearMotion( nSeq, &vecLocalDelta );
|
|
|
|
// Transform the sequence delta
|
|
matrix3x4_t fRotateMatrix;
|
|
AngleMatrix( GetLocalAngles(), fRotateMatrix );
|
|
Vector vecDelta;
|
|
VectorRotate( vecLocalDelta, fRotateMatrix, vecDelta );
|
|
|
|
Vector vecNewPos = GetAbsOrigin() - vecDelta;
|
|
Teleport( &vecNewPos, &GetAbsAngles(), &vec3_origin );
|
|
}
|
|
|
|
SetState( NPC_STATE_IDLE );
|
|
SetActivity( m_UnburrowIdleActivity );
|
|
SetSchedule( SCHED_BURROW_WAIT ); // todo: a schedule where they don't crawl out right away?
|
|
}
|
|
|
|
if ( m_iMoveCloneName != NULL_STRING )
|
|
{
|
|
SetMoveClone( m_iMoveCloneName, NULL );
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::Precache()
|
|
{
|
|
|
|
BaseClass::Precache();
|
|
|
|
|
|
PrecacheModel( m_pszAlienModelName );
|
|
|
|
//pre-cache any models used by particle gib effects
|
|
int modelIndex = modelinfo->GetModelIndex( m_pszAlienModelName );
|
|
const model_t *model = modelinfo->GetModel( modelIndex );
|
|
if ( model )
|
|
{
|
|
KeyValues *modelKeyValues = new KeyValues( "" );
|
|
if ( modelKeyValues->LoadFromBuffer( m_ModelName.ToCStr(), modelinfo->GetModelKeyValueText( model ) ) )
|
|
{
|
|
|
|
KeyValues *pkvMeshParticleEffect = modelKeyValues->FindKey( "MeshParticles" );
|
|
if ( pkvMeshParticleEffect )
|
|
{
|
|
|
|
for ( KeyValues *pSingleEffect = pkvMeshParticleEffect->GetFirstSubKey(); pSingleEffect; pSingleEffect = pSingleEffect->GetNextKey() )
|
|
{
|
|
const char *pszModelName = pSingleEffect->GetString( "modelName", "" );
|
|
if( pszModelName )
|
|
{
|
|
PrecacheModel( pszModelName );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
modelKeyValues->deleteThis();
|
|
}
|
|
|
|
|
|
PrecacheParticleSystem( "drone_death" ); // death
|
|
PrecacheParticleSystem( "drone_shot" ); // shot
|
|
PrecacheParticleSystem( "freeze_statue_shatter" );
|
|
}
|
|
|
|
// Updates our memory about the enemies we Swarm Sensed
|
|
// todo: add various swarm sense conditions?
|
|
void CASW_Alien::OnSwarmSensed(int iDistance)
|
|
{
|
|
AISightIter_t iter;
|
|
CBaseEntity *pSenseEnt;
|
|
|
|
pSenseEnt = GetASWSenses()->GetFirstSwarmSenseEntity( &iter );
|
|
|
|
while( pSenseEnt )
|
|
{
|
|
if ( pSenseEnt->IsPlayer() )
|
|
{
|
|
// if we see a client, remember that (mostly for scripted AI)
|
|
//SetCondition(COND_SEE_PLAYER);
|
|
}
|
|
|
|
Disposition_t relation = IRelationType( pSenseEnt );
|
|
|
|
// the looker will want to consider this entity
|
|
// don't check anything else about an entity that can't be seen, or an entity that you don't care about.
|
|
if ( relation != D_NU )
|
|
{
|
|
if ( pSenseEnt == GetEnemy() )
|
|
{
|
|
// we know this ent is visible, so if it also happens to be our enemy, store that now.
|
|
//SetCondition(COND_SEE_ENEMY);
|
|
}
|
|
|
|
// don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
|
|
// we see npcs other than the Enemy.
|
|
switch ( relation )
|
|
{
|
|
case D_HT:
|
|
{
|
|
int priority = IRelationPriority( pSenseEnt );
|
|
if (priority < 0)
|
|
{
|
|
//SetCondition(COND_SEE_DISLIKE);
|
|
}
|
|
else if (priority > 10)
|
|
{
|
|
//SetCondition(COND_SEE_NEMESIS);
|
|
}
|
|
else
|
|
{
|
|
//SetCondition(COND_SEE_HATE);
|
|
}
|
|
UpdateEnemyMemory(pSenseEnt,pSenseEnt->GetAbsOrigin());
|
|
break;
|
|
|
|
}
|
|
case D_FR:
|
|
UpdateEnemyMemory(pSenseEnt,pSenseEnt->GetAbsOrigin());
|
|
//SetCondition(COND_SEE_FEAR);
|
|
break;
|
|
case D_LI:
|
|
case D_NU:
|
|
break;
|
|
default:
|
|
DevWarning( 2, "%s can't assess %s\n", GetClassname(), pSenseEnt->GetClassname() );
|
|
break;
|
|
}
|
|
}
|
|
|
|
pSenseEnt = GetASWSenses()->GetNextSwarmSenseEntity( &iter );
|
|
}
|
|
}
|
|
|
|
// create our custom senses class
|
|
CAI_Senses* CASW_Alien::CreateSenses()
|
|
{
|
|
CAI_Senses *pSenses = new CASW_AI_Senses;
|
|
pSenses->SetOuter( this );
|
|
return pSenses;
|
|
}
|
|
|
|
CASW_AI_Senses* CASW_Alien::GetASWSenses()
|
|
{
|
|
return dynamic_cast<CASW_AI_Senses*>(GetSenses());
|
|
}
|
|
|
|
bool CASW_Alien::QuerySeeEntity( CBaseEntity *pEntity, bool bOnlyHateOrFearIfNPC )
|
|
{
|
|
if ( !BaseClass::QuerySeeEntity( pEntity, bOnlyHateOrFearIfNPC ) )
|
|
return false;
|
|
|
|
if ( pEntity && pEntity->Classify() == CLASS_ASW_BAIT )
|
|
{
|
|
return GetAbsOrigin().DistToSqr( pEntity->GetAbsOrigin() ) < 90000.0f; // only see bait within 300 units
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// wake the alien up when a marine gets nearby
|
|
void CASW_Alien::UpdateSleepState(bool bInPVS)
|
|
{
|
|
if ( GetSleepState() > AISS_AWAKE ) // alien is asleep, check for marines getting near to wake us up
|
|
{
|
|
// wake up if we have a script to run
|
|
if (m_hCine != NULL && GetSleepState() > AISS_AWAKE )
|
|
Wake();
|
|
|
|
bInPVS = MarineCanSee(384, 0.1f);
|
|
if ( bInPVS )
|
|
SetCondition( COND_IN_PVS );
|
|
else
|
|
ClearCondition( COND_IN_PVS );
|
|
|
|
if ( GetSleepState() > AISS_AWAKE && GetSleepState() != AISS_WAITING_FOR_INPUT )
|
|
{
|
|
if (bInPVS)
|
|
{
|
|
if (asw_draw_awake_ai.GetBool())
|
|
NDebugOverlay::EntityText(entindex(), 1, "WAKING", 60, 255,255,0,255);
|
|
Wake();
|
|
}
|
|
}
|
|
}
|
|
else // alien is awake, check for going back to ZZZ again :)
|
|
{
|
|
bool bHasOrders = (m_AlienOrders != AOT_None);
|
|
|
|
// Don't let an NPC sleep if they're running a script!
|
|
if( !ShouldAlwaysThink() && !bHasOrders && !IsInAScript() && m_NPCState != NPC_STATE_SCRIPT )
|
|
{
|
|
if( m_fLastSleepCheckTime < gpGlobals->curtime + ASW_ALIEN_SLEEP_CHECK_INTERVAL )
|
|
{
|
|
//if (!GetEnemy() && !MarineNearby(1024.0f) )
|
|
if (!GetEnemy() && !MarineCanSee(384, 2.0f) )
|
|
{
|
|
SetSleepState( AISS_WAITING_FOR_PVS );
|
|
|
|
if (asw_draw_awake_ai.GetBool())
|
|
NDebugOverlay::EntityText(entindex(), 1, "SLEEPING", 600, 255,255,0,255);
|
|
|
|
Sleep();
|
|
if (m_bVisibleWhenAsleep)
|
|
RemoveEffects( EF_NODRAW );
|
|
}
|
|
m_fLastSleepCheckTime = gpGlobals->curtime;
|
|
}
|
|
}
|
|
}
|
|
if ( GetSleepState() == AISS_AWAKE )
|
|
{
|
|
if ( !m_bRegisteredAsAwake )
|
|
{
|
|
ASWSpawnManager()->OnAlienWokeUp( this );
|
|
m_bRegisteredAsAwake = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_bRegisteredAsAwake )
|
|
{
|
|
ASWSpawnManager()->OnAlienSleeping( this );
|
|
m_bRegisteredAsAwake = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::UpdateOnRemove()
|
|
{
|
|
if ( m_bRegisteredAsAwake )
|
|
{
|
|
m_bRegisteredAsAwake = false;
|
|
ASWSpawnManager()->OnAlienSleeping( this );
|
|
}
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
void CASW_Alien::SetDistSwarmSense( float flDistSense )
|
|
{
|
|
if (!GetASWSenses())
|
|
return;
|
|
GetASWSenses()->SetDistSwarmSense( flDistSense );
|
|
}
|
|
|
|
void CASW_Alien::NPCInit()
|
|
{
|
|
BaseClass::NPCInit();
|
|
|
|
// set default alien swarm sight/sense distances
|
|
SetDistSwarmSense(576.0f);
|
|
SetDistLook( 768.0f );
|
|
m_flDistTooFar = 1500.0f; // seems this is used as an early out for checking attack conditions or not, also for LOS?
|
|
|
|
// if flagged, alien can see twice as far
|
|
if ( HasSpawnFlags( SF_NPC_LONG_RANGE ) )
|
|
{
|
|
SetDistSwarmSense(1152.0f);
|
|
SetDistLook( 1536.0f );
|
|
m_flDistTooFar = 2000.0f;
|
|
}
|
|
SetCollisionBounds( GetHullMins(), GetHullMaxs() );
|
|
|
|
CASW_GameStats.Event_AlienSpawned( this );
|
|
|
|
m_LagCompensation.Init(this);
|
|
}
|
|
|
|
void CASW_Alien::CallBehaviorThink()
|
|
{
|
|
CAI_ASW_Behavior *pCurrent = GetPrimaryASWBehavior();
|
|
if ( pCurrent )
|
|
{
|
|
pCurrent->BehaviorThink();
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::NPCThink( void )
|
|
{
|
|
BaseClass::NPCThink();
|
|
|
|
if ( asw_springcol.GetBool() && CanBePushedAway() )
|
|
{
|
|
PerformPushaway();
|
|
}
|
|
// stop electro stunning if we're slowed
|
|
if ( m_bElectroStunned && m_lifeState != LIFE_DYING )
|
|
{
|
|
if ( m_fHurtSlowMoveTime < gpGlobals->curtime )
|
|
{
|
|
m_bElectroStunned = false;
|
|
}
|
|
else
|
|
{
|
|
if ( gpGlobals->curtime >= m_fNextStunSound )
|
|
{
|
|
m_fNextStunSound = gpGlobals->curtime + RandomFloat( 0.2f, 0.5f );
|
|
|
|
EmitSound( "ASW_Tesla_Laser.Damage" );
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gpGlobals->maxClients > 1)
|
|
m_LagCompensation.StorePositionHistory();
|
|
// Update range attack
|
|
if ( m_nVolleyType >= 0 )
|
|
UpdateRangedAttack();
|
|
|
|
UpdateThawRate();
|
|
|
|
m_flLastThinkTime = gpGlobals->curtime;
|
|
}
|
|
|
|
bool CASW_Alien::MarineNearby(float radius, bool bCheck3D)
|
|
{
|
|
// find the closest marine
|
|
CASW_Game_Resource *pGameResource = ASWGameResource();
|
|
if (!pGameResource)
|
|
return false;
|
|
|
|
for (int i=0;i<pGameResource->GetMaxMarineResources();i++)
|
|
{
|
|
CASW_Marine_Resource* pMarineResource = pGameResource->GetMarineResource(i);
|
|
if (!pMarineResource)
|
|
continue;
|
|
|
|
CASW_Marine* pMarine = pMarineResource->GetMarineEntity();
|
|
if (!pMarine)
|
|
continue;
|
|
|
|
Vector diff = pMarine->GetAbsOrigin() - GetAbsOrigin();
|
|
float dist = bCheck3D ? diff.Length() : diff.Length2D();
|
|
|
|
if (dist < radius)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CBaseEntity *CASW_Alien::CheckTraceHullAttack( float flDist, const Vector &mins, const Vector &maxs, float flDamage, int iDmgType, float forceScale, bool bDamageAnyNPC )
|
|
{
|
|
// If only a length is given assume we want to trace in our facing direction
|
|
Vector forward;
|
|
AngleVectors( GetAbsAngles(), &forward );
|
|
Vector vStart = GetAbsOrigin();
|
|
|
|
// The ideal place to start the trace is in the center of the attacker's bounding box.
|
|
// however, we need to make sure there's enough clearance. Some of the smaller monsters aren't
|
|
// as big as the hull we try to trace with. (SJB)
|
|
float flVerticalOffset = WorldAlignSize().z * 0.5;
|
|
|
|
if( flVerticalOffset < maxs.z )
|
|
{
|
|
// There isn't enough room to trace this hull, it's going to drag the ground.
|
|
// so make the vertical offset just enough to clear the ground.
|
|
flVerticalOffset = maxs.z + 1.0;
|
|
}
|
|
|
|
vStart.z += flVerticalOffset;
|
|
Vector vEnd = vStart + (forward * flDist );
|
|
return CheckTraceHullAttack( vStart, vEnd, mins, maxs, flDamage, iDmgType, forceScale, bDamageAnyNPC );
|
|
}
|
|
|
|
// alien has been hit by a melee attack
|
|
void CASW_Alien::MeleeBleed(CTakeDamageInfo* info)
|
|
{
|
|
if ( info->GetDamage() >= 1.0 && !(info->GetDamageType() & DMG_SHOCK )
|
|
&& !(info->GetDamageType() & DMG_BURN ))
|
|
{
|
|
Vector vecDir = -info->GetDamageForce();
|
|
vecDir.NormalizeInPlace();
|
|
UTIL_ASW_DroneBleed( info->GetDamagePosition() + m_LagCompensation.GetLagCompensationOffset(), vecDir, 4 );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Freezes this NPC in place for a period of time.
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::Freeze( float flFreezeAmount, CBaseEntity *pFreezer, Ray_t *pFreezeRay )
|
|
{
|
|
if ( flFreezeAmount <= 0.0f )
|
|
{
|
|
SetCondition(COND_NPC_FREEZE);
|
|
SetMoveType(MOVETYPE_NONE);
|
|
SetGravity(0);
|
|
SetLocalAngularVelocity(vec3_angle);
|
|
SetAbsVelocity( vec3_origin );
|
|
return;
|
|
}
|
|
|
|
if ( flFreezeAmount > 1.0f )
|
|
{
|
|
float flFreezeDuration = flFreezeAmount - 1.0f;
|
|
|
|
// if freezing permanently, then reduce freeze duration by freeze resistance
|
|
flFreezeDuration *= ( 1.0f - m_flFreezeResistance );
|
|
|
|
BaseClass::Freeze( 1.0f, pFreezer, pFreezeRay ); // make alien fully frozen
|
|
|
|
m_flFrozenTime = gpGlobals->curtime + flFreezeDuration;
|
|
}
|
|
else
|
|
{
|
|
// if doing a partial freeze, then freeze resistance reduces that
|
|
flFreezeAmount *= ( 1.0f - m_flFreezeResistance );
|
|
|
|
BaseClass::Freeze( flFreezeAmount, pFreezer, pFreezeRay );
|
|
}
|
|
|
|
UpdateThawRate();
|
|
}
|
|
|
|
bool CASW_Alien::ShouldBecomeStatue()
|
|
{
|
|
// Prevents parent classes from turning this NPC into a statue
|
|
// We handle that ourselves if the alien dies while iced up
|
|
return false;
|
|
}
|
|
|
|
void CASW_Alien::UpdateThawRate()
|
|
{
|
|
if ( m_flFrozenTime > gpGlobals->curtime )
|
|
{
|
|
m_flFrozenThawRate = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
m_flFrozenThawRate = m_flBaseThawRate * (1.5f - m_flFrozen);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose : start and end trace position, amount
|
|
// of damage to do, and damage type. Returns a pointer to
|
|
// the damaged entity in case the NPC wishes to do
|
|
// other stuff to the victim (punchangle, etc)
|
|
//
|
|
// Used for many contact-range melee attacks. Bites, claws, etc.
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
CBaseEntity *CASW_Alien::CheckTraceHullAttack( const Vector &vStart, const Vector &vEnd, const Vector &mins, const Vector &maxs, float flDamage, int iDmgType, float flForceScale, bool bDamageAnyNPC )
|
|
{
|
|
// Handy debuging tool to visualize HullAttack trace
|
|
if ( ai_show_hull_attacks.GetBool() )
|
|
{
|
|
float length = (vEnd - vStart ).Length();
|
|
Vector direction = (vEnd - vStart );
|
|
VectorNormalize( direction );
|
|
Vector hullMaxs = maxs;
|
|
hullMaxs.x = length + hullMaxs.x;
|
|
NDebugOverlay::BoxDirection(vStart, mins, hullMaxs, direction, 100,255,255,20,1.0);
|
|
NDebugOverlay::BoxDirection(vStart, mins, maxs, direction, 255,0,0,20,1.0);
|
|
}
|
|
|
|
CTakeDamageInfo dmgInfo( this, this, flDamage, DMG_SLASH );
|
|
|
|
CASW_Trace_Filter_Melee traceFilter( this, COLLISION_GROUP_NONE, this, bDamageAnyNPC );
|
|
|
|
Ray_t ray;
|
|
ray.Init( vStart, vEnd, mins, maxs );
|
|
|
|
trace_t tr;
|
|
enginetrace->TraceRay( ray, MASK_SHOT_HULL, &traceFilter, &tr );
|
|
|
|
Vector vecAttackerCenter = WorldSpaceCenter();
|
|
|
|
for ( int i = 0; i < traceFilter.m_nNumHits; i++ )
|
|
{
|
|
trace_t *tr = &traceFilter.m_HitTraces[i];
|
|
CBaseEntity *pHitEntity = tr->m_pEnt;
|
|
|
|
Vector attackDir = pHitEntity->WorldSpaceCenter() - vecAttackerCenter;
|
|
VectorNormalize( attackDir );
|
|
CalculateMeleeDamageForce( &dmgInfo, attackDir, vecAttackerCenter, flForceScale );
|
|
tr->m_pEnt->DispatchTraceAttack( dmgInfo, tr->endpos - vecAttackerCenter, tr );
|
|
#ifdef GAME_DLL
|
|
ApplyMultiDamage();
|
|
#endif
|
|
}
|
|
|
|
CBaseEntity *pEntity = traceFilter.m_pHit;
|
|
|
|
return pEntity;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// NOTE: I removed the ON_GROUND check for Alien Swarm. Do we need to put it
|
|
// back?
|
|
//-----------------------------------------------------------------------------
|
|
int CASW_Alien::MeleeAttack1Conditions( float flDot, float flDist )
|
|
{
|
|
// Should we even check this condition?
|
|
if ( !meleeAttack1.m_bCheck )
|
|
return COND_NONE;
|
|
|
|
// Check range.
|
|
if ( flDist < meleeAttack1.m_flMinDist )
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
|
|
if ( flDist > meleeAttack1.m_flMaxDist )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
// Check facing.
|
|
if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
|
|
{
|
|
if ( flDot < meleeAttack1.m_flDotAngle )
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
|
|
return COND_CAN_MELEE_ATTACK1;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CASW_Alien::MeleeAttack2Conditions( float flDot, float flDist )
|
|
{
|
|
// Should we even check this condition?
|
|
if ( !meleeAttack2.m_bCheck )
|
|
return COND_NONE;
|
|
|
|
// Check ranges.
|
|
if ( flDist < meleeAttack2.m_flMinDist )
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
|
|
if ( flDist > meleeAttack2.m_flMaxDist )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
// Check facing.
|
|
if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
|
|
{
|
|
if ( flDot < meleeAttack2.m_flDotAngle )
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
|
|
return COND_CAN_MELEE_ATTACK2;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CASW_Alien::RangeAttack1Conditions ( float flDot, float flDist )
|
|
{
|
|
// Should we even check this condition?
|
|
if ( !rangeAttack1.m_bCheck )
|
|
return COND_NONE;
|
|
|
|
// Check ranges.
|
|
if ( flDist < rangeAttack1.m_flMinDist )
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
|
|
if ( flDist > rangeAttack1.m_flMaxDist )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
// Check facing.
|
|
if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
|
|
{
|
|
if ( flDot < rangeAttack1.m_flDotAngle )
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
|
|
return COND_CAN_RANGE_ATTACK1;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CASW_Alien::RangeAttack2Conditions ( float flDot, float flDist )
|
|
{
|
|
// Should we even check this condition?
|
|
if ( !rangeAttack2.m_bCheck )
|
|
return COND_NONE;
|
|
|
|
// Check ranges.
|
|
if ( flDist < rangeAttack2.m_flMinDist )
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
|
|
if ( flDist > rangeAttack2.m_flMaxDist )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
// Check facing.
|
|
if ( meleeAttack1.m_flDotAngle != COMBAT_COND_NO_FACING_CHECK )
|
|
{
|
|
if ( flDot < rangeAttack2.m_flDotAngle )
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
|
|
return COND_CAN_RANGE_ATTACK2;
|
|
}
|
|
|
|
// give our aliens 360 degree vision
|
|
bool CASW_Alien::FInViewCone( const Vector &vecSpot )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// always gib
|
|
bool CASW_Alien::ShouldGib( const CTakeDamageInfo &info )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// player (and player controlled marines) always avoid drones
|
|
bool CASW_Alien::ShouldPlayerAvoid( void )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// catching on fire
|
|
int CASW_Alien::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
|
{
|
|
int result = 0;
|
|
|
|
// scale burning damage up
|
|
//if (dynamic_cast<CEntityFlame*>(info.GetAttacker()))
|
|
if (dynamic_cast<CASW_Burning*>(info.GetInflictor()))
|
|
{
|
|
CTakeDamageInfo newDamage = info;
|
|
newDamage.ScaleDamage(asw_fire_alien_damage_scale.GetFloat());
|
|
if (asw_debug_alien_damage.GetBool())
|
|
{
|
|
Msg("%d %s hurt by %f dmg (scaled up by asw_fire_alien_damage_scale)\n", entindex(), GetClassname(), newDamage.GetDamage());
|
|
}
|
|
result = BaseClass::OnTakeDamage_Alive(newDamage);
|
|
}
|
|
else
|
|
{
|
|
if (asw_debug_alien_damage.GetBool())
|
|
{
|
|
Msg("%d %s hurt by %f dmg\n", entindex(), GetClassname(), info.GetDamage());
|
|
}
|
|
result = BaseClass::OnTakeDamage_Alive(info);
|
|
}
|
|
|
|
// if we take fire damage, catch on fire
|
|
if ( result > 0 && ( info.GetDamageType() & DMG_BURN ) )
|
|
{
|
|
ASW_Ignite( asw_alien_burn_duration.GetFloat(), 0, info.GetAttacker(), info.GetWeapon() );
|
|
}
|
|
|
|
// make the alien move slower for 0.5 seconds
|
|
if (info.GetDamageType() & DMG_SHOCK)
|
|
{
|
|
ElectroStun( asw_stun_grenade_time.GetFloat() );
|
|
|
|
m_fNoDamageDecal = true;
|
|
}
|
|
else
|
|
{
|
|
if (m_fHurtSlowMoveTime < gpGlobals->curtime + 0.5f)
|
|
m_fHurtSlowMoveTime = gpGlobals->curtime + 0.5f;
|
|
}
|
|
|
|
CASW_Marine* pMarine = NULL;
|
|
if ( info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
|
|
{
|
|
pMarine = static_cast<CASW_Marine*>( info.GetAttacker() );
|
|
}
|
|
if (pMarine)
|
|
pMarine->HurtAlien(this, info);
|
|
|
|
// Notify gamestats of the damage
|
|
CASW_GameStats.Event_AlienTookDamage( this, info );
|
|
|
|
if ( m_RecentDamage.Count() == ASW_NUM_RECENT_DAMAGE )
|
|
{
|
|
m_RecentDamage.RemoveAtHead();
|
|
}
|
|
m_RecentDamage.Insert( info );
|
|
if ( m_pFlinchBehavior )
|
|
{
|
|
m_pFlinchBehavior->OnOuterTakeDamage( info );
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ===================
|
|
// schedule/task stuff
|
|
// ====================
|
|
|
|
void CASW_Alien::StartTask( const Task_t *pTask )
|
|
{
|
|
//int task = pTask->iTask;
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_BURROW_WAIT:
|
|
if ( pTask->flTaskData == 1.0f )
|
|
{
|
|
//Set our next burrow time
|
|
if (m_flBurrowTime == 0)
|
|
m_flBurrowTime = gpGlobals->curtime;
|
|
else
|
|
m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 1, 6);
|
|
}
|
|
break;
|
|
|
|
case TASK_UNBURROW:
|
|
Unburrow();
|
|
break;
|
|
case TASK_CHECK_FOR_UNBORROW:
|
|
//if ( ValidBurrowPoint( GetAbsOrigin() ) )
|
|
{
|
|
m_spawnflags &= ~SF_NPC_GAG;
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
case TASK_SET_UNBURROW_IDLE_ACTIVITY:
|
|
{
|
|
SetIdealActivity( m_UnburrowIdleActivity );
|
|
}
|
|
break;
|
|
case TASK_ASW_WAIT_FOR_ORDER_MOVE:
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
|
|
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->ClearGoal(); // Clear residual state
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
}
|
|
}
|
|
break;
|
|
case TASK_ASW_ORDER_RETRY_WAIT:
|
|
//Msg("TASK_ASW_ORDER_RETRY_WAIT %d tries. doorblocked=%d\n", m_iNumASWOrderRetries, m_bBlockedByOpeningDoor);
|
|
m_iNumASWOrderRetries++;
|
|
if (m_iNumASWOrderRetries > 5)
|
|
{
|
|
TaskFail("Failed to find route to order target\n");
|
|
}
|
|
else
|
|
{
|
|
SetWait(MIN(float(m_iNumASWOrderRetries) / 2.0f, 12.0f)); // wait a bit longer in between each try
|
|
}
|
|
break;
|
|
case TASK_ASW_REPORT_BLOCKING_ENT:
|
|
Msg("Last blocking ent was %d:%s\n",
|
|
GetNavigator()->GetBlockingEntity(),
|
|
GetNavigator()->GetBlockingEntity() ? GetNavigator()->GetBlockingEntity()->GetClassname() : "Unknown");
|
|
TaskComplete();
|
|
break;
|
|
case TASK_ASW_ALIEN_ZIGZAG:
|
|
m_bPerformingZigZag = false;
|
|
break;
|
|
// override the default TASK_GET_PATH_TO_ENEMY to make our alien run at the end of the path
|
|
case TASK_GET_PATH_TO_ENEMY:
|
|
{
|
|
if (IsUnreachable(GetEnemy()))
|
|
{
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
return;
|
|
}
|
|
|
|
CBaseEntity *pEnemy = GetEnemy();
|
|
|
|
if ( pEnemy == NULL )
|
|
{
|
|
TaskFail(FAIL_NO_ENEMY);
|
|
return;
|
|
}
|
|
|
|
if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) )
|
|
{
|
|
if (m_bRunAtChasingPathEnds)
|
|
{
|
|
//Msg("Setting arrival speed to %f\n", GetIdealSpeed());
|
|
//GetNavigator()->SetArrivalSpeed(GetIdealSpeed()); // asw test
|
|
GetNavigator()->SetArrivalSpeed(300.0f); // asw test
|
|
}
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
DevWarning( 2, "GetPathToEnemy failed!!\n" );
|
|
RememberUnreachable(GetEnemy());
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
return;
|
|
}
|
|
case TASK_GET_PATH_TO_ENEMY_LKP:
|
|
{
|
|
CBaseEntity *pEnemy = GetEnemy();
|
|
if (!pEnemy || IsUnreachable(pEnemy))
|
|
{
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
return;
|
|
}
|
|
AI_NavGoal_t goal( GetEnemyLKP() );
|
|
|
|
TranslateNavGoal( pEnemy, goal.dest );
|
|
|
|
if ( GetNavigator()->SetGoal( goal, AIN_CLEAR_TARGET ) )
|
|
{
|
|
if (m_bRunAtChasingPathEnds)
|
|
{
|
|
//Msg("Setting arrival speed to %f\n", GetIdealSpeed());
|
|
//GetNavigator()->SetArrivalSpeed(GetIdealSpeed()); // asw test
|
|
GetNavigator()->SetArrivalSpeed(300.0f); // asw test
|
|
}
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// no way to get there =(
|
|
DevWarning( 2, "GetPathToEnemyLKP failed!!\n" );
|
|
RememberUnreachable(GetEnemy());
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_SPREAD_THEN_HIBERNATE:
|
|
{
|
|
// This task really uses 2 parameters, so we have to extract
|
|
// them from a single integer. To send both parameters, the
|
|
// formula is MIN_DIST * 10000 + MAX_DIST
|
|
{
|
|
int iMinDist, iMaxDist;
|
|
|
|
iMinDist = 90;
|
|
iMaxDist = 200;
|
|
|
|
// try to find a simple vector goal
|
|
if ( GetNavigator()->SetWanderGoal( iMinDist, iMaxDist ) )
|
|
TaskComplete();
|
|
else
|
|
{
|
|
// if we couldn't go for a full path
|
|
if ( GetNavigator()->SetRandomGoal( 150.0f ) )
|
|
TaskComplete();
|
|
else
|
|
TaskFail(FAIL_NO_REACHABLE_NODE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case TASK_ASW_BUILD_PATH_TO_ORDER:
|
|
{
|
|
CBaseEntity *pGoalEnt = m_AlienOrderObject;
|
|
m_bFailedMoveTo = false;
|
|
if (pGoalEnt)
|
|
{
|
|
// if our target is a path corner, do normal AI following of that route
|
|
bool bIsFlying = (GetMoveType() == MOVETYPE_FLY) || (GetMoveType() == MOVETYPE_FLYGRAVITY);
|
|
if (pGoalEnt->ClassMatches("path_corner"))
|
|
{
|
|
SetGoalEnt(pGoalEnt);
|
|
|
|
AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pGoalEnt->GetAbsOrigin(),
|
|
bIsFlying ? ACT_FLY : ACT_WALK,
|
|
AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
|
|
|
|
SetState( NPC_STATE_IDLE );
|
|
if ( !GetNavigator()->SetGoal( goal ) )
|
|
{
|
|
DevWarning( 2, "Can't Create Route!\n" );
|
|
m_bFailedMoveTo = true;
|
|
}
|
|
else
|
|
{
|
|
TaskComplete();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// HACKHACK: Call through TranslateNavGoal to fixup this goal position
|
|
// UNDONE: Remove this and have NPCs that need this functionality fix up paths in the
|
|
// movement system instead of when they are specified.
|
|
AI_NavGoal_t goal(pGoalEnt->GetAbsOrigin(), bIsFlying ? ACT_FLY : ACT_WALK, AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
|
|
TranslateNavGoal( pGoalEnt, goal.dest );
|
|
|
|
if (!GetNavigator()->SetGoal( goal ))
|
|
{
|
|
DevWarning( 2, "Can't Create Route!\n" );
|
|
m_bFailedMoveTo = true;
|
|
}
|
|
else
|
|
{
|
|
TaskComplete();
|
|
}
|
|
}
|
|
}
|
|
if (!pGoalEnt || m_bFailedMoveTo)
|
|
{
|
|
//m_AlienOrders = AOT_SpreadThenHibernate; // make sure our orders are set correctly for this action (may be incorrect if a MoveTo order fails)
|
|
if (pTask->flTaskData <= 0) // check the taskdata to see if we should do a short spread movement when failing to build a path
|
|
{
|
|
// random nearby position
|
|
if ( GetNavigator()->SetWanderGoal( 90, 200 ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
else
|
|
{
|
|
// if we couldn't go for a full path
|
|
if ( GetNavigator()->SetRandomGoal( 150.0f ) )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TaskFail(FAIL_NO_ROUTE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
BaseClass::StartTask(pTask);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool CASW_Alien::IsCurTaskContinuousMove()
|
|
{
|
|
const Task_t* pTask = GetTask();
|
|
if( !pTask || pTask->iTask == TASK_ASW_WAIT_FOR_ORDER_MOVE )
|
|
return true;
|
|
|
|
return BaseClass::IsCurTaskContinuousMove();
|
|
}
|
|
|
|
void CASW_Alien::RunTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
case TASK_BURROW_WAIT:
|
|
//See if enough time has passed
|
|
if ( m_flBurrowTime < gpGlobals->curtime )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
case TASK_ASW_WAIT_FOR_ORDER_MOVE:
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
|
|
bool fTimeExpired = ( pTask->flTaskData != 0 && pTask->flTaskData < gpGlobals->curtime - GetTimeTaskStarted() );
|
|
|
|
if (fTimeExpired || GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
TaskComplete();
|
|
GetNavigator()->StopMoving(); // Stop moving
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else
|
|
{
|
|
// Check validity of goal type
|
|
ValidateNavGoal();
|
|
|
|
if ( m_AlienOrderObject.Get() )
|
|
{
|
|
const Vector &vecGoalPos = m_AlienOrderObject->GetAbsOrigin();
|
|
if ( ( GetNavigator()->GetGoalPos() - vecGoalPos ).LengthSqr() > Square( 60 ) )
|
|
{
|
|
if ( GetNavigator()->GetNavType() != NAV_JUMP )
|
|
{
|
|
if ( !GetNavigator()->UpdateGoalPos( vecGoalPos ) )
|
|
{
|
|
TaskFail( FAIL_NO_ROUTE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TASK_CHECK_FOR_UNBORROW:
|
|
//Must wait for our next check time
|
|
if ( m_flBurrowTime > gpGlobals->curtime )
|
|
return;
|
|
|
|
//See if we can pop up
|
|
// todo: see if a marine is near?
|
|
//if ( ValidBurrowPoint( GetAbsOrigin() ) )
|
|
{
|
|
m_spawnflags &= ~SF_NPC_GAG;
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
|
|
TaskComplete();
|
|
return;
|
|
}
|
|
|
|
//Try again in a couple of seconds
|
|
m_flBurrowTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 1.0f );
|
|
break;
|
|
case TASK_UNBURROW:
|
|
{
|
|
AutoMovement();
|
|
if ( IsActivityFinished() )
|
|
{
|
|
RemoveFlag( FL_FLY );
|
|
CheckForBlockingTraps();
|
|
SetCollisionGroup( m_nAlienCollisionGroup );
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_SET_UNBURROW_IDLE_ACTIVITY:
|
|
{
|
|
AutoMovement();
|
|
if ( IsActivityFinished() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_ORDER_RETRY_WAIT:
|
|
{
|
|
if ( IsWaitFinished() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_ASW_ALIEN_ZIGZAG:
|
|
{
|
|
if ( IsMovementFrozen() )
|
|
{
|
|
TaskFail(FAIL_FROZEN);
|
|
break;
|
|
}
|
|
|
|
if (GetNavigator()->GetGoalType() == GOALTYPE_NONE)
|
|
{
|
|
m_bPerformingZigZag = false;
|
|
TaskComplete();
|
|
GetNavigator()->StopMoving(); // Stop moving
|
|
}
|
|
else if (!GetNavigator()->IsGoalActive())
|
|
{
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
}
|
|
else if (ValidateNavGoal())
|
|
{
|
|
SetIdealActivity( GetNavigator()->GetMovementActivity() );
|
|
|
|
// if we've just spotted our enemy, recompute our path, to try and get a single local path to add a zigzag to
|
|
/*
|
|
if (HasCondition( COND_SEE_ENEMY ) && !m_bLastSeeEnemy)
|
|
{
|
|
if ( !GetNavigator()->RefindPathToGoal( false ) )
|
|
{
|
|
TaskFail( FAIL_NO_ROUTE );
|
|
return;
|
|
}
|
|
}
|
|
m_bLastSeeEnemy = HasCondition( COND_SEE_ENEMY );
|
|
|
|
if (GetNavigator()->CurWaypointIsGoal())
|
|
{
|
|
m_bPerformingZigZag = false;
|
|
AddZigZagToPath();
|
|
}*/
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
BaseClass::RunTask( pTask );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#define ZIG_ZAG_SIZE 1500 // was 3600
|
|
|
|
// don't change our chase goal while we're on a zigzag detour
|
|
bool CASW_Alien::ShouldUpdateEnemyPos()
|
|
{
|
|
if (GetTask() && GetTask()->iTask == TASK_ASW_ALIEN_ZIGZAG
|
|
//&& (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR) )
|
|
&& m_bPerformingZigZag)
|
|
return false;
|
|
|
|
// don't repath if we're heading to a point that shouldn't be simplified
|
|
if (asw_drone_zig_zagging.GetBool() && GetNavigator()->GetCurWaypointFlags() & bits_WP_DONT_SIMPLIFY)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Msg("updating enemy pos %f\n", gpGlobals->curtime);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
float CASW_Alien::GetGoalRepathTolerance( CBaseEntity *pGoalEnt, GoalType_t type, const Vector &curGoal, const Vector &curTargetPos )
|
|
{
|
|
if (!ShouldUpdateEnemyPos())
|
|
return 10000.0f; // allow huge tolerance to prevent repath
|
|
|
|
|
|
|
|
//return BaseClass::GetGoalRepathTolerance(pGoalEnt, type, curGoal, curTargetPos);
|
|
float distToGoal = ( GetAbsOrigin() - curTargetPos ).Length() - GetNavigator()->GetArrivalDistance();
|
|
float distMoved1Sec = GetSmoothedVelocity().Length();
|
|
float result = 120; // FIXME: why 120?
|
|
|
|
if (distToGoal <= 100)
|
|
return GetMotor()->MinStoppingDist(0.0f);
|
|
|
|
if (distMoved1Sec > 0.0)
|
|
{
|
|
float t = distToGoal / distMoved1Sec;
|
|
|
|
result = clamp( 120 * t, 0, 120 );
|
|
// Msg("t %.2f : d %.0f (%.0f)\n", t, result, distMoved1Sec );
|
|
}
|
|
|
|
if ( !pGoalEnt->IsPlayer() )
|
|
result *= 1.20;
|
|
|
|
return result;
|
|
}
|
|
|
|
void CASW_Alien::AddZigZagToPath(void)
|
|
{
|
|
// If already on a detour don't add a zigzag
|
|
if (GetNavigator()->GetCurWaypointFlags() & bits_WP_TO_DETOUR)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If enemy isn't facing me or occluded, don't add a zigzag
|
|
//if (HasCondition(COND_ENEMY_OCCLUDED) || !HasCondition ( COND_ENEMY_FACING_ME ))
|
|
//{
|
|
//return;
|
|
//}
|
|
|
|
Vector waypointPos = GetNavigator()->GetCurWaypointPos();
|
|
Vector waypointDir = (waypointPos - GetAbsOrigin());
|
|
|
|
// If the distance to the next node is greater than ZIG_ZAG_SIZE
|
|
// then add a random zig/zag to the path
|
|
if (waypointDir.LengthSqr() > ZIG_ZAG_SIZE)
|
|
{
|
|
// Pick a random distance for the zigzag (less that sqrt(ZIG_ZAG_SIZE)
|
|
float fZigZigDistance = random->RandomFloat( 100, 200 ); //30, 60 );
|
|
int iZigZagSide = random->RandomInt(0,1);
|
|
|
|
|
|
Msg("adding a zig zag of %f units\n", fZigZigDistance);
|
|
|
|
// Get me a vector orthogonal to the direction of motion
|
|
VectorNormalize( waypointDir );
|
|
Vector vDirUp(0,0,1);
|
|
Vector vDir;
|
|
CrossProduct( waypointDir, vDirUp, vDir);
|
|
|
|
// Pick a random direction (left/right) for the zigzag
|
|
if (iZigZagSide)
|
|
{
|
|
vDir = -1 * vDir;
|
|
}
|
|
|
|
// Get zigzag position in direction of target waypoint
|
|
Vector zigZagPos = GetAbsOrigin() + waypointDir * 200;
|
|
|
|
// Now offset
|
|
zigZagPos = zigZagPos + (vDir * fZigZigDistance);
|
|
|
|
// Now make sure that we can still get to the zigzag position and the waypoint
|
|
AIMoveTrace_t moveTrace1, moveTrace2;
|
|
GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), zigZagPos, MASK_NPCSOLID, NULL, &moveTrace1);
|
|
GetMoveProbe()->MoveLimit( NAV_GROUND, zigZagPos, waypointPos, MASK_NPCSOLID, NULL, &moveTrace2);
|
|
if ( !IsMoveBlocked( moveTrace1 ) && !IsMoveBlocked( moveTrace2 ) )
|
|
{
|
|
m_bPerformingZigZag = true;
|
|
GetNavigator()->PrependWaypoint( zigZagPos, NAV_GROUND, bits_WP_TO_DETOUR );
|
|
}
|
|
}
|
|
}
|
|
|
|
int CASW_Alien::TranslateSchedule( int scheduleType )
|
|
{
|
|
if (scheduleType == SCHED_CHASE_ENEMY)
|
|
return SCHED_ASW_ALIEN_CHASE_ENEMY;
|
|
|
|
int i = BaseClass::TranslateSchedule(scheduleType);
|
|
|
|
if ( i == SCHED_MELEE_ATTACK1 )
|
|
{
|
|
if ( ShouldStopBeforeMeleeAttack() )
|
|
{
|
|
return SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1;
|
|
}
|
|
return SCHED_ASW_ALIEN_MELEE_ATTACK1;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
// pushes aliens out of each other
|
|
void CASW_Alien::PerformPushaway()
|
|
{
|
|
VPROF("CASW_Alien::PerformPushaway");
|
|
SetupPushawayVector();
|
|
|
|
#ifdef MOVEPROBE_PUSHAWAY
|
|
AIMoveTrace_t moveTrace;
|
|
unsigned testFlags = AITGM_IGNORE_FLOOR;
|
|
GetMoveProbe()->TestGroundMove( GetLocalOrigin(), GetAbsOrigin() + m_vecLastPush, GetAITraceMask(), testFlags, &moveTrace );
|
|
UTIL_SetOrigin( this, moveTrace.vEndPosition, true );
|
|
#else
|
|
// check if we can safely move through this push
|
|
Vector dest = GetAbsOrigin() + m_vecLastPush;
|
|
trace_t tr;
|
|
AI_TraceHull( GetAbsOrigin(), dest, WorldAlignMins(), WorldAlignMaxs(), GetAITraceMask(), this, GetCollisionGroup(), &tr );
|
|
if ( !tr.startsolid && (tr.fraction == 1.0) )
|
|
{
|
|
// all was clear, move into new position
|
|
UTIL_SetOrigin(this, dest);
|
|
}
|
|
#endif
|
|
|
|
// if we're close to stopping, zero our velocity
|
|
if ( GetMotor()->GetCurSpeed() < 1.0f )
|
|
{
|
|
GetMotor()->SetMoveVel( Vector( 0,0,0 ) );
|
|
}
|
|
// if we were pushed, slow us down a bit
|
|
else if ( m_bPushed )
|
|
{
|
|
float scale_vel = 1.0f - ( m_vecLastPush.Length2D() / asw_springcol_push_cap.GetFloat() );
|
|
GetMotor()->SetMoveVel( GetMotor()->GetCurVel() * scale_vel );
|
|
}
|
|
}
|
|
|
|
float CASW_Alien::GetSpringColRadius()
|
|
{
|
|
return asw_springcol_radius.GetFloat();
|
|
}
|
|
|
|
void CASW_Alien::SetupPushawayVector()
|
|
{
|
|
CASW_Alien *pOtherAlien;
|
|
Vector vecPush;
|
|
vecPush.Init();
|
|
bool m_bPushed = false;
|
|
float flSpringColRadius = GetSpringColRadius();
|
|
// go through all aliens
|
|
|
|
int iAliens = IAlienAutoList::AutoList().Count();
|
|
for ( int i = 0; i < iAliens; i++ )
|
|
{
|
|
pOtherAlien = static_cast< CASW_Alien* >( IAlienAutoList::AutoList()[ i ] );
|
|
if ( pOtherAlien != this && !( Classify() == CLASS_ASW_HARVESTER && pOtherAlien->Classify() != CLASS_ASW_PARASITE )
|
|
&& !( Classify() == CLASS_ASW_PARASITE && pOtherAlien->Classify() != CLASS_ASW_HARVESTER ) )
|
|
{
|
|
Vector diff = m_vecLastPushAwayOrigin - pOtherAlien->GetAbsOrigin();
|
|
float dist = diff.Length();
|
|
VectorNormalize( diff );
|
|
// TODO: Customize springcolradius per alien type
|
|
if ( dist < flSpringColRadius ) // if alien is too close, add a force from him to our push vector
|
|
{
|
|
dist -= flSpringColRadius * asw_springcol_core.GetFloat();
|
|
if (dist < 1)
|
|
dist = 1;
|
|
float push_amount = flSpringColRadius / dist;
|
|
vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat();
|
|
m_bPushed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// cap the push vector
|
|
float over_length = vecPush.Length() / asw_springcol_push_cap.GetFloat();
|
|
if (over_length > 1)
|
|
vecPush /= over_length;
|
|
|
|
// push out of players with equal force to total push from other drones
|
|
int iMaxMarines = ASWGameResource()->GetMaxMarineResources();
|
|
for ( int i = 0; i < iMaxMarines; i++ )
|
|
{
|
|
CASW_Marine_Resource *pMR = ASWGameResource()->GetMarineResource( i );
|
|
if ( !pMR )
|
|
continue;
|
|
|
|
CASW_Marine *pMarine = pMR->GetMarineEntity();
|
|
if ( !pMarine )
|
|
continue;
|
|
|
|
Vector diff = m_vecLastPushAwayOrigin - pMarine->GetAbsOrigin();
|
|
float dist = diff.Length();
|
|
VectorNormalize( diff );
|
|
if ( dist < flSpringColRadius ) // if drone is too close, add a force from him to our push vector
|
|
{
|
|
dist -= flSpringColRadius * asw_springcol_core.GetFloat();
|
|
if ( dist < 1 )
|
|
{
|
|
dist = 1;
|
|
}
|
|
float push_amount = flSpringColRadius / dist;
|
|
vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat();
|
|
m_bPushed = true;
|
|
}
|
|
}
|
|
// push away from tesla traps
|
|
int nTraps = g_aTeslaTraps.Count();
|
|
for ( int i = 0; i < nTraps; i++ )
|
|
{
|
|
Vector diff = m_vecLastPushAwayOrigin - g_aTeslaTraps[i]->GetAbsOrigin();
|
|
float dist = diff.Length();
|
|
VectorNormalize( diff );
|
|
if ( dist < flSpringColRadius ) // if drone is too close, add a force from him to our push vector
|
|
{
|
|
dist -= flSpringColRadius * asw_springcol_core.GetFloat();
|
|
if ( dist < 1 )
|
|
{
|
|
dist = 1;
|
|
}
|
|
float push_amount = flSpringColRadius / dist;
|
|
vecPush += push_amount * diff * asw_springcol_force_scale.GetFloat();
|
|
m_bPushed = true;
|
|
}
|
|
}
|
|
// cap the push vector
|
|
over_length = vecPush.Length() / asw_springcol_push_cap.GetFloat();
|
|
if ( over_length > 1 )
|
|
{
|
|
vecPush /= over_length;
|
|
}
|
|
|
|
// smooth the push vector
|
|
m_vecLastPush = (m_vecLastPush * 2.0f + vecPush) / 3.0f;
|
|
//Msg("%d Pushaway vector size %f\n", entindex(), m_vecLastPush.Length2D());
|
|
if ( asw_springcol_debug.GetInt() == -1 ||
|
|
asw_springcol_debug.GetInt() == entindex() )
|
|
{
|
|
float flYaw = UTIL_VecToYaw( m_vecLastPush );
|
|
NDebugOverlay::YawArrow( GetAbsOrigin() + Vector( 0, 0, 24 ), flYaw, 64, 8, 255, 255, 0, 0, true, 0.1f );
|
|
}
|
|
m_vecLastPushAwayOrigin = GetAbsOrigin();
|
|
}
|
|
|
|
bool CASW_Alien::CanBePushedAway()
|
|
{
|
|
// no pushing away while burrowed
|
|
if ( IsCurSchedule( SCHED_BURROW_WAIT, false ) || IsCurSchedule( SCHED_WAIT_FOR_CLEAR_UNBORROW, false )
|
|
|| IsCurSchedule( SCHED_BURROW_OUT, false ) )
|
|
return false;
|
|
|
|
// don't push away aliens that have low sight ('asleep')
|
|
return !IsCurSchedule( CAI_ASW_SleepBehavior::SCHED_SLEEP_UNBURROW );
|
|
}
|
|
|
|
AI_BEGIN_CUSTOM_NPC( asw_alien, CASW_Alien )
|
|
DECLARE_CONDITION( COND_ASW_BEGIN_COMBAT_STUN )
|
|
DECLARE_CONDITION( COND_ASW_FLINCH )
|
|
|
|
DECLARE_TASK( TASK_ASW_ALIEN_ZIGZAG )
|
|
DECLARE_TASK( TASK_ASW_SPREAD_THEN_HIBERNATE )
|
|
DECLARE_TASK( TASK_ASW_BUILD_PATH_TO_ORDER )
|
|
DECLARE_TASK( TASK_ASW_ORDER_RETRY_WAIT )
|
|
DECLARE_TASK( TASK_ASW_REPORT_BLOCKING_ENT )
|
|
DECLARE_TASK( TASK_UNBURROW )
|
|
DECLARE_TASK( TASK_CHECK_FOR_UNBORROW )
|
|
DECLARE_TASK( TASK_BURROW_WAIT )
|
|
DECLARE_TASK( TASK_SET_UNBURROW_IDLE_ACTIVITY )
|
|
DECLARE_TASK( TASK_ASW_WAIT_FOR_ORDER_MOVE )
|
|
|
|
DECLARE_ACTIVITY( ACT_MELEE_ATTACK1_HIT )
|
|
DECLARE_ACTIVITY( ACT_MELEE_ATTACK2_HIT )
|
|
DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_SMALL )
|
|
DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_MEDIUM )
|
|
DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_BIG )
|
|
DECLARE_ACTIVITY( ACT_ALIEN_FLINCH_GESTURE )
|
|
DECLARE_ACTIVITY( ACT_DEATH_FIRE )
|
|
DECLARE_ACTIVITY( ACT_DEATH_ELEC )
|
|
DECLARE_ACTIVITY( ACT_DIE_FANCY )
|
|
DECLARE_ACTIVITY( ACT_BURROW_OUT )
|
|
DECLARE_ACTIVITY( ACT_BURROW_IDLE )
|
|
|
|
DECLARE_ANIMEVENT( AE_ALIEN_MELEE_HIT )
|
|
|
|
//=========================================================
|
|
// > SCHED_ASW_ALIEN_CHASE_ENEMY
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_ALIEN_CHASE_ENEMY,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
|
|
//" TASK_SET_TOLERANCE_DISTANCE 24"
|
|
" TASK_GET_PATH_TO_ENEMY 300"
|
|
" TASK_RUN_PATH 0"
|
|
//" TASK_ASW_ALIEN_ZIGZAG 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" 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_HEAR_DANGER"
|
|
)
|
|
|
|
// Same as base melee attack1, minus the TASK_STOP_MOVING
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_ALIEN_MELEE_ATTACK1,
|
|
|
|
" Tasks"
|
|
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_ENEMY_OCCLUDED"
|
|
);
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_ALIEN_SLOW_MELEE_ATTACK1,
|
|
|
|
" Tasks"
|
|
|
|
" TASK_STOP_MOVING 1"
|
|
" TASK_WAIT 0.1"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
|
|
" TASK_MELEE_ATTACK1 1"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_ENEMY_OCCLUDED"
|
|
);
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_SPREAD_THEN_HIBERNATE,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_ASW_SPREAD_THEN_HIBERNATE 0"
|
|
" TASK_WALK_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_IDLE_STAND"
|
|
" "
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_ORDER_MOVE,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_RETRY_ORDERS"
|
|
" TASK_ASW_BUILD_PATH_TO_ORDER 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_ASW_WAIT_FOR_ORDER_MOVE 0" // 0 is spread if fail to build path
|
|
//" TASK_WAIT_PVS 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY" // in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SMELL"
|
|
" COND_PROVOKED"
|
|
" COND_HEAR_COMBAT"
|
|
" COND_HEAR_BULLET_IMPACT"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ASW_RETRY_ORDERS,
|
|
|
|
" Tasks"
|
|
//" TASK_ASW_REPORT_BLOCKING_ENT 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_SPREAD_THEN_HIBERNATE"
|
|
" TASK_ASW_ORDER_RETRY_WAIT 1" // this will fail the schedule if we've retried too many times
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ASW_RETRY_ORDERS"
|
|
" TASK_ASW_BUILD_PATH_TO_ORDER 1" // 1 is no spread if fail to build path
|
|
" TASK_WALK_PATH 9999"
|
|
" TASK_WAIT_FOR_MOVEMENT 0" // 0 is spread if fail to build path
|
|
" TASK_WAIT_PVS 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SEE_ENEMY" // in deference to scripted schedule where the enemy was slammed, thus no COND_NEW_ENEMY
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SMELL"
|
|
" COND_PROVOKED"
|
|
" COND_HEAR_COMBAT"
|
|
" COND_HEAR_BULLET_IMPACT"
|
|
)
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_BURROW_WAIT,
|
|
" Tasks"
|
|
" TASK_SET_UNBURROW_IDLE_ACTIVITY 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BURROW_WAIT"
|
|
" TASK_BURROW_WAIT 1"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_WAIT_FOR_CLEAR_UNBORROW"
|
|
""
|
|
" Interrupts"
|
|
" COND_TASK_FAILED"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_WAIT_FOR_CLEAR_UNBORROW,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BURROW_WAIT"
|
|
" TASK_CHECK_FOR_UNBORROW 1"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_BURROW_OUT"
|
|
""
|
|
" Interrupts"
|
|
" COND_TASK_FAILED"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_BURROW_OUT,
|
|
|
|
" Tasks"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_BURROW_WAIT"
|
|
" TASK_UNBURROW 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_TASK_FAILED"
|
|
)
|
|
|
|
AI_END_CUSTOM_NPC()
|
|
|
|
|
|
|
|
// Behaviour stuff to support act busy
|
|
bool CASW_Alien::CreateBehaviors()
|
|
{
|
|
AddBehavior( &m_ActBusyBehavior );
|
|
|
|
return BaseClass::CreateBehaviors();
|
|
}
|
|
|
|
int CASW_Alien::SelectAlienOrdersSchedule()
|
|
{
|
|
int schedule = 0;
|
|
// check if we have any alien orders to follow
|
|
switch (m_AlienOrders)
|
|
{
|
|
case AOT_MoveToIgnoringMarines:
|
|
{
|
|
IgnoreMarines(true);
|
|
schedule = SCHED_ASW_ORDER_MOVE;
|
|
}
|
|
break;
|
|
case AOT_SpreadThenHibernate:
|
|
case AOT_MoveTo:
|
|
{
|
|
IgnoreMarines(false);
|
|
schedule = SCHED_ASW_ORDER_MOVE;
|
|
}
|
|
break;
|
|
case AOT_MoveToNearestMarine:
|
|
{
|
|
float marine_distance;
|
|
// refresh our order target, in case the one passed in by the orders is no longer the nearest
|
|
m_AlienOrderObject = UTIL_ASW_NearestMarine( GetAbsOrigin(), marine_distance );
|
|
IgnoreMarines(false);
|
|
schedule = SCHED_ASW_ORDER_MOVE;
|
|
}
|
|
break;
|
|
case AOT_None:
|
|
default:
|
|
{
|
|
// nothing
|
|
}
|
|
}
|
|
return schedule;
|
|
}
|
|
|
|
int CASW_Alien::SelectSchedule( void )
|
|
{
|
|
// If we're supposed to be burrowed, stay there
|
|
if ( m_bStartBurrowed )
|
|
return SCHED_BURROW_WAIT;
|
|
|
|
// see if our alien orders want to schedule something
|
|
int order_schedule = SelectAlienOrdersSchedule();
|
|
if (order_schedule > 0)
|
|
{
|
|
//Msg("SelectSchedule picked alien orders\n");
|
|
return order_schedule;
|
|
}
|
|
|
|
if ( !BehaviorSelectSchedule() )
|
|
{
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
ConVar asw_drone_death_force_pitch( "asw_drone_death_force_pitch", "-10", FCVAR_CHEAT, "Tilt the angle of death forces on alien ragdolls" );
|
|
ConVar asw_drone_death_force( "asw_drone_death_force", "5", FCVAR_CHEAT, "Scale for alien death forces" );
|
|
|
|
#define DRONE_MASS 90.0f
|
|
|
|
Vector CASW_Alien::CalcDeathForceVector( const CTakeDamageInfo &info )
|
|
{
|
|
if ( asw_debug_alien_damage.GetBool() )
|
|
{
|
|
Msg( "Death force pre = %f\n", info.GetDamageForce().Length() );
|
|
}
|
|
|
|
if ( !( info.GetDamageType() & DMG_BLAST || info.GetDamageType() & DMG_BURN ) )
|
|
{
|
|
// normalize force for non-explosive weapons, as they each have different fire rates/forces
|
|
// TODO: Tilt force vector upwards a bit
|
|
|
|
float flDesiredForceScale = asw_drone_death_force.GetFloat() * 10000.0f;
|
|
float flMassScale = 1.0f;
|
|
if ( VPhysicsGetObject() )
|
|
{
|
|
flMassScale = 1.0f / ( VPhysicsGetObject()->GetMass() / DRONE_MASS ); // using drone's mass as a baseline
|
|
}
|
|
|
|
CBaseEntity *pForce = info.GetInflictor();
|
|
if ( !pForce )
|
|
{
|
|
pForce = info.GetAttacker();
|
|
}
|
|
Vector forceVector = vec3_origin;
|
|
|
|
if ( pForce )
|
|
{
|
|
forceVector = GetAbsOrigin() - pForce->GetAbsOrigin();
|
|
forceVector.NormalizeInPlace();
|
|
|
|
if ( asw_debug_alien_damage.GetBool() )
|
|
{
|
|
Msg( "Death force post = %f flDesiredForceScale=%f flMassScale=%f\n", (forceVector * flDesiredForceScale * flMassScale).Length(), flDesiredForceScale, flMassScale );
|
|
}
|
|
|
|
return forceVector * flDesiredForceScale * flMassScale;
|
|
}
|
|
else
|
|
{
|
|
forceVector.x = random->RandomFloat( -1.0f, 1.0f );
|
|
forceVector.y = random->RandomFloat( -1.0f, 1.0f );
|
|
forceVector.z = 0.0;
|
|
|
|
if ( asw_debug_alien_damage.GetBool() )
|
|
{
|
|
Msg( "Death force post = %f flDesiredForceScale=%f flMassScale=%f\n", (forceVector * flDesiredForceScale * flMassScale).Length(), flDesiredForceScale, flMassScale );
|
|
}
|
|
|
|
return forceVector * (flDesiredForceScale/4) * flMassScale;
|
|
}
|
|
}
|
|
return BaseClass::CalcDeathForceVector( info );
|
|
}
|
|
|
|
void CASW_Alien::BreakAlien( const CTakeDamageInfo &info )
|
|
{
|
|
Vector velocity;
|
|
velocity = CalcDeathForceVector( info )/150;
|
|
if ( velocity == vec3_origin )
|
|
velocity = Vector( RandomFloat( 1, 15 ), RandomFloat( 1, 15 ), 75.0f );
|
|
velocity.z *= 1.25;
|
|
velocity.z += 175.0;
|
|
AngularImpulse angVelocity = RandomAngularImpulse( -500.0f, 500.0f );
|
|
breakablepropparams_t params( GetAbsOrigin(), GetAbsAngles(), velocity, angVelocity );
|
|
params.impactEnergyScale = 1.0f;
|
|
params.defBurstScale = 100.0f;
|
|
params.defCollisionGroup = COLLISION_GROUP_DEBRIS;
|
|
PropBreakableCreateAll( GetModelIndex(), NULL, params, this, -1, true, true );
|
|
}
|
|
|
|
//=========================================================
|
|
// GetDeathActivity - determines the best type of death
|
|
// anim to play.
|
|
//=========================================================
|
|
Activity CASW_Alien::GetDeathActivity ( void )
|
|
{
|
|
Activity deathActivity;
|
|
deathActivity = ACT_DIESIMPLE;
|
|
|
|
if ( m_nDeathStyle == kDIE_FANCY )
|
|
{
|
|
if ( m_bOnFire )
|
|
deathActivity = ( Activity ) ACT_DEATH_FIRE;
|
|
else if ( m_bElectroStunned )
|
|
deathActivity = ( Activity ) ACT_DEATH_ELEC;
|
|
else
|
|
deathActivity = ( Activity ) ACT_DIE_FANCY;
|
|
}
|
|
|
|
//BaseClass::GetDeathActivity();
|
|
return deathActivity;
|
|
}
|
|
|
|
|
|
bool CASW_Alien::CanBecomeRagdoll( void )
|
|
{
|
|
MDLCACHE_CRITICAL_SECTION();
|
|
|
|
int ragdollSequence = SelectWeightedSequence( ACT_DIERAGDOLL );
|
|
|
|
if ( m_nDeathStyle == kDIE_FANCY && !m_bTimeToRagdoll )
|
|
return false;
|
|
|
|
//Can't cause we don't have a ragdoll sequence.
|
|
if ( ragdollSequence == ACTIVITY_NOT_AVAILABLE )
|
|
return false;
|
|
|
|
if ( GetFlags() & FL_TRANSRAGDOLL )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CASW_Alien::CanDoFancyDeath()
|
|
{
|
|
if ( m_nDeathStyle == kDIE_BREAKABLE || m_nDeathStyle == kDIE_INSTAGIB )
|
|
return false;
|
|
|
|
if ( GetNavType() != NAV_GROUND )
|
|
return false;
|
|
|
|
if ( IsCurSchedule( SCHED_BURROW_WAIT, false ) || IsCurSchedule( SCHED_WAIT_FOR_CLEAR_UNBORROW, false ) || IsCurSchedule( SCHED_BURROW_OUT, false ) )
|
|
return false;
|
|
|
|
if ( m_hMoveClone.Get() )
|
|
return false;
|
|
|
|
if ( m_bOnFire )
|
|
{
|
|
// do i have the custom death activities?
|
|
if ( SelectWeightedSequence ( ( Activity ) ACT_DEATH_FIRE ) == ACTIVITY_NOT_AVAILABLE || RandomFloat() < 0.5f )
|
|
return false;
|
|
}
|
|
else if ( m_bElectroStunned )
|
|
{
|
|
// do i have the custom death activities?
|
|
if ( SelectWeightedSequence ( ( Activity ) ACT_DEATH_ELEC ) == ACTIVITY_NOT_AVAILABLE || RandomFloat() < 0.75f )
|
|
return false;
|
|
}
|
|
// do i have the custom death activities?
|
|
else
|
|
{
|
|
if ( m_fFancyDeathChance > asw_alien_fancy_death_chance.GetFloat() )
|
|
return false;
|
|
|
|
if ( SelectWeightedSequence ( ( Activity ) ACT_DIE_FANCY ) == ACTIVITY_NOT_AVAILABLE )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
extern ConVar asw_fist_ragdoll_chance;
|
|
|
|
void CASW_Alien::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
if (asw_debug_alien_damage.GetBool())
|
|
Msg("%f alien killed\n", gpGlobals->curtime);
|
|
if (ASWGameRules())
|
|
{
|
|
ASWGameRules()->AlienKilled(this, info);
|
|
}
|
|
|
|
CASW_GameStats.Event_AlienKilled( this, info );
|
|
|
|
if ( ASWDirector() )
|
|
ASWDirector()->Event_AlienKilled( this, info );
|
|
|
|
if (m_hSpawner.Get())
|
|
m_hSpawner->AlienKilled(this);
|
|
|
|
//bool bRagdollCreated = Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_ELECTRICAL );
|
|
|
|
//switch to the dead bodygroup if you are not on fire or electrocuted
|
|
if ( HasDeadBodyGroup() && !m_bOnFire && !m_bElectroStunned )
|
|
{
|
|
SetBodygroup( 0, m_iDeadBodyGroup );
|
|
}
|
|
|
|
if ( m_flFrozen >= 0.1f )
|
|
{
|
|
bool bShatter = ( RandomFloat() > 0.01f );
|
|
CreateASWServerStatue( this, COLLISION_GROUP_NONE, info, bShatter );
|
|
BaseClass::Event_Killed( CTakeDamageInfo( info.GetAttacker(), info.GetAttacker(), info.GetDamage(), DMG_GENERIC | DMG_REMOVENORAGDOLL | DMG_PREVENT_PHYSICS_FORCE ) );
|
|
RemoveDeferred();
|
|
return;
|
|
}
|
|
|
|
// if we died from an explosion, instagib
|
|
if ( !m_bNeverInstagib && info.GetDamage() > 8.0f && (info.GetDamageType() & DMG_BLAST || info.GetDamageType() & DMG_SONIC) )
|
|
{
|
|
m_nDeathStyle = kDIE_INSTAGIB;
|
|
}
|
|
else if ( !m_bNeverInstagib && !m_bElectroStunned && info.GetDamage() > 20.0f && RandomFloat() > 0.05f ) // if the damage inflicted was a high amount of damage, instagib 95% of the time
|
|
{
|
|
m_nDeathStyle = kDIE_INSTAGIB;
|
|
}
|
|
else // else see if we can break apart
|
|
{
|
|
// if we can do a fancy death or we don't do a break, we will ragdoll to the split version
|
|
if ( CanDoFancyDeath() )
|
|
{
|
|
m_nDeathStyle = kDIE_FANCY;
|
|
}
|
|
// if our model is set up to break and we aren't on fire or electrocuted, break
|
|
else if ( CanBreak() && RandomFloat() < asw_alien_break_chance.GetFloat() && !m_bElectroStunned && !m_bOnFire )
|
|
{
|
|
m_nDeathStyle = kDIE_BREAKABLE;
|
|
//BreakAlien( info );
|
|
}
|
|
// if we are set to never ragdoll or if we are electrified, but dont have anims or decided not to do them, instagib
|
|
else if ( m_bNeverRagdoll || m_bElectroStunned )
|
|
{
|
|
m_nDeathStyle = kDIE_INSTAGIB;
|
|
}
|
|
}
|
|
|
|
if (!ShouldGib(info))
|
|
{
|
|
const unsigned int nDamageTypesThatCauseHurling = DMG_BLAST | DMG_BLAST_SURFACE;
|
|
SetCollisionGroup(ASW_COLLISION_GROUP_PASSABLE); // don't block marines by dead bodies
|
|
if ( (info.GetDamageType() & nDamageTypesThatCauseHurling) &&
|
|
gpGlobals->curtime > sm_flLastHurlTime + asw_drone_hurl_interval.GetFloat() &&
|
|
random->RandomFloat() <= asw_drone_hurl_chance.GetFloat() )
|
|
{
|
|
m_nDeathStyle = kDIE_HURL;
|
|
sm_flLastHurlTime = gpGlobals->curtime;
|
|
}
|
|
if ( ( info.GetDamageType() & DMG_CLUB ) && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
|
|
{
|
|
CASW_Marine *pMarine = assert_cast<CASW_Marine*>( info.GetAttacker() );
|
|
|
|
if ( pMarine->HasPowerFist() && RandomFloat() < asw_fist_ragdoll_chance.GetFloat() )
|
|
{
|
|
m_nDeathStyle = kDIE_MELEE_THROW;
|
|
}
|
|
}
|
|
}
|
|
|
|
DropMoney( info );
|
|
if ( ASWGameRules() )
|
|
ASWGameRules()->DropPowerup( this, info, GetClassname() );
|
|
|
|
if ( asw_alien_debug_death_style.GetBool() )
|
|
Msg( "'%s' CASW_Alien::Event_Killed: m_nDeathStyle = %d\n", GetClassname(), m_nDeathStyle );
|
|
|
|
BaseClass::Event_Killed(info);
|
|
}
|
|
|
|
void CASW_Alien::SetSpawner(CASW_Base_Spawner* spawner)
|
|
{
|
|
m_hSpawner = spawner;
|
|
}
|
|
|
|
void CASW_Alien::InputBreakWaitForScript(inputdata_t &inputdata)
|
|
{
|
|
if (IsCurSchedule( SCHED_WAIT_FOR_SCRIPT ))
|
|
{
|
|
SetSchedule(SCHED_IDLE_STAND);
|
|
}
|
|
}
|
|
|
|
// set orders for our alien
|
|
// select schedule should activate the appropriate orders
|
|
void CASW_Alien::SetAlienOrders(AlienOrder_t Orders, Vector vecOrderSpot, CBaseEntity* pOrderObject)
|
|
{
|
|
m_AlienOrders = Orders;
|
|
m_vecAlienOrderSpot = vecOrderSpot; // unused currently
|
|
m_AlienOrderObject = pOrderObject;
|
|
|
|
if (Orders == AOT_None)
|
|
{
|
|
ClearAlienOrders();
|
|
return;
|
|
}
|
|
|
|
ForceDecisionThink(); // todo: stagger the decision time if at start of mission?
|
|
|
|
//Msg("Drone recieved orders\n");
|
|
}
|
|
|
|
AlienOrder_t CASW_Alien::GetAlienOrders()
|
|
{
|
|
return m_AlienOrders;
|
|
}
|
|
|
|
void CASW_Alien::ClearAlienOrders()
|
|
{
|
|
//Msg("Drone orders cleared\n");
|
|
m_AlienOrders = AOT_None;
|
|
m_vecAlienOrderSpot = vec3_origin;
|
|
m_AlienOrderObject = NULL;
|
|
m_bIgnoreMarines = false;
|
|
m_bFailedMoveTo = false;
|
|
}
|
|
|
|
void CASW_Alien::GatherConditions()
|
|
{
|
|
BaseClass::GatherConditions();
|
|
|
|
if (m_bIgnoreMarines) // todo: 'proper' way to ignore conditions? (using SetIgnoreConditions on receiving orders doesn't work)
|
|
{
|
|
ClearCondition(COND_CAN_MELEE_ATTACK1);
|
|
ClearCondition(COND_CAN_MELEE_ATTACK2);
|
|
ClearCondition(COND_CAN_RANGE_ATTACK1);
|
|
ClearCondition(COND_CAN_RANGE_ATTACK2);
|
|
ClearCondition(COND_ENEMY_DEAD);
|
|
ClearCondition(COND_HEAR_BULLET_IMPACT);
|
|
ClearCondition(COND_HEAR_COMBAT);
|
|
ClearCondition(COND_HEAR_DANGER);
|
|
ClearCondition(COND_HEAR_PHYSICS_DANGER);
|
|
ClearCondition(COND_NEW_ENEMY);
|
|
ClearCondition(COND_PROVOKED);
|
|
ClearCondition(COND_SEE_ENEMY);
|
|
ClearCondition(COND_SEE_FEAR);
|
|
ClearCondition(COND_SMELL);
|
|
ClearCondition(COND_HEAVY_DAMAGE);
|
|
ClearCondition(COND_LIGHT_DAMAGE);
|
|
ClearCondition(COND_RECEIVED_ORDERS);
|
|
}
|
|
|
|
if (HasCondition(COND_NEW_ENEMY)
|
|
&& m_AlienOrders != AOT_MoveToIgnoringMarines) // if we're not ignoring marines, finish our orders once we spot an enemy
|
|
{
|
|
ClearAlienOrders();
|
|
}
|
|
}
|
|
|
|
// if we arrive at our destination, clear our orders
|
|
void CASW_Alien::OnMovementComplete()
|
|
{
|
|
if ( ShouldClearOrdersOnMovementComplete() )
|
|
{
|
|
ClearAlienOrders();
|
|
}
|
|
|
|
BaseClass::OnMovementComplete();
|
|
}
|
|
|
|
bool CASW_Alien::ShouldClearOrdersOnMovementComplete()
|
|
{
|
|
if (m_AlienOrders != AOT_None)
|
|
{
|
|
if (m_AlienOrders == AOT_SpreadThenHibernate && m_bFailedMoveTo)
|
|
{
|
|
// should try to repath?
|
|
// or go to a schedule where we wait X seconds, then try to repath?
|
|
}
|
|
// check if we finished trying to chase a marine, but don't have an enemy yet
|
|
// this means the marine probably moved somewhere else and we need to chase after him again
|
|
if (m_AlienOrders == AOT_MoveToNearestMarine && !GetEnemy())
|
|
{
|
|
float marine_distance;
|
|
CBaseEntity *pMarine = UTIL_ASW_NearestMarine(GetAbsOrigin(), marine_distance);
|
|
if (pMarine)
|
|
{
|
|
// don't clear orders, this'll make the alien's selectschedule do the marine chase again
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
void CASW_Alien::IgnoreMarines(bool bIgnoreMarines)
|
|
{
|
|
static int g_GeneralConditions[] =
|
|
{
|
|
COND_CAN_MELEE_ATTACK1,
|
|
COND_CAN_MELEE_ATTACK2,
|
|
COND_CAN_RANGE_ATTACK1,
|
|
COND_CAN_RANGE_ATTACK2,
|
|
COND_ENEMY_DEAD,
|
|
COND_HEAR_BULLET_IMPACT,
|
|
COND_HEAR_COMBAT,
|
|
COND_HEAR_DANGER,
|
|
COND_HEAR_PHYSICS_DANGER,
|
|
COND_NEW_ENEMY,
|
|
COND_PROVOKED,
|
|
COND_SEE_ENEMY,
|
|
COND_SEE_FEAR,
|
|
COND_SMELL,
|
|
};
|
|
|
|
static int g_DamageConditions[] =
|
|
{
|
|
COND_HEAVY_DAMAGE,
|
|
COND_LIGHT_DAMAGE,
|
|
COND_RECEIVED_ORDERS,
|
|
};
|
|
|
|
ClearIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
|
|
ClearIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
|
|
|
|
if (bIgnoreMarines)
|
|
{
|
|
SetIgnoreConditions( g_GeneralConditions, ARRAYSIZE(g_GeneralConditions) );
|
|
SetIgnoreConditions( g_DamageConditions, ARRAYSIZE(g_DamageConditions) );
|
|
m_bIgnoreMarines = true;
|
|
}
|
|
}
|
|
|
|
// we're blocking a fellow alien from spawning, let's move a short distance
|
|
void CASW_Alien::MoveAside()
|
|
{
|
|
if (!GetEnemy() && !IsMoving())
|
|
{
|
|
// random nearby position
|
|
if ( !GetNavigator()->SetWanderGoal( 90, 200 ) )
|
|
{
|
|
if ( !GetNavigator()->SetRandomGoal( 150.0f ) )
|
|
{
|
|
return; // couldn't find a wander spot
|
|
}
|
|
}
|
|
//SetSchedule(SCHED_IDLE_WALK);
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::ASW_Ignite( float flFlameLifetime, float flSize, CBaseEntity *pAttacker, CBaseEntity *pDamagingWeapon )
|
|
{
|
|
if (AllowedToIgnite())
|
|
{
|
|
if( IsOnFire() )
|
|
return;
|
|
|
|
AddFlag( FL_ONFIRE );
|
|
m_bOnFire = true;
|
|
if (ASWBurning())
|
|
ASWBurning()->BurnEntity(this, pAttacker, flFlameLifetime, 0.4f, 2.5f * 0.4f, pDamagingWeapon ); // 2.5 dps, applied every 0.4 seconds
|
|
|
|
m_OnIgnite.FireOutput( this, this );
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::Extinguish()
|
|
{
|
|
m_bOnFire = false;
|
|
if (ASWBurning())
|
|
ASWBurning()->Extinguish(this);
|
|
RemoveFlag( FL_ONFIRE );
|
|
}
|
|
|
|
bool CASW_Alien::IsMeleeAttacking()
|
|
{
|
|
return (GetTask() && (GetTask()->iTask == TASK_MELEE_ATTACK1));
|
|
}
|
|
|
|
bool CASW_Alien::Knockback(Vector vecForce)
|
|
{
|
|
/*
|
|
Vector end = GetAbsOrigin() + vecForce; // todo: divide by mass, etc
|
|
trace_t tr;
|
|
UTIL_TraceEntity( this, GetAbsOrigin(), end, MASK_NPCSOLID, &tr );
|
|
if (tr.allsolid)
|
|
return false;
|
|
|
|
if( tr.fraction > 0 )
|
|
{
|
|
SetAbsOrigin(tr.endpos);
|
|
return true;
|
|
}
|
|
return false;*/
|
|
Vector newVel = GetAbsVelocity();
|
|
SetAbsVelocity(newVel + vecForce);
|
|
SetGroundEntity( NULL );
|
|
return true;
|
|
}
|
|
|
|
void CASW_Alien::UpdateEfficiency( bool bInPVS )
|
|
{
|
|
// Sleeping NPCs always dormant
|
|
if ( GetSleepState() != AISS_AWAKE )
|
|
{
|
|
SetEfficiency( AIE_DORMANT );
|
|
return;
|
|
}
|
|
|
|
m_bInChoreo = ( GetState() == NPC_STATE_SCRIPT || IsCurSchedule( SCHED_SCENE_GENERIC, false ) );
|
|
|
|
#ifndef _RETAIL
|
|
if ( !(ai_use_think_optimizations.GetBool() && ai_use_efficiency.GetBool()) )
|
|
{
|
|
SetEfficiency( AIE_NORMAL );
|
|
SetMoveEfficiency( AIME_NORMAL );
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
bool bInVisibilityPVS = ( UTIL_FindClientInVisibilityPVS( edict() ) != NULL );
|
|
|
|
//if ( bInPVS && MarineNearby(1024) )
|
|
if ( bInPVS && MarineCanSee(384, 1.0f) )
|
|
{
|
|
SetMoveEfficiency( AIME_NORMAL );
|
|
}
|
|
else
|
|
{
|
|
SetMoveEfficiency( AIME_EFFICIENT );
|
|
}
|
|
|
|
//---------------------------------
|
|
|
|
if ( !IsRetail() && ai_efficiency_override.GetInt() > AIE_NORMAL && ai_efficiency_override.GetInt() <= AIE_DORMANT )
|
|
{
|
|
SetEfficiency( (AI_Efficiency_t)ai_efficiency_override.GetInt() );
|
|
return;
|
|
}
|
|
|
|
// Some conditions will always force normal
|
|
if ( gpGlobals->curtime - GetLastAttackTime() < .15 )
|
|
{
|
|
SetEfficiency( AIE_NORMAL );
|
|
return;
|
|
}
|
|
|
|
bool bFramerateOk = ( gpGlobals->frametime < ai_frametime_limit.GetFloat() );
|
|
|
|
if ( IsForceGatherConditionsSet() ||
|
|
gpGlobals->curtime - GetLastAttackTime() < .2 ||
|
|
gpGlobals->curtime - m_flLastDamageTime < .2 ||
|
|
( GetState() < NPC_STATE_IDLE || GetState() > NPC_STATE_SCRIPT ) ||
|
|
( ( bInPVS || bInVisibilityPVS ) &&
|
|
( ( GetTask() && !TaskIsRunning() ) ||
|
|
GetTaskInterrupt() > 0 ||
|
|
m_bInChoreo ) ) )
|
|
{
|
|
SetEfficiency( ( bFramerateOk ) ? AIE_NORMAL : AIE_EFFICIENT );
|
|
return;
|
|
}
|
|
|
|
SetEfficiency( ( bFramerateOk ) ? AIE_EFFICIENT : AIE_VERY_EFFICIENT );
|
|
}
|
|
|
|
void CASW_Alien::OnRestore()
|
|
{
|
|
BaseClass::OnRestore();
|
|
m_LagCompensation.Init(this);
|
|
}
|
|
|
|
// checks if a marine can see us
|
|
// caches the results and won't recheck unless the specified interval has passed since the last check
|
|
bool CASW_Alien::MarineCanSee(int padding, float interval)
|
|
{
|
|
if (gpGlobals->curtime >= m_fLastMarineCanSeeTime + interval)
|
|
{
|
|
bool bCorpseCanSee = false;
|
|
m_bLastMarineCanSee = (UTIL_ASW_AnyMarineCanSee(GetAbsOrigin(), padding, bCorpseCanSee) != NULL) || bCorpseCanSee;
|
|
m_fLastMarineCanSeeTime = gpGlobals->curtime;
|
|
}
|
|
return m_bLastMarineCanSee;
|
|
}
|
|
|
|
void CASW_Alien::SetHealthByDifficultyLevel()
|
|
{
|
|
// filled in by subclasses
|
|
}
|
|
|
|
void CASW_Alien::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
|
|
{
|
|
return; // use ASW_Ignite instead
|
|
}
|
|
|
|
bool CASW_Alien::ShouldMoveSlow() const
|
|
{
|
|
return (gpGlobals->curtime < m_fHurtSlowMoveTime);
|
|
}
|
|
|
|
bool CASW_Alien::ModifyAutoMovement( Vector &vecNewPos )
|
|
{
|
|
// melee auto movement on the drones seems way too fast
|
|
float fFactor = 1.0f;
|
|
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;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
float CASW_Alien::GetIdealSpeed() const
|
|
{
|
|
// if the alien is hurt, move slower
|
|
if ( ShouldMoveSlow() )
|
|
{
|
|
if ( m_bElectroStunned.Get() )
|
|
{
|
|
return BaseClass::GetIdealSpeed() * asw_alien_stunned_speed.GetFloat();
|
|
}
|
|
else
|
|
{
|
|
return BaseClass::GetIdealSpeed() * asw_alien_hurt_speed.GetFloat();
|
|
}
|
|
}
|
|
|
|
return BaseClass::GetIdealSpeed();
|
|
}
|
|
|
|
void CASW_Alien::DropMoney( const CTakeDamageInfo &info )
|
|
{
|
|
if ( !asw_drop_money.GetBool() || !asw_money.GetBool() )
|
|
return;
|
|
|
|
int iMoneyCount = GetMoneyCount( info );
|
|
for (int i=0;i<iMoneyCount;i++)
|
|
{
|
|
CASW_Pickup_Money* pMoney = (CASW_Pickup_Money *)CreateEntityByName( "asw_pickup_money" );
|
|
UTIL_SetOrigin( pMoney, WorldSpaceCenter() );
|
|
pMoney->Spawn();
|
|
Vector vel = -info.GetDamageForce();
|
|
vel.NormalizeInPlace();
|
|
vel *= RandomFloat( 30, 50 );
|
|
vel += RandomVector( -20, 20 );
|
|
vel.z = RandomFloat( 30, 50 );
|
|
if ( iMoneyCount >= 3 ) // if we're dropping a lot of money, make it fly up in the air more
|
|
{
|
|
vel.z += RandomFloat( 60, 80 );
|
|
}
|
|
pMoney->SetAbsVelocity( vel );
|
|
}
|
|
}
|
|
|
|
int CASW_Alien::GetMoneyCount( const CTakeDamageInfo &info )
|
|
{
|
|
if ( RandomFloat() < asw_alien_money_chance.GetFloat() )
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void CASW_Alien::ElectroStun( float flStunTime )
|
|
{
|
|
if (m_fHurtSlowMoveTime < gpGlobals->curtime + flStunTime)
|
|
m_fHurtSlowMoveTime = gpGlobals->curtime + flStunTime;
|
|
|
|
m_bElectroStunned = true;
|
|
|
|
if ( ASWGameResource() )
|
|
{
|
|
ASWGameResource()->m_iElectroStunnedAliens++;
|
|
}
|
|
|
|
// can't jump after being elecrostunned
|
|
CapabilitiesRemove( bits_CAP_MOVE_JUMP );
|
|
}
|
|
|
|
void CASW_Alien::ForceFlinch( const Vector &vecSrc )
|
|
{
|
|
SetCondition( COND_HEAVY_DAMAGE );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CASW_AlienVolley *CASW_Alien::GetVolley( const char *pszVolleyName )
|
|
{
|
|
for( int nIndex = 0; nIndex < m_volleys.Count(); nIndex++ )
|
|
{
|
|
if ( m_volleys[ nIndex ].m_strName == pszVolleyName )
|
|
{
|
|
return &m_volleys[ nIndex ];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CASW_Alien::GetVolleyIndex( const char *pszVolleyName )
|
|
{
|
|
for( int nIndex = 0; nIndex < m_volleys.Count(); nIndex++ )
|
|
{
|
|
if ( m_volleys[ nIndex ].m_strName == pszVolleyName )
|
|
{
|
|
return nIndex;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CASW_AlienShot *CASW_Alien::GetShot( const char *pszShotName )
|
|
{
|
|
for( int nIndex = 0; nIndex < m_shots.Count(); nIndex++ )
|
|
{
|
|
if ( m_shots[ nIndex ].m_strName == pszShotName )
|
|
{
|
|
return &m_shots[ nIndex ];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CASW_Alien::GetShotIndex( const char *pszShotName )
|
|
{
|
|
for( int nIndex = 0; nIndex < m_shots.Count(); nIndex++ )
|
|
{
|
|
if ( m_shots[ nIndex ].m_strName == pszShotName )
|
|
{
|
|
return nIndex;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::CreateShot( const char *pszShotName, const CASW_AlienShot *pShot )
|
|
{
|
|
CASW_AlienShot *pNewShot = GetShot( pszShotName );
|
|
if ( !pNewShot )
|
|
{
|
|
int nIndex = m_shots.AddToTail();
|
|
pNewShot = &m_shots[ nIndex ];
|
|
}
|
|
|
|
*pNewShot = *pShot;
|
|
pNewShot->m_strName = pszShotName;
|
|
|
|
// Precache resources
|
|
if ( !pShot->m_strModel.IsEmpty() )
|
|
PrecacheModel( pShot->m_strModel );
|
|
|
|
if ( !pShot->m_strSound_spawn.IsEmpty() )
|
|
PrecacheScriptSound( pShot->m_strSound_spawn );
|
|
|
|
if ( !pShot->m_strSound_hitNPC.IsEmpty() )
|
|
PrecacheScriptSound( pShot->m_strSound_hitNPC );
|
|
|
|
if ( !pShot->m_strSound_hitWorld.IsEmpty() )
|
|
PrecacheScriptSound( pShot->m_strSound_hitWorld );
|
|
|
|
if ( !pShot->m_strParticles_trail.IsEmpty() )
|
|
PrecacheParticleSystem( pShot->m_strParticles_trail );
|
|
|
|
if ( !pShot->m_strParticles_hit.IsEmpty() )
|
|
PrecacheParticleSystem( pShot->m_strParticles_hit );
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::CreateVolley( const char *pszVolleyName, const CASW_AlienVolley *pVolley )
|
|
{
|
|
CASW_AlienVolley *pNewVolley = GetVolley( pszVolleyName );
|
|
if ( !pNewVolley )
|
|
{
|
|
int nIndex = m_volleys.AddToTail();
|
|
pNewVolley = &m_volleys[ nIndex ];
|
|
}
|
|
|
|
*pNewVolley = *pVolley;
|
|
pNewVolley->m_strName = pszVolleyName;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::CalcMissileVelocity( const Vector &vTargetPos, Vector &vVelocity, float flSpeed, float flAngle )
|
|
{
|
|
float flRadians = DEG2RAD( flAngle );
|
|
Vector vForward = ( vTargetPos - GetAbsOrigin() );
|
|
vForward.NormalizeInPlace();
|
|
Vector vRight = vForward.Cross( Vector( 0, 0, 1 ) );
|
|
Vector vDir = vForward * cos( flRadians ) + vRight * sin( flRadians );
|
|
|
|
vVelocity = vDir * flSpeed;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::UpdateRangedAttack()
|
|
{
|
|
if ( m_nVolleyType < 0 )
|
|
return;
|
|
|
|
Vector vecSpawn = GetAbsOrigin() + Vector( 0.0f, 0.0f, 15.0f );
|
|
QAngle vecAngles = QAngle( 0.0f, 0.0f, 0.0f );
|
|
int iAttachment = LookupAttachment( "mouth" );
|
|
if ( iAttachment > 0 )
|
|
GetAttachment( iAttachment, vecSpawn, vecAngles );
|
|
|
|
Vector vForward = ( m_vecRangeAttackTargetPosition- GetAbsOrigin() );
|
|
vForward.NormalizeInPlace();
|
|
Vector vRight = vForward.Cross( Vector( 0, 0, 1 ) );
|
|
|
|
int nNumFinished = 0;
|
|
CASW_AlienVolley *pVolley = &m_volleys[ m_nVolleyType ];
|
|
for( int nRound = 0; nRound < pVolley->m_rounds.Count(); nRound++ )
|
|
{
|
|
CASW_AlienVolleyRound &round = pVolley->m_rounds[ nRound ];
|
|
if ( round.m_nShot_type < 0 )
|
|
{
|
|
nNumFinished++;
|
|
continue;
|
|
}
|
|
|
|
// has round started yet?
|
|
if ( m_flRangeAttackStartTime + round.m_flTime > gpGlobals->curtime )
|
|
continue;
|
|
|
|
// is round finished?
|
|
float flEndTime = m_flRangeAttackStartTime + round.m_flTime + round.m_nNumShots * round.m_flShotDelay;
|
|
if ( ( flEndTime < gpGlobals->curtime ) && ( flEndTime < m_flRangeAttackLastUpdateTime ) )
|
|
{
|
|
nNumFinished++;
|
|
continue;
|
|
}
|
|
|
|
for( int i = 0; i < round.m_nNumShots; i++ )
|
|
{
|
|
float flShotTime = m_flRangeAttackStartTime + round.m_flTime + i * round.m_flShotDelay;
|
|
if ( ( flShotTime > m_flRangeAttackLastUpdateTime ) && ( flShotTime <= gpGlobals->curtime ) )
|
|
{
|
|
Vector vVelocity;
|
|
CASW_AlienShot &shot = m_shots[ round.m_nShot_type ];
|
|
float flPercent = ( round.m_nNumShots > 1 ) ? ( ( float )i / ( float )( round.m_nNumShots - 1 ) ) : 0.0f;
|
|
float flRadians = DEG2RAD( Lerp( flPercent, round.m_flStartAngle, round.m_flEndAngle ) );
|
|
Vector vDir = vForward * cos( flRadians ) + vRight * sin( flRadians );
|
|
vVelocity = vDir * round.m_flSpeed;
|
|
Vector vStart = vecSpawn + vRight * round.m_flHorizontalOffset;
|
|
CASW_Missile_Round::Missile_Round_Create( shot, vStart, vecAngles, vVelocity, this );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( nNumFinished >= pVolley->m_rounds.Count() )
|
|
{
|
|
m_nVolleyType = -1;
|
|
return;
|
|
}
|
|
|
|
m_flRangeAttackLastUpdateTime = gpGlobals->curtime;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Alien::LaunchMissile( const char *pszVolleyName, Vector &vTargetPosition )
|
|
{
|
|
m_nVolleyType = GetVolleyIndex( pszVolleyName );
|
|
if ( m_nVolleyType < 0 )
|
|
{
|
|
//FIXME: add warning
|
|
return;
|
|
}
|
|
|
|
m_flRangeAttackStartTime = gpGlobals->curtime;
|
|
m_flRangeAttackLastUpdateTime = -1.0f;
|
|
m_vecRangeAttackTargetPosition = vTargetPosition;
|
|
UpdateRangedAttack();
|
|
}
|
|
void CASW_Alien::AddBehaviorParam( const char *pszParmName, int nDefaultValue )
|
|
{
|
|
CUtlSymbol ParmName = CAI_ASW_Behavior::GetSymbol( pszParmName );
|
|
|
|
m_BehaviorParms.Insert( ParmName, nDefaultValue );
|
|
}
|
|
|
|
|
|
int CASW_Alien::GetBehaviorParam( CUtlSymbol ParmName )
|
|
{
|
|
int nIndex = m_BehaviorParms.Find( ParmName );
|
|
|
|
if ( m_BehaviorParms.IsValidIndex( nIndex ) == true )
|
|
{
|
|
return m_BehaviorParms.Element( nIndex );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void CASW_Alien::SetBehaviorParam( CUtlSymbol ParmName, int nValue )
|
|
{
|
|
if ( ParmName.IsValid() == false )
|
|
{
|
|
return;
|
|
}
|
|
|
|
int nIndex = m_BehaviorParms.Find( ParmName );
|
|
if ( m_BehaviorParms.IsValidIndex( nIndex ) == true )
|
|
{
|
|
if ( m_BehaviorParms.Element( nIndex ) != nValue )
|
|
{
|
|
m_BehaviorParms.Element( nIndex ) = nValue;
|
|
m_bBehaviorParameterChanged = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
void CASW_Alien::OnChangeRunningBehavior( CAI_BehaviorBase *pOldBehavior, CAI_BehaviorBase *pNewBehavior )
|
|
{
|
|
BaseClass::OnChangeRunningBehavior( pOldBehavior, pNewBehavior );
|
|
|
|
m_pPreviousBehavior = m_pPreviousBehavior;
|
|
}
|
|
|
|
void CASW_Alien::SendBehaviorEvent( CBaseEntity *pInflictor, BehaviorEvent_t Event, int nParm, bool bToAllBehaviors )
|
|
{
|
|
if ( bToAllBehaviors )
|
|
{
|
|
for ( int i = 0; i < m_Behaviors.Count(); i++)
|
|
{
|
|
CAI_ASW_Behavior *pBehavior = static_cast < CAI_ASW_Behavior * >( m_Behaviors[ i ] );
|
|
|
|
pBehavior->HandleBehaviorEvent( pInflictor, Event, nParm );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CAI_ASW_Behavior *pBehavior = GetPrimaryASWBehavior();
|
|
if ( pBehavior == NULL )
|
|
{ // we have probably died, try sending this to the last behavior
|
|
pBehavior = static_cast < CAI_ASW_Behavior * >( m_pPreviousBehavior );
|
|
}
|
|
|
|
if ( pBehavior != NULL )
|
|
{
|
|
pBehavior->HandleBehaviorEvent( pInflictor, Event, nParm );
|
|
}
|
|
else
|
|
{
|
|
// some how the boomer is coming back with having no primary behavior, which I don't understand.
|
|
// if you see this assert fire, please let RJ know.
|
|
Assert( 0 );
|
|
}
|
|
}
|
|
}
|
|
void CASW_Alien::BuildScheduleTestBits()
|
|
{
|
|
CAI_ASW_CombatStunBehavior *pBehavior = NULL;
|
|
|
|
// Check for combat stun behavior, or, if we're not using behaviors, we'll fall back to the combat stun schedule in CASW_Alien
|
|
if ( GetBehavior( &pBehavior ) && pBehavior != m_pPrimaryBehavior )
|
|
{
|
|
SetCustomInterruptCondition( COND_ASW_BEGIN_COMBAT_STUN );
|
|
}
|
|
|
|
SetCustomInterruptCondition( COND_BEHAVIOR_PARAMETERS_CHANGED );
|
|
|
|
if ( m_pFlinchBehavior )
|
|
{
|
|
SetCustomInterruptCondition( COND_ASW_FLINCH );
|
|
}
|
|
|
|
BaseClass::BuildScheduleTestBits();
|
|
}
|
|
void CASW_Alien::StartTouch( CBaseEntity *pOther )
|
|
{
|
|
BaseClass::StartTouch(pOther);
|
|
|
|
CAI_ASW_Behavior *pCurrent = GetPrimaryASWBehavior();
|
|
if ( pCurrent )
|
|
{
|
|
pCurrent->StartTouch( pOther );
|
|
}
|
|
}
|
|
CAI_ASW_Behavior* CASW_Alien::GetPrimaryASWBehavior()
|
|
{
|
|
return assert_cast<CAI_ASW_Behavior*>( GetPrimaryBehavior() );
|
|
}
|
|
|
|
void CASW_Alien::HandleAnimEvent( animevent_t *pEvent )
|
|
{
|
|
|
|
int nEvent = pEvent->Event();
|
|
CTakeDamageInfo info;
|
|
if ( nEvent == AE_NPC_RAGDOLL )
|
|
{
|
|
m_bTimeToRagdoll = true;
|
|
// anim event is telling us to ragdoll. If we aren't on fire, break instead
|
|
if ( m_nDeathStyle == kDIE_FANCY && !m_bOnFire )
|
|
{
|
|
m_nDeathStyle = kDIE_BREAKABLE;
|
|
//BreakAlien( info );
|
|
}
|
|
//BecomeRagdollOnClient(GetAbsOrigin());
|
|
//return;
|
|
}
|
|
|
|
CAI_ASW_Behavior *pBehavior = GetPrimaryASWBehavior();
|
|
if ( pBehavior && pBehavior->BehaviorHandleAnimEvent( pEvent ) )
|
|
return;
|
|
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
|
|
int CASW_Alien::DrawDebugTextOverlays()
|
|
{
|
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Freeze amt.: %f", m_flFrozen.Get() ),0 );
|
|
text_offset++;
|
|
NDebugOverlay::EntityText( entindex(),text_offset,CFmtStr( "Freeze time: %f", m_flFrozenTime - gpGlobals->curtime ),0 );
|
|
text_offset++;
|
|
}
|
|
return text_offset;
|
|
}
|
|
|
|
void CASW_Alien::InputSetMoveClone( inputdata_t &inputdata )
|
|
{
|
|
SetMoveClone( inputdata.value.StringID(), inputdata.pActivator );
|
|
}
|
|
|
|
void CASW_Alien::SetMoveClone( string_t EntityName, CBaseEntity *pActivator )
|
|
{
|
|
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, EntityName, NULL, pActivator );
|
|
|
|
if ( EntityName != NULL_STRING && pEnt == NULL )
|
|
{
|
|
Msg( "Entity %s(%s) has bad move clone %s\n", STRING(m_iClassname), GetDebugName(), STRING(EntityName) );
|
|
}
|
|
else
|
|
{
|
|
// make sure there isn't any ambiguity
|
|
if ( gEntList.FindEntityByName( pEnt, EntityName, NULL, pActivator ) )
|
|
{
|
|
Msg( "Entity %s(%s) is ambiguously move cloned to %s, because there is more than one entity by that name.\n", STRING(m_iClassname), GetDebugName(), STRING(EntityName) );
|
|
}
|
|
SetMoveClone( pEnt );
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::SetMoveClone( CBaseEntity *pEnt )
|
|
{
|
|
m_hMoveClone = pEnt;
|
|
|
|
if ( pEnt )
|
|
{
|
|
matrix3x4_t entityToWorld = EntityToWorldTransform();
|
|
matrix3x4_t otherToWorld = pEnt->EntityToWorldTransform();
|
|
|
|
matrix3x4_t temp;
|
|
MatrixInvert( otherToWorld, temp );
|
|
ConcatTransforms( temp, entityToWorld, m_moveCloneOffset );
|
|
}
|
|
}
|
|
|
|
bool CASW_Alien::OverrideMove( float flInterval )
|
|
{
|
|
if ( m_hMoveClone.Get() )
|
|
{
|
|
matrix3x4_t otherToWorld = m_hMoveClone->EntityToWorldTransform();
|
|
matrix3x4_t temp;
|
|
ConcatTransforms( otherToWorld, m_moveCloneOffset, temp );
|
|
SetLocalTransform( temp );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CASW_Alien::ClearBurrowPoint( const Vector &origin )
|
|
{
|
|
CBaseEntity *pEntity = NULL;
|
|
float flDist;
|
|
Vector vecSpot, vecCenter, vecForce;
|
|
|
|
//Cause a ruckus
|
|
//UTIL_ScreenShake( origin, 1.0f, 80.0f, 1.0f, 256.0f, SHAKE_START );
|
|
|
|
//Iterate on all entities in the vicinity.
|
|
for ( CEntitySphereQuery sphere( origin, 128 ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
|
|
{
|
|
//if ( pEntity->m_takedamage != DAMAGE_NO && pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() )
|
|
if ( pEntity->Classify() != CLASS_PLAYER && pEntity->VPhysicsGetObject() )
|
|
{
|
|
vecSpot = pEntity->BodyTarget( origin );
|
|
vecForce = ( vecSpot - origin ) + Vector( 0, 0, 16 );
|
|
|
|
// decrease damage for an ent that's farther from the bomb.
|
|
flDist = VectorNormalize( vecForce );
|
|
|
|
//float mass = pEntity->VPhysicsGetObject()->GetMass();
|
|
CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1.0f, 1.0f, 1.0f ), &vecCenter );
|
|
|
|
if ( flDist <= 128.0f )
|
|
{
|
|
pEntity->VPhysicsGetObject()->Wake();
|
|
pEntity->VPhysicsGetObject()->ApplyForceOffset( vecForce * 250.0f, vecCenter );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Alien::Unburrow( void )
|
|
{
|
|
m_bStartBurrowed = false;
|
|
|
|
ClearBurrowPoint( m_vecUnburrowEndPoint ); // physics blast anything in the way out of the way
|
|
|
|
//Become solid again and visible
|
|
m_spawnflags &= ~SF_NPC_GAG;
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
m_takedamage = DAMAGE_YES;
|
|
|
|
RemoveEffects( EF_NODRAW );
|
|
RemoveFlag( FL_NOTARGET );
|
|
|
|
SetIdealActivity( m_UnburrowActivity );
|
|
|
|
//If we have an enemy, come out facing them
|
|
/*
|
|
if ( GetEnemy() )
|
|
{
|
|
Vector dir = GetEnemy()->GetAbsOrigin() - GetAbsOrigin();
|
|
VectorNormalize(dir);
|
|
|
|
QAngle angles = GetAbsAngles();
|
|
angles[ YAW ] = UTIL_VecToYaw( dir );
|
|
SetLocalAngles( angles );
|
|
}*/
|
|
}
|
|
|
|
void CASW_Alien::SetUnburrowActivity( string_t iszActivityName )
|
|
{
|
|
m_iszUnburrowActivityName = iszActivityName;
|
|
}
|
|
|
|
void CASW_Alien::SetUnburrowIdleActivity( string_t iszActivityName )
|
|
{
|
|
m_iszUnburrowIdleActivityName = iszActivityName;
|
|
}
|
|
|
|
void CASW_Alien::LookupBurrowActivities()
|
|
{
|
|
if ( m_iszUnburrowActivityName == NULL_STRING )
|
|
{
|
|
m_UnburrowActivity = (Activity) ACT_BURROW_OUT;
|
|
}
|
|
else
|
|
{
|
|
m_UnburrowActivity = (Activity) LookupActivity( STRING( m_iszUnburrowActivityName ) );
|
|
if ( m_UnburrowActivity == ACT_INVALID )
|
|
{
|
|
Warning( "Unknown unburrow activity %s", STRING( m_iszUnburrowActivityName ) );
|
|
if ( m_hSpawner.Get() )
|
|
{
|
|
Warning( " Spawner is: %d %s at %f %f %f\n", m_hSpawner->entindex(), m_hSpawner->GetDebugName(), VectorExpand( m_hSpawner->GetAbsOrigin() ) );
|
|
}
|
|
m_UnburrowActivity = (Activity) ACT_BURROW_OUT;
|
|
}
|
|
}
|
|
|
|
if ( m_iszUnburrowIdleActivityName == NULL_STRING )
|
|
{
|
|
m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE;
|
|
}
|
|
else
|
|
{
|
|
m_UnburrowIdleActivity = (Activity) LookupActivity( STRING( m_iszUnburrowIdleActivityName ) );
|
|
if ( m_UnburrowActivity == ACT_INVALID )
|
|
{
|
|
Warning( "Unknown unburrow idle activity %s", STRING( m_iszUnburrowIdleActivityName ) );
|
|
if ( m_hSpawner.Get() )
|
|
{
|
|
Warning( " Spawner is: %d %s at %f %f %f\n", m_hSpawner->entindex(), m_hSpawner->GetDebugName(), VectorExpand( m_hSpawner->GetAbsOrigin() ) );
|
|
}
|
|
m_UnburrowIdleActivity = (Activity) ACT_BURROW_IDLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
class CASW_Trace_Filter_Disable_Collision_With_Traps : public CTraceFilterEntitiesOnly
|
|
{
|
|
public:
|
|
// It does have a base, but we'll never network anything below here..
|
|
DECLARE_CLASS_NOBASE( CASW_Trace_Filter_Disable_Collision_With_Traps );
|
|
|
|
CASW_Trace_Filter_Disable_Collision_With_Traps( IHandleEntity *passentity, int collisionGroup )
|
|
: m_pPassEnt(passentity), m_collisionGroup(collisionGroup)
|
|
{
|
|
}
|
|
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
|
|
{
|
|
if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
|
|
return false;
|
|
|
|
// Don't test if the game code tells us we should ignore this collision...
|
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
|
|
CBaseEntity *pEntPass = EntityFromEntityHandle( m_pPassEnt );
|
|
|
|
// don't hurt ourself
|
|
if ( pEntPass == pEntity )
|
|
return false;
|
|
|
|
Class_T entClass = pEntity->Classify();
|
|
|
|
if ( entClass == CLASS_ASW_SENTRY_BASE ||
|
|
entClass == CLASS_ASW_TESLA_TRAP
|
|
)
|
|
{
|
|
PhysDisableEntityCollisions( pEntPass, pEntity );
|
|
}
|
|
|
|
return false; // keep iterating over entities in the trace ray
|
|
}
|
|
|
|
public:
|
|
IHandleEntity *m_pPassEnt;
|
|
int m_collisionGroup;
|
|
};
|
|
|
|
// if the alien is stuck inside any player set traps, this will disable collision between them
|
|
void CASW_Alien::CheckForBlockingTraps()
|
|
{
|
|
Ray_t ray;
|
|
trace_t tr;
|
|
ray.Init( GetAbsOrigin() + Vector( 0, 0, 1 ), GetAbsOrigin(), GetHullMins(), GetHullMaxs() );
|
|
|
|
CASW_Trace_Filter_Disable_Collision_With_Traps traceFilter( this, GetCollisionGroup() );
|
|
enginetrace->TraceRay( ray, PhysicsSolidMaskForEntity(), &traceFilter, &tr );
|
|
}
|
|
|
|
void CASW_Alien::SetDefaultEyeOffset()
|
|
{
|
|
m_vDefaultEyeOffset = vec3_origin;
|
|
m_vDefaultEyeOffset.z = ( WorldAlignMins().z + WorldAlignMaxs().z ) * 0.75f;
|
|
|
|
SetViewOffset( m_vDefaultEyeOffset );
|
|
} |