You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1297 lines
32 KiB
1297 lines
32 KiB
// 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()
|
|
|