1298 lines
33 KiB
C++
1298 lines
33 KiB
C++
// Our Swarm Parasite - jumps and infests people
|
|
|
|
#include "cbase.h"
|
|
#include "asw_parasite.h"
|
|
#include "asw_marine.h"
|
|
#include "te_effect_dispatch.h"
|
|
#include "npc_bullseye.h"
|
|
#include "npcevent.h"
|
|
#include "asw_marine.h"
|
|
#include "asw_marine_speech.h"
|
|
#include "asw_util_shared.h"
|
|
#include "asw_spawner.h"
|
|
#include "asw_gamerules.h"
|
|
#include "asw_colonist.h"
|
|
#include "soundenvelope.h"
|
|
#include "asw_player.h"
|
|
#include "asw_achievements.h"
|
|
#include "asw_fx_shared.h"
|
|
#include "asw_marine_resource.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
#define SWARM_PARASITE_MODEL "models/aliens/parasite/parasite.mdl"
|
|
|
|
const int ASW_PARASITE_MIN_JUMP_DIST = 48;
|
|
const int ASW_PARASITE_MAX_JUMP_DIST = 170;
|
|
|
|
#define PARASITE_IGNORE_WORLD_COLLISION_TIME 0.5
|
|
|
|
ConVar asw_parasite_defanged_damage( "asw_parasite_defanged_damage", "15.0", FCVAR_CHEAT, "Damage per hit from defanged parasites");
|
|
ConVar asw_parasite_speedboost( "asw_parasite_speedboost", "1.0", FCVAR_CHEAT, "boost speed for the parasites" );
|
|
ConVar asw_infest_angle("asw_infest_angle", "0", 0, "Angle adjustment for parasite infestation attachment");
|
|
ConVar asw_infest_pitch("asw_infest_pitch", "-45", 0, "Angle adjustment for parasite infestation attachment");
|
|
ConVar asw_parasite_inside("asw_parasite_inside", "0", FCVAR_NONE, "If set, parasites will burrow into their victims rather than staying attached");
|
|
extern ConVar asw_debug_alien_damage;
|
|
extern ConVar sv_gravity;
|
|
|
|
int ACT_ASW_EGG_IDLE;
|
|
|
|
float CASW_Parasite::s_fNextSpottedChatterTime = 0;
|
|
float CASW_Parasite::s_fLastHarvesiteAttackSound = 0;
|
|
static const char *s_pParasiteAnimThink = "ParasiteAnimThink";
|
|
|
|
#define ASW_HARVESITE_ATTACK_SOUND_INTERVAL 1.5f
|
|
|
|
CASW_Parasite::CASW_Parasite( void )// : CASW_Alien()
|
|
{
|
|
m_bMidJump = false;
|
|
m_bCommittedToJump = false;
|
|
m_hEgg = NULL;
|
|
m_hMother = NULL;
|
|
m_pszAlienModelName = SWARM_PARASITE_MODEL;
|
|
m_nAlienCollisionGroup = ASW_COLLISION_GROUP_ALIEN;
|
|
m_bNeverRagdoll = true;
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS( asw_parasite, CASW_Parasite );
|
|
LINK_ENTITY_TO_CLASS( asw_parasite_defanged, CASW_Parasite );
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CASW_Parasite, DT_ASW_Parasite )
|
|
SendPropBool(SENDINFO(m_bStartIdleSound)),
|
|
SendPropBool(SENDINFO(m_bDoEggIdle)),
|
|
SendPropBool(SENDINFO(m_bInfesting)),
|
|
END_SEND_TABLE()
|
|
|
|
BEGIN_DATADESC( CASW_Parasite )
|
|
DEFINE_FIELD( m_bCommittedToJump, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bMidJump, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_vecCommittedJumpPos, FIELD_POSITION_VECTOR ),
|
|
DEFINE_FIELD( m_flNextNPCThink, FIELD_TIME ),
|
|
DEFINE_FIELD( m_flIgnoreWorldCollisionTime, FIELD_TIME ),
|
|
DEFINE_FIELD( m_bStartIdleSound, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_bInfesting, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_hEgg, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_bJumpFromEgg, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_flEggJumpDistance, FIELD_FLOAT ),
|
|
DEFINE_FIELD( m_bDefanged, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fSuicideTime, FIELD_FLOAT ),
|
|
DEFINE_THINKFUNC( LeapThink ),
|
|
DEFINE_THINKFUNC( InfestThink ),
|
|
DEFINE_ENTITYFUNC( LeapTouch ),
|
|
DEFINE_ENTITYFUNC( NormalTouch ),
|
|
END_DATADESC()
|
|
|
|
enum
|
|
{
|
|
SCHED_PARASITE_RANGE_ATTACK1 = LAST_ASW_ALIEN_SHARED_SCHEDULE,
|
|
SCHED_PARASITE_JUMP_FROM_EGG = LAST_ASW_ALIEN_SHARED_SCHEDULE+1,
|
|
};
|
|
|
|
enum
|
|
{
|
|
TASK_PARASITE_JUMP_FROM_EGG = LAST_ASW_ALIEN_SHARED_TASK,
|
|
};
|
|
|
|
int AE_PARASITE_INFEST_SPURT;
|
|
int AE_PARASITE_INFEST;
|
|
|
|
int AE_HEADCRAB_JUMPATTACK;
|
|
|
|
void CASW_Parasite::Spawn( void )
|
|
{
|
|
SetHullType(HULL_TINY);
|
|
|
|
BaseClass::Spawn();
|
|
|
|
SetModel( SWARM_PARASITE_MODEL);
|
|
|
|
if (FClassnameIs(this, "asw_parasite_defanged"))
|
|
{
|
|
m_bDefanged = true;
|
|
m_iHealth = ASWGameRules()->ModifyAlienHealthBySkillLevel(10);
|
|
SetBodygroup( 0, 1 );
|
|
m_fSuicideTime = gpGlobals->curtime + 60;
|
|
}
|
|
else
|
|
{
|
|
m_bDefanged = false;
|
|
m_iHealth = ASWGameRules()->ModifyAlienHealthBySkillLevel(25);
|
|
SetBodygroup( 0, 0 );
|
|
m_fSuicideTime = 0;
|
|
}
|
|
|
|
SetMoveType( MOVETYPE_STEP );
|
|
SetHullType(HULL_TINY);
|
|
SetCollisionGroup( ASW_COLLISION_GROUP_PARASITE );
|
|
SetViewOffset( Vector(6, 0, 11) ) ; // Position of the eyes relative to NPC's origin.
|
|
|
|
m_NPCState = NPC_STATE_NONE;
|
|
|
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
|
|
|
|
m_bInfesting = false;
|
|
|
|
}
|
|
|
|
void CASW_Parasite::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
if (GetMother())
|
|
GetMother()->ChildAlienKilled(this);
|
|
|
|
BaseClass::Event_Killed( info );
|
|
|
|
if ( !m_bDefanged && !m_bDoEggIdle.Get() && ( info.GetDamageType() & DMG_CLUB ) && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE )
|
|
{
|
|
CASW_Marine *pMarine = static_cast<CASW_Marine*>( info.GetAttacker() );
|
|
if ( pMarine && pMarine->IsInhabited() && pMarine->GetCommander() )
|
|
{
|
|
pMarine->GetCommander()->AwardAchievement( ACHIEVEMENT_ASW_MELEE_PARASITE );
|
|
if ( pMarine->GetMarineResource() )
|
|
{
|
|
pMarine->GetMarineResource()->m_bMeleeParasiteKill = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CASW_Parasite::~CASW_Parasite()
|
|
{
|
|
StopLoopingSounds();
|
|
if (GetEgg())
|
|
{
|
|
GetEgg()->ParasiteDied(this);
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::Precache( void )
|
|
{
|
|
|
|
PrecacheModel( SWARM_PARASITE_MODEL );
|
|
|
|
PrecacheScriptSound("ASW_Parasite.Death");
|
|
PrecacheScriptSound("ASW_Parasite.Attack");
|
|
PrecacheScriptSound("ASW_Parasite.Idle");
|
|
PrecacheScriptSound("ASW_Parasite.Pain");
|
|
PrecacheScriptSound("ASW_Parasite.Attack");
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
void CASW_Parasite::RunAnimation()
|
|
{
|
|
StudioFrameAdvance();
|
|
SetContextThink( &CASW_Parasite::RunAnimation, gpGlobals->curtime + 0.1f, s_pParasiteAnimThink );
|
|
}
|
|
|
|
float CASW_Parasite::GetIdealSpeed() const
|
|
{
|
|
// Hack to get rid of const needed to use the BaseAnimating calls.
|
|
CASW_Parasite *pNPC = const_cast<CASW_Parasite*>( this );
|
|
pNPC->UpdatePlaybackRate();
|
|
return BaseClass::GetIdealSpeed() * m_flPlaybackRate;
|
|
}
|
|
|
|
|
|
float CASW_Parasite::GetIdealAccel( ) const
|
|
{
|
|
return GetIdealSpeed() * 1.5f;
|
|
}
|
|
|
|
float CASW_Parasite::MaxYawSpeed( void )
|
|
{
|
|
return 128.0f;
|
|
|
|
switch ( GetActivity() )
|
|
{
|
|
case ACT_IDLE:
|
|
return 64.0f;
|
|
break;
|
|
|
|
case ACT_WALK:
|
|
return 64.0f;
|
|
break;
|
|
|
|
default:
|
|
case ACT_RUN:
|
|
return 64.0f;
|
|
break;
|
|
}
|
|
|
|
return 64.0f;
|
|
}
|
|
|
|
void CASW_Parasite::AlertSound()
|
|
{
|
|
IdleSound();
|
|
}
|
|
|
|
// defanged leaptouch sound
|
|
void CASW_Parasite::BiteSound( void )
|
|
{
|
|
EmitSound( "ASW_Parasite.Attack" );
|
|
}
|
|
|
|
void CASW_Parasite::PainSound( const CTakeDamageInfo &info )
|
|
{
|
|
if (gpGlobals->curtime > m_fNextPainSound)
|
|
{
|
|
if (m_bDefanged)
|
|
EmitSound("ASW_Parasite.Pain");
|
|
else
|
|
EmitSound("ASW_Parasite.Pain");
|
|
m_fNextPainSound = gpGlobals->curtime + 0.5f;
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::DeathSound( const CTakeDamageInfo &info )
|
|
{
|
|
EmitSound( "ASW_Parasite.Death" );
|
|
}
|
|
|
|
void CASW_Parasite::AttackSound()
|
|
{
|
|
if (m_bDefanged)
|
|
{
|
|
// since we have a lot of these, force a delay between playing sounds
|
|
if (gpGlobals->curtime > s_fLastHarvesiteAttackSound + ASW_HARVESITE_ATTACK_SOUND_INTERVAL)
|
|
{
|
|
EmitSound("ASW_Parasite.Attack");
|
|
s_fLastHarvesiteAttackSound = gpGlobals->curtime;
|
|
}
|
|
}
|
|
else
|
|
EmitSound("ASW_Parasite.Attack");
|
|
}
|
|
|
|
void CASW_Parasite::IdleSound()
|
|
{
|
|
if (!m_bDefanged) // defanged parasites don't make the idle sounds
|
|
m_bStartIdleSound = true;
|
|
}
|
|
|
|
void CASW_Parasite::PrescheduleThink( void )
|
|
{
|
|
BaseClass::PrescheduleThink();
|
|
|
|
if (( m_NPCState == NPC_STATE_COMBAT ) && ( random->RandomFloat( 0, 5 ) < 0.1 ))
|
|
{
|
|
IdleSound();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For innate melee attack
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
float CASW_Parasite::InnateRange1MinRange( void )
|
|
{
|
|
return ASW_PARASITE_MIN_JUMP_DIST;
|
|
}
|
|
|
|
float CASW_Parasite::InnateRange1MaxRange( void )
|
|
{
|
|
return ASW_PARASITE_MAX_JUMP_DIST;
|
|
}
|
|
|
|
int CASW_Parasite::RangeAttack1Conditions( float flDot, float flDist )
|
|
{
|
|
if ( gpGlobals->curtime < m_flNextAttack )
|
|
return 0;
|
|
|
|
if ( ( GetFlags() & FL_ONGROUND ) == false )
|
|
return 0;
|
|
|
|
// This code stops lots of headcrabs swarming you and blocking you
|
|
// whilst jumping up and down in your face over and over. It forces
|
|
// them to back up a bit. If this causes problems, consider using it
|
|
// for the fast headcrabs only, rather than just removing it.(sjb)
|
|
if ( flDist < ASW_PARASITE_MIN_JUMP_DIST )
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
|
|
if ( flDist > ASW_PARASITE_MAX_JUMP_DIST )
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
|
|
// Make sure the way is clear!
|
|
CBaseEntity *pEnemy = GetEnemy();
|
|
if( pEnemy )
|
|
{
|
|
bool bEnemyIsBullseye = ( dynamic_cast<CNPC_Bullseye *>(pEnemy) != NULL );
|
|
|
|
trace_t tr;
|
|
AI_TraceLine( EyePosition(), pEnemy->EyePosition(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if ( tr.m_pEnt != GetEnemy() )
|
|
{
|
|
if ( !bEnemyIsBullseye || tr.m_pEnt != NULL )
|
|
return COND_NONE;
|
|
}
|
|
|
|
if( GetEnemy()->EyePosition().z - 36.0f > GetAbsOrigin().z )
|
|
{
|
|
// Only run this test if trying to jump at a player who is higher up than me, else this
|
|
// code will always prevent a headcrab from jumping down at an enemy, and sometimes prevent it
|
|
// jumping just slightly up at an enemy.
|
|
Vector vStartHullTrace = GetAbsOrigin();
|
|
vStartHullTrace.z += 1.0;
|
|
|
|
Vector vEndHullTrace = GetEnemy()->EyePosition() - GetAbsOrigin();
|
|
vEndHullTrace.NormalizeInPlace();
|
|
vEndHullTrace *= 8.0;
|
|
vEndHullTrace += GetAbsOrigin();
|
|
|
|
AI_TraceHull( vStartHullTrace, vEndHullTrace,GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
|
|
|
|
if ( tr.m_pEnt != NULL && tr.m_pEnt != GetEnemy() )
|
|
{
|
|
return COND_TOO_CLOSE_TO_ATTACK;
|
|
}
|
|
}
|
|
}
|
|
|
|
return COND_CAN_RANGE_ATTACK1;
|
|
}
|
|
|
|
|
|
void CASW_Parasite::HandleAnimEvent( animevent_t *pEvent )
|
|
{
|
|
int nEvent = pEvent->Event();
|
|
|
|
if ( nEvent == AE_HEADCRAB_JUMPATTACK )
|
|
{
|
|
// Ignore if we're in mid air
|
|
if ( m_bMidJump )
|
|
return;
|
|
|
|
CBaseEntity *pEnemy = GetEnemy();
|
|
|
|
if ( pEnemy )
|
|
{
|
|
if ( m_bCommittedToJump )
|
|
{
|
|
JumpAttack( false, m_vecCommittedJumpPos );
|
|
}
|
|
else
|
|
{
|
|
// Jump at my enemy's eyes.
|
|
JumpAttack( false, pEnemy->EyePosition() );
|
|
}
|
|
|
|
m_bCommittedToJump = false;
|
|
|
|
}
|
|
else
|
|
{
|
|
// Jump hop, don't care where.
|
|
JumpAttack( true );
|
|
}
|
|
|
|
return;
|
|
}
|
|
else if ( nEvent == AE_PARASITE_INFEST_SPURT)
|
|
{
|
|
// spurt some blood from our front claws
|
|
Vector vecBloodPos;
|
|
if( GetAttachment( "leftclaw", vecBloodPos ) )
|
|
UTIL_ASW_BloodDrips( vecBloodPos, Vector(1,0,0), BLOOD_COLOR_RED, 1 );
|
|
if( GetAttachment( "rightclaw", vecBloodPos ) )
|
|
UTIL_ASW_BloodDrips( vecBloodPos, Vector(1,0,0), BLOOD_COLOR_RED, 1 );
|
|
return;
|
|
}
|
|
else if ( nEvent == AE_PARASITE_INFEST)
|
|
{
|
|
// we're done infesting, make ourselves vanish
|
|
FinishedInfesting();
|
|
return;
|
|
}
|
|
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
|
|
bool CASW_Parasite::ShouldGib( const CTakeDamageInfo &info )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool CASW_Parasite::CorpseGib( const CTakeDamageInfo &info )
|
|
{
|
|
|
|
CEffectData data;
|
|
|
|
data.m_vOrigin = WorldSpaceCenter();
|
|
data.m_vNormal = data.m_vOrigin - info.GetDamagePosition();
|
|
VectorNormalize( data.m_vNormal );
|
|
|
|
data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
|
|
data.m_flScale = clamp( data.m_flScale, 1, 3 );
|
|
data.m_fFlags = IsOnFire() ? ASW_GIBFLAG_ON_FIRE : 0;
|
|
|
|
if (m_bDefanged)
|
|
DispatchEffect( "HarvesiteGib", data );
|
|
else
|
|
DispatchEffect( "ParasiteGib", data );
|
|
|
|
return true;
|
|
}
|
|
|
|
void CASW_Parasite::BuildScheduleTestBits( void )
|
|
{
|
|
//Don't allow any modifications when scripted
|
|
if ( m_NPCState == NPC_STATE_SCRIPT )
|
|
return;
|
|
|
|
//Make sure we interrupt a run schedule if we can jump
|
|
if ( IsCurSchedule(SCHED_CHASE_ENEMY) )
|
|
{
|
|
SetCustomInterruptCondition( COND_ENEMY_UNREACHABLE );
|
|
}
|
|
|
|
//Interrupt any schedule unless already fleeing, burrowing, burrowed, or unburrowing.
|
|
if( GetFlags() & FL_ONGROUND )
|
|
{
|
|
if ( GetEnemy() == NULL )
|
|
{
|
|
SetCustomInterruptCondition( COND_HEAR_PHYSICS_DANGER );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::GatherEnemyConditions( CBaseEntity *pEnemy )
|
|
{
|
|
// Do the base class
|
|
BaseClass::GatherEnemyConditions( pEnemy );
|
|
|
|
// If we're not already too far away, check again
|
|
//TODO: Check to make sure we don't already have a condition set that removes the need for this
|
|
if ( HasCondition( COND_ENEMY_UNREACHABLE ) == false )
|
|
{
|
|
Vector predPosition;
|
|
UTIL_PredictedPosition( GetEnemy(), 1.0f, &predPosition );
|
|
|
|
Vector predDir = ( predPosition - GetAbsOrigin() );
|
|
float predLength = VectorNormalize( predDir );
|
|
|
|
// See if we'll be outside our effective target range
|
|
if ( predLength > 2000 ) // m_flEludeDistance
|
|
{
|
|
Vector predVelDir = ( predPosition - GetEnemy()->GetAbsOrigin() );
|
|
float predSpeed = VectorNormalize( predVelDir );
|
|
|
|
// See if the enemy is moving mostly away from us
|
|
if ( ( predSpeed > 512.0f ) && ( DotProduct( predVelDir, predDir ) > 0.0f ) )
|
|
{
|
|
// Mark the enemy as eluded and burrow away
|
|
ClearEnemyMemory();
|
|
SetEnemy( NULL );
|
|
SetIdealState( NPC_STATE_ALERT );
|
|
SetCondition( COND_ENEMY_UNREACHABLE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Does a jump attack at the given position.
|
|
// Input : bRandomJump - Just hop in a random direction.
|
|
// vecPos - Position to jump at, ignored if bRandom is set to true.
|
|
// bThrown -
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Parasite::JumpAttack( bool bRandomJump, const Vector &vecPos, bool bThrown )
|
|
{
|
|
Vector vecJumpVel;
|
|
if ( !bRandomJump )
|
|
{
|
|
float gravity = sv_gravity.GetFloat();
|
|
if ( gravity <= 1 )
|
|
{
|
|
gravity = 1;
|
|
}
|
|
|
|
// How fast does the headcrab need to travel to reach the position given gravity?
|
|
float flActualHeight = vecPos.z - GetAbsOrigin().z;
|
|
float height = flActualHeight;
|
|
if ( height < 16 )
|
|
{
|
|
height = 60; //16;
|
|
}
|
|
else
|
|
{
|
|
float flMaxHeight = bThrown ? 400 : 120;
|
|
if ( height > flMaxHeight )
|
|
{
|
|
height = flMaxHeight;
|
|
}
|
|
}
|
|
|
|
// overshoot the jump by an additional 8 inches
|
|
// NOTE: This calculation jumps at a position INSIDE the box of the enemy (player)
|
|
// so if you make the additional height too high, the crab can land on top of the
|
|
// enemy's head. If we want to jump high, we'll need to move vecPos to the surface/outside
|
|
// of the enemy's box.
|
|
|
|
float additionalHeight = 0;
|
|
if ( height < 32 )
|
|
{
|
|
additionalHeight = 8;
|
|
}
|
|
|
|
height += additionalHeight;
|
|
|
|
// NOTE: This equation here is from vf^2 = vi^2 + 2*a*d
|
|
float speed = sqrt( 2 * gravity * height );
|
|
float time = speed / gravity;
|
|
|
|
// add in the time it takes to fall the additional height
|
|
// So the impact takes place on the downward slope at the original height
|
|
time += sqrt( (2 * additionalHeight) / gravity );
|
|
|
|
// Scale the sideways velocity to get there at the right time
|
|
VectorSubtract( vecPos, GetAbsOrigin(), vecJumpVel );
|
|
vecJumpVel /= time;
|
|
|
|
// Speed to offset gravity at the desired height.
|
|
vecJumpVel.z = speed;
|
|
|
|
// Don't jump too far/fast.
|
|
float flJumpSpeed = vecJumpVel.Length();
|
|
float flMaxSpeed = bThrown ? 1000.0f : 650.0f;
|
|
if ( flJumpSpeed > flMaxSpeed )
|
|
{
|
|
vecJumpVel *= flMaxSpeed / flJumpSpeed;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Jump hop, don't care where.
|
|
//
|
|
Vector forward, up;
|
|
AngleVectors( GetLocalAngles(), &forward, NULL, &up );
|
|
vecJumpVel = Vector( forward.x, forward.y, up.z ) * 350;
|
|
}
|
|
|
|
AttackSound();
|
|
Leap( vecJumpVel );
|
|
}
|
|
|
|
void CASW_Parasite::Leap( const Vector &vecVel )
|
|
{
|
|
SetTouch( &CASW_Parasite::LeapTouch );
|
|
|
|
SetCondition( COND_FLOATING_OFF_GROUND );
|
|
SetGroundEntity( NULL );
|
|
|
|
m_flIgnoreWorldCollisionTime = gpGlobals->curtime + PARASITE_IGNORE_WORLD_COLLISION_TIME;
|
|
|
|
if( HasHeadroom() )
|
|
{
|
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
|
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0, 0, 1 ) );
|
|
}
|
|
|
|
SetAbsVelocity( vecVel );
|
|
|
|
// Think every frame so the player sees the headcrab where he actually is...
|
|
m_bMidJump = true;
|
|
SetThink( &CASW_Parasite::LeapThink );
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
|
|
void CASW_Parasite::LeapThink( void )
|
|
{
|
|
if (gpGlobals->curtime > m_flNextNPCThink)
|
|
{
|
|
NPCThink();
|
|
m_flNextNPCThink = gpGlobals->curtime + 0.1;
|
|
}
|
|
|
|
if( GetFlags() & FL_ONGROUND )
|
|
{
|
|
SetThink( &CASW_Parasite::CallNPCThink );
|
|
SetNextThink( gpGlobals->curtime + 0.1 );
|
|
return;
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime );
|
|
}
|
|
|
|
static const char *s_pStartInfestContext = "StartInfestContext";
|
|
|
|
void CASW_Parasite::NormalTouch( CBaseEntity* pOther )
|
|
{
|
|
if ( !m_bDefanged && !m_hPrepareToInfest.Get() && pOther && ( pOther->Classify() == CLASS_ASW_COLONIST || pOther->Classify() == CLASS_ASW_MARINE ) )
|
|
{
|
|
SetCollisionGroup( ASW_COLLISION_GROUP_BUZZER ); // stop collisions with the marine/colonist
|
|
|
|
if ( !CheckInfestTarget( pOther ) )
|
|
{
|
|
// Hop away in a random direction!
|
|
JumpAttack( true );
|
|
return;
|
|
}
|
|
// infest after a delay equal to the default interpolation time for aliens. This stops the parasite teleporting to its target immediately.
|
|
m_hPrepareToInfest = pOther;
|
|
SetContextThink( &CASW_Parasite::StartInfestation, gpGlobals->curtime + 0.2f, s_pStartInfestContext );
|
|
}
|
|
}
|
|
|
|
bool CASW_Parasite::CheckInfestTarget( CBaseEntity *pOther )
|
|
{
|
|
CASW_Marine* pMarine = CASW_Marine::AsMarine( pOther );
|
|
if ( pOther )
|
|
{
|
|
// if marine has electrified armour on, that protects him from infestation
|
|
if ( pMarine->IsElectrifiedArmorActive() )
|
|
{
|
|
CTakeDamageInfo info( NULL, NULL, Vector(0,0,0), GetAbsOrigin(), GetHealth() * 2, DMG_SHOCK );
|
|
TakeDamage(info);
|
|
return false;
|
|
}
|
|
|
|
if ( pMarine->m_takedamage == DAMAGE_NO )
|
|
{
|
|
// We're in the death cam... no fair infesting there
|
|
return false;
|
|
}
|
|
|
|
if ( IsOnFire() )
|
|
{
|
|
// don't actually infest if we're on fire, since we'll die very shortly
|
|
return false;
|
|
}
|
|
|
|
if ( pMarine->m_iJumpJetting.Get() != 0 )
|
|
{
|
|
// marine is in the middle of a jump jet or blink, don't infest him
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else if ( pOther->Classify() == CLASS_ASW_COLONIST )
|
|
{
|
|
return !IsOnFire();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CASW_Parasite::StartInfestation()
|
|
{
|
|
CASW_Marine* pMarine = CASW_Marine::AsMarine( m_hPrepareToInfest.Get() );
|
|
if ( pMarine )
|
|
{
|
|
InfestMarine( pMarine );
|
|
}
|
|
else
|
|
{
|
|
CASW_Colonist *pColonist = dynamic_cast<CASW_Colonist*>( m_hPrepareToInfest.Get() );
|
|
if ( pColonist )
|
|
{
|
|
InfestColonist( pColonist );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::InfestThink( void )
|
|
{
|
|
SetNextThink( gpGlobals->curtime + 0.1f );
|
|
|
|
if ( !GetModelPtr() )
|
|
return;
|
|
|
|
StudioFrameAdvance();
|
|
|
|
DispatchAnimEvents( this );
|
|
|
|
CASW_Marine *pMarine = dynamic_cast<CASW_Marine*>(GetParent());
|
|
if ( !pMarine || !pMarine->IsInfested() || pMarine->IsEffectActive( EF_NODRAW ) )
|
|
{
|
|
FinishedInfesting();
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::InfestMarine(CASW_Marine* pMarine)
|
|
{
|
|
if ( !pMarine )
|
|
return;
|
|
|
|
pMarine->BecomeInfested(this);
|
|
|
|
// attach
|
|
int attachment = pMarine->LookupAttachment( "chest" );
|
|
if ( attachment )
|
|
{
|
|
SetSolid( SOLID_NONE );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
QAngle current(0,0,0);
|
|
|
|
Vector diff = pMarine->GetAbsOrigin() - GetAbsOrigin();
|
|
float angle = UTIL_VecToYaw(diff);
|
|
angle -= pMarine->GetAbsAngles()[YAW]; // get the diff between our angle from the marine and the marine's facing;
|
|
|
|
current = GetAbsAngles();
|
|
|
|
Vector vAttachmentPos;
|
|
pMarine->GetAttachment( attachment, vAttachmentPos );
|
|
|
|
// Make sure it's near the chest attachement before parenting
|
|
Teleport( &vAttachmentPos, &vec3_angle, &vec3_origin );
|
|
|
|
SetParent( pMarine, attachment );
|
|
|
|
float flRaise = RandomFloat( 15.0f, 18.0f );
|
|
float flForward = RandomFloat( -3.0f, 0.0f );
|
|
float flSide = RandomFloat( 1.75f, 3.0f ) * ( RandomInt( 0, 1 ) == 0 ? 1.0f : -1.0f );
|
|
|
|
if ( asw_debug_alien_damage.GetBool() )
|
|
{
|
|
Msg( "INFEST: flRaise = %f flForward = %f flSide = %f yaw = %f\n", flRaise, flForward, flSide, angle + asw_infest_angle.GetFloat() );
|
|
}
|
|
SetLocalOrigin( Vector( flForward, flSide, flRaise ) );
|
|
SetLocalAngles( QAngle( asw_infest_pitch.GetFloat(), angle + asw_infest_angle.GetFloat(), 0 ) );
|
|
// play our infesting anim
|
|
if ( asw_parasite_inside.GetBool() )
|
|
{
|
|
SetActivity(ACT_RANGE_ATTACK2);
|
|
}
|
|
else
|
|
{
|
|
int iInfestAttack = LookupSequence("Infest_attack");
|
|
if (GetSequence() != iInfestAttack)
|
|
{
|
|
ResetSequence(iInfestAttack);
|
|
}
|
|
}
|
|
|
|
AddFlag( FL_NOTARGET );
|
|
SetThink( &CASW_Parasite::InfestThink );
|
|
SetTouch( NULL );
|
|
m_bInfesting = true;
|
|
}
|
|
else
|
|
{
|
|
FinishedInfesting();
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::InfestColonist(CASW_Colonist* pColonist)
|
|
{
|
|
if (m_bDefanged || !pColonist) // no infesting if we've been defanged
|
|
return;
|
|
|
|
if (!IsOnFire()) // don't actually infest if we're on fire, since we'll die very shortly
|
|
pColonist->BecomeInfested(this);
|
|
|
|
// attach
|
|
int attachment = pColonist->LookupAttachment( "chest" );
|
|
if ( attachment )
|
|
{
|
|
//SetAbsAngles( GetOwnerEntity()->GetAbsAngles() );
|
|
SetSolid( SOLID_NONE );
|
|
SetMoveType( MOVETYPE_NONE );
|
|
QAngle current(0,0,0);
|
|
|
|
Vector diff = pColonist->GetAbsOrigin() - GetAbsOrigin();
|
|
float angle = UTIL_VecToYaw(diff);
|
|
angle -= pColonist->GetAbsAngles()[YAW]; // get the diff between our angle from the marine and the marine's facing;
|
|
|
|
current = GetAbsAngles();
|
|
|
|
SetParent( pColonist, attachment );
|
|
Vector vecPosition;
|
|
float fRaise = random->RandomFloat(0,20);
|
|
|
|
SetLocalOrigin( Vector( -fRaise * 0.2f, 0, fRaise ) );
|
|
SetLocalAngles( QAngle( 0, angle + asw_infest_angle.GetFloat(), 0 ) );
|
|
// play our infesting anim
|
|
if ( asw_parasite_inside.GetBool() )
|
|
{
|
|
SetActivity(ACT_RANGE_ATTACK2);
|
|
}
|
|
else
|
|
{
|
|
int iInfestAttack = LookupSequence("Infest_attack");
|
|
if (GetSequence() != iInfestAttack)
|
|
{
|
|
ResetSequence(iInfestAttack);
|
|
}
|
|
}
|
|
// don't do anymore thinking - need to think still to animate?
|
|
AddFlag( FL_NOTARGET );
|
|
SetThink( &CASW_Parasite::InfestThink );
|
|
SetTouch( NULL );
|
|
m_bInfesting = true;
|
|
}
|
|
else
|
|
{
|
|
FinishedInfesting();
|
|
}
|
|
}
|
|
|
|
// we're done clawing our way in, remove the AI
|
|
void CASW_Parasite::FinishedInfesting()
|
|
{
|
|
StopLoopingSounds();
|
|
|
|
// notify everything that needs to know about our death
|
|
if (ASWGameRules())
|
|
{
|
|
CTakeDamageInfo info;
|
|
ASWGameRules()->AlienKilled(this, info);
|
|
}
|
|
|
|
if (m_hSpawner.Get())
|
|
m_hSpawner->AlienKilled(this);
|
|
|
|
if (GetMother())
|
|
GetMother()->ChildAlienKilled(this);
|
|
|
|
UTIL_Remove( this );
|
|
SetThink( NULL ); //We're going away, so don't think anymore.
|
|
SetTouch( NULL );
|
|
}
|
|
|
|
void CASW_Parasite::SetEgg(CASW_Egg* pEgg)
|
|
{
|
|
m_hEgg = pEgg;
|
|
}
|
|
|
|
CASW_Egg* CASW_Parasite::GetEgg()
|
|
{
|
|
return dynamic_cast<CASW_Egg*>(m_hEgg.Get());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: LeapTouch - this is the headcrab's touch function when it is in the air.
|
|
// Input : *pOther -
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CASW_Parasite::LeapTouch( CBaseEntity *pOther )
|
|
{
|
|
m_bMidJump = false;
|
|
|
|
if ( IRelationType( pOther ) == D_HT )
|
|
{
|
|
if (m_bDefanged)
|
|
{
|
|
if ( pOther->m_takedamage != DAMAGE_NO )
|
|
{
|
|
BiteSound();
|
|
TouchDamage( pOther );
|
|
//ClearSchedule( "About to gib self" );
|
|
// gib us
|
|
CTakeDamageInfo info(NULL, NULL, Vector(0,0,0), GetAbsOrigin(), GetHealth() * 2,
|
|
DMG_ACID);
|
|
TakeDamage(info);
|
|
SetSchedule( SCHED_DIE );
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//ImpactSound();
|
|
}
|
|
}
|
|
// Don't hit if back on ground
|
|
//if ( !( GetFlags() & FL_ONGROUND ) && m_bDefanged) // if we're defanged, don't infest, just do some combat damage
|
|
//{
|
|
|
|
//}
|
|
//else
|
|
//{
|
|
//ImpactSound();
|
|
//}
|
|
}
|
|
else if( !(GetFlags() & FL_ONGROUND) )
|
|
{
|
|
// Still in the air...
|
|
if( gpGlobals->curtime < m_flIgnoreWorldCollisionTime )
|
|
{
|
|
// Headcrabs try to ignore the world, static props, and friends for a
|
|
// fraction of a second after they jump. This is because they often brush
|
|
// doorframes or props as they leap, and touching those objects turns off
|
|
// this touch function, which can cause them to hit the player and not bite.
|
|
// A timer probably isn't the best way to fix this, but it's one of our
|
|
// safer options at this point (sjb).
|
|
return;
|
|
}
|
|
|
|
if( !pOther->IsSolid() )
|
|
{
|
|
// Touching a trigger or something.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// make sure we're solid
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
// Shut off the touch function.
|
|
SetTouch( &CASW_Parasite::NormalTouch );
|
|
SetThink ( &CASW_Parasite::CallNPCThink );
|
|
|
|
SetCollisionGroup( ASW_COLLISION_GROUP_PARASITE );
|
|
|
|
// if we hit a marine, infest him and go away
|
|
NormalTouch( pOther );
|
|
}
|
|
|
|
int CASW_Parasite::CalcDamageInfo( CTakeDamageInfo *pInfo )
|
|
{
|
|
Assert(ASWGameRules());
|
|
pInfo->Set( this, this, asw_parasite_defanged_damage.GetFloat(), DMG_ACID );
|
|
CalculateMeleeDamageForce( pInfo, GetAbsVelocity(), GetAbsOrigin() );
|
|
return pInfo->GetDamage();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Deal the damage from the defanged parasite's touch attack.
|
|
//-----------------------------------------------------------------------------
|
|
void CASW_Parasite::TouchDamage( CBaseEntity *pOther )
|
|
{
|
|
CTakeDamageInfo info;
|
|
CalcDamageInfo( &info );
|
|
int damage = ASWGameRules()->ModifyAlienDamageBySkillLevel(info.GetDamage());
|
|
info.SetDamage(damage);
|
|
pOther->TakeDamage( info );
|
|
EmitSound("ASWFire.AcidBurn");
|
|
CEffectData data;
|
|
data.m_vOrigin = GetAbsOrigin();
|
|
data.m_nOtherEntIndex = pOther->entindex();
|
|
DispatchEffect( "ASWAcidBurn", data );
|
|
}
|
|
|
|
bool CASW_Parasite::HasHeadroom()
|
|
{
|
|
trace_t tr;
|
|
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, 1 ), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
|
|
|
|
#if 0
|
|
if( tr.fraction == 1.0f )
|
|
{
|
|
Msg("Headroom\n");
|
|
}
|
|
else
|
|
{
|
|
Msg("NO Headroom\n");
|
|
}
|
|
#endif
|
|
|
|
return (tr.fraction == 1.0);
|
|
}
|
|
|
|
void CASW_Parasite::StartTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
/*
|
|
case TASK_STOP_MOVING:
|
|
{
|
|
if (m_bDoEggIdle)
|
|
{
|
|
SetIdealActivity( (Activity) ACT_ASW_EGG_IDLE );
|
|
TaskComplete();
|
|
return;
|
|
}
|
|
if ( ( GetNavigator()->IsGoalSet() && GetNavigator()->IsGoalActive() ) || GetNavType() == NAV_JUMP )
|
|
{
|
|
DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
|
|
if ( pTask->flTaskData == 1 )
|
|
{
|
|
DbgNavMsg( this, "Initiating stopping path\n" );
|
|
GetNavigator()->StopMoving( false );
|
|
}
|
|
else
|
|
{
|
|
GetNavigator()->ClearGoal();
|
|
}
|
|
|
|
// E3 Hack
|
|
if (LookupPoseParameter( "move_yaw") >= 0)
|
|
{
|
|
SetPoseParameter( "move_yaw", 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( pTask->flTaskData == 1 && GetNavigator()->SetGoalFromStoppingPath() )
|
|
{
|
|
DbgNavMsg( this, "Start TASK_STOP_MOVING\n" );
|
|
DbgNavMsg( this, "Initiating stopping path\n" );
|
|
}
|
|
else
|
|
{
|
|
GetNavigator()->ClearGoal();
|
|
if (m_bDoEggIdle)
|
|
SetIdealActivity( (Activity) ACT_ASW_EGG_IDLE );
|
|
else
|
|
SetIdealActivity( GetStoppedActivity() );
|
|
|
|
TaskComplete();
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
case TASK_PARASITE_JUMP_FROM_EGG:
|
|
{
|
|
DoJumpFromEgg();
|
|
break;
|
|
}
|
|
case TASK_RANGE_ATTACK1:
|
|
{
|
|
SetIdealActivity( ACT_RANGE_ATTACK1 );
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
BaseClass::StartTask( pTask );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::RunTask( const Task_t *pTask )
|
|
{
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_RANGE_ATTACK1:
|
|
case TASK_RANGE_ATTACK2:
|
|
{
|
|
if ( IsActivityFinished() )
|
|
{
|
|
TaskComplete();
|
|
m_bMidJump = false;
|
|
SetTouch( NULL );
|
|
SetThink( &CASW_Parasite::CallNPCThink );
|
|
SetIdealActivity( ACT_IDLE );
|
|
}
|
|
break;
|
|
}
|
|
case TASK_PARASITE_JUMP_FROM_EGG:
|
|
GetMotor()->UpdateYaw();
|
|
if ( FacingIdeal() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
default:
|
|
BaseClass::RunTask( pTask );
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::UpdatePlaybackRate()
|
|
{
|
|
if ( GetActivity() != ACT_RUN )
|
|
{
|
|
m_flPlaybackRate = 1.0f;
|
|
return;
|
|
}
|
|
|
|
float boost = asw_parasite_speedboost.GetFloat();
|
|
switch (ASWGameRules()->GetSkillLevel())
|
|
{
|
|
case 5: boost *= asw_alien_speed_scale_insane.GetFloat(); break;
|
|
case 4: boost *= asw_alien_speed_scale_insane.GetFloat(); break;
|
|
case 3: boost *= asw_alien_speed_scale_hard.GetFloat(); break;
|
|
case 2: boost *= asw_alien_speed_scale_normal.GetFloat(); break;
|
|
default: boost *= asw_alien_speed_scale_easy.GetFloat(); break;
|
|
}
|
|
m_flPlaybackRate = boost;
|
|
}
|
|
|
|
int CASW_Parasite::TranslateSchedule( int scheduleType )
|
|
{
|
|
switch( scheduleType )
|
|
{
|
|
case SCHED_RANGE_ATTACK1:
|
|
return SCHED_PARASITE_RANGE_ATTACK1;
|
|
}
|
|
|
|
return BaseClass::TranslateSchedule( scheduleType );
|
|
}
|
|
|
|
int CASW_Parasite::SelectSchedule()
|
|
{
|
|
if ( m_bJumpFromEgg )
|
|
{
|
|
m_bJumpFromEgg = false;
|
|
return SCHED_PARASITE_JUMP_FROM_EGG;
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
void CASW_Parasite::SetJumpFromEgg(bool b, float flJumpDistance)
|
|
{
|
|
m_bJumpFromEgg = true;
|
|
m_flEggJumpDistance = flJumpDistance;
|
|
}
|
|
|
|
void CASW_Parasite::DoJumpFromEgg()
|
|
{
|
|
SetContextThink( NULL, gpGlobals->curtime, s_pParasiteAnimThink );
|
|
SetParent( NULL );
|
|
SetAbsOrigin( GetAbsOrigin() + Vector( 0, 0, 30 ) ); // TODO: position parasite at where his 'idle in egg' animation has him. This has to be some distance off the ground, else the jump will immediately end.
|
|
Vector dir = vec3_origin;
|
|
AngleVectors( GetAbsAngles(), &dir );
|
|
//Vector vecJumpPos = GetAbsOrigin()+ Vector(19,0,60)+ dir * m_flEggJumpDistance;
|
|
Vector vecJumpPos = GetAbsOrigin() + dir * m_flEggJumpDistance;
|
|
|
|
SetActivity( ACT_RANGE_ATTACK1 );
|
|
StudioFrameAdvanceManual( 0.0 );
|
|
SetParent( NULL );
|
|
RemoveFlag( FL_FLY );
|
|
AddEffects( EF_NOINTERP );
|
|
m_bDoEggIdle = false;
|
|
|
|
GetMotor()->SetIdealYaw( GetAbsAngles().y );
|
|
|
|
JumpAttack( false, vecJumpPos, false );
|
|
}
|
|
|
|
void CASW_Parasite::IdleInEgg(bool b)
|
|
{
|
|
if (b)
|
|
{
|
|
SetActivity((Activity) ACT_ASW_EGG_IDLE);
|
|
SetIdealActivity((Activity) ACT_ASW_EGG_IDLE);
|
|
SetContextThink( &CASW_Parasite::RunAnimation, gpGlobals->curtime + 0.1f, s_pParasiteAnimThink );
|
|
}
|
|
m_bDoEggIdle = b;
|
|
}
|
|
|
|
Activity CASW_Parasite::TranslateActivity( Activity baseAct, Activity *pIdealWeaponActivity )
|
|
{
|
|
Activity translated = BaseClass::TranslateActivity(baseAct, pIdealWeaponActivity);
|
|
/*
|
|
if (translated == ACT_IDLE && m_bDoEggIdle)
|
|
{
|
|
Msg("Translated idle to egg idle\n");
|
|
return (Activity) ACT_ASW_EGG_IDLE;
|
|
}
|
|
else if (translated == ACT_IDLE)
|
|
{
|
|
Msg("Go an act idle, but not translating it as we're not set to do an egg idle\n");
|
|
}
|
|
*/
|
|
return translated;
|
|
}
|
|
|
|
void CASW_Parasite::SetMother(CASW_Alien* spawner)
|
|
{
|
|
m_hMother = spawner;
|
|
}
|
|
|
|
CASW_Alien* CASW_Parasite::GetMother()
|
|
{
|
|
return dynamic_cast<CASW_Alien*>(m_hMother.Get());
|
|
}
|
|
|
|
int CASW_Parasite::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
|
{
|
|
int result = 0;
|
|
|
|
// scale damage up while in the air
|
|
if (m_bMidJump)
|
|
{
|
|
CTakeDamageInfo newDamage = info;
|
|
newDamage.ScaleDamage(10.0f);
|
|
result = BaseClass::OnTakeDamage_Alive(newDamage);
|
|
}
|
|
else
|
|
{
|
|
result = BaseClass::OnTakeDamage_Alive(info);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void CASW_Parasite::UpdateSleepState(bool bInPVS)
|
|
{
|
|
if (m_bDoEggIdle)
|
|
{
|
|
int iEggIdle = LookupSequence("Egg_Idle");
|
|
if (GetSequence() != iEggIdle)
|
|
{
|
|
ResetSequence(iEggIdle);
|
|
}
|
|
}
|
|
BaseClass::UpdateSleepState(bInPVS);
|
|
}
|
|
|
|
void CASW_Parasite::SetHealthByDifficultyLevel()
|
|
{
|
|
if (FClassnameIs(this, "asw_parasite_defanged"))
|
|
{
|
|
SetHealth(ASWGameRules()->ModifyAlienHealthBySkillLevel(10));
|
|
}
|
|
else
|
|
{
|
|
SetHealth(ASWGameRules()->ModifyAlienHealthBySkillLevel(30));
|
|
}
|
|
}
|
|
|
|
void CASW_Parasite::NPCThink()
|
|
{
|
|
BaseClass::NPCThink();
|
|
|
|
if ( GetEfficiency() < AIE_DORMANT && GetSleepState() == AISS_AWAKE
|
|
&& !m_bDefanged && gpGlobals->curtime > s_fNextSpottedChatterTime && GetEnemy())
|
|
{
|
|
CASW_Marine *pMarine = UTIL_ASW_Marine_Can_Chatter_Spot(this);
|
|
if (pMarine)
|
|
{
|
|
pMarine->GetMarineSpeech()->Chatter(CHATTER_PARASITE);
|
|
s_fNextSpottedChatterTime = gpGlobals->curtime + 30.0f;
|
|
}
|
|
else
|
|
s_fNextSpottedChatterTime = gpGlobals->curtime + 1.0f;
|
|
}
|
|
if (m_bDefanged && m_fSuicideTime < gpGlobals->curtime && GetEnemy() == NULL)
|
|
{
|
|
// suicide!
|
|
CTakeDamageInfo info(NULL, NULL, Vector(0,0,0), GetAbsOrigin(), GetHealth() * 2,
|
|
DMG_ACID);
|
|
TakeDamage(info);
|
|
}
|
|
}
|
|
|
|
// can't be seen by AI marines when infesting someone
|
|
bool CASW_Parasite::CanBeSeenBy( CAI_BaseNPC *pNPC )
|
|
{
|
|
return !m_bInfesting;
|
|
}
|
|
|
|
AI_BEGIN_CUSTOM_NPC( asw_parasite, CASW_Parasite )
|
|
DECLARE_ANIMEVENT( AE_HEADCRAB_JUMPATTACK )
|
|
DECLARE_ANIMEVENT( AE_PARASITE_INFEST_SPURT )
|
|
DECLARE_ANIMEVENT( AE_PARASITE_INFEST )
|
|
DECLARE_TASK( TASK_PARASITE_JUMP_FROM_EGG )
|
|
DECLARE_ACTIVITY( ACT_ASW_EGG_IDLE )
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_PARASITE_RANGE_ATTACK1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_RANGE_ATTACK1 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_WAIT_RANDOM 0.5"
|
|
""
|
|
" Interrupts"
|
|
" COND_ENEMY_OCCLUDED"
|
|
" COND_NO_PRIMARY_AMMO"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_PARASITE_JUMP_FROM_EGG,
|
|
|
|
" Tasks"
|
|
" TASK_PARASITE_JUMP_FROM_EGG 0"
|
|
""
|
|
" Interrupts"
|
|
)
|
|
AI_END_CUSTOM_NPC()
|