2024-08-29 19:18:30 -04:00
// 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
2024-08-29 19:27:02 -04:00
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 " ) ;
2024-08-29 19:18:30 -04:00
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 ( ) )
{
2024-08-29 20:12:35 -04:00
case 5 : boost * = asw_alien_speed_scale_insane . GetFloat ( ) ; break ;
2024-08-29 19:18:30 -04:00
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 ( )