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.
1167 lines
30 KiB
1167 lines
30 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Cute hound like Alien. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "game.h" |
|
#include "ai_default.h" |
|
#include "ai_schedule.h" |
|
#include "ai_hull.h" |
|
#include "ai_route.h" |
|
#include "ai_hint.h" |
|
#include "ai_navigator.h" |
|
#include "ai_senses.h" |
|
#include "npcevent.h" |
|
#include "animation.h" |
|
#include "hl1_npc_bullsquid.h" |
|
#include "gib.h" |
|
#include "soundent.h" |
|
#include "ndebugoverlay.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
#include "hl1_grenade_spit.h" |
|
#include "util.h" |
|
#include "shake.h" |
|
#include "movevars_shared.h" |
|
#include "decals.h" |
|
#include "hl2_shareddefs.h" |
|
#include "ammodef.h" |
|
|
|
#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve |
|
|
|
ConVar sk_bullsquid_health ( "sk_bullsquid_health", "0" ); |
|
ConVar sk_bullsquid_dmg_bite ( "sk_bullsquid_dmg_bite", "0" ); |
|
ConVar sk_bullsquid_dmg_whip ( "sk_bullsquid_dmg_whip", "0" ); |
|
extern ConVar sk_bullsquid_dmg_spit; |
|
|
|
//========================================================= |
|
// monster-specific schedule types |
|
//========================================================= |
|
enum |
|
{ |
|
SCHED_SQUID_HURTHOP = LAST_SHARED_SCHEDULE, |
|
SCHED_SQUID_SEECRAB, |
|
SCHED_SQUID_EAT, |
|
SCHED_SQUID_SNIFF_AND_EAT, |
|
SCHED_SQUID_WALLOW, |
|
SCHED_SQUID_CHASE_ENEMY, |
|
}; |
|
|
|
//========================================================= |
|
// monster-specific tasks |
|
//========================================================= |
|
enum |
|
{ |
|
TASK_SQUID_HOPTURN = LAST_SHARED_TASK, |
|
TASK_SQUID_EAT, |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Squid Conditions |
|
//----------------------------------------------------------------------------- |
|
enum |
|
{ |
|
COND_SQUID_SMELL_FOOD = LAST_SHARED_CONDITION + 1, |
|
}; |
|
|
|
|
|
//========================================================= |
|
// Monster's Anim Events Go Here |
|
//========================================================= |
|
#define BSQUID_AE_SPIT ( 1 ) |
|
#define BSQUID_AE_BITE ( 2 ) |
|
#define BSQUID_AE_BLINK ( 3 ) |
|
#define BSQUID_AE_TAILWHIP ( 4 ) |
|
#define BSQUID_AE_HOP ( 5 ) |
|
#define BSQUID_AE_THROW ( 6 ) |
|
|
|
LINK_ENTITY_TO_CLASS( monster_bullchicken, CNPC_Bullsquid ); |
|
|
|
int ACT_SQUID_EXCITED; |
|
int ACT_SQUID_EAT; |
|
int ACT_SQUID_DETECT_SCENT; |
|
int ACT_SQUID_INSPECT_FLOOR; |
|
|
|
//========================================================= |
|
// Bullsquid's spit projectile |
|
//========================================================= |
|
class CSquidSpit : public CBaseEntity |
|
{ |
|
DECLARE_CLASS( CSquidSpit, CBaseEntity ); |
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
|
|
static void Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity ); |
|
void Touch( CBaseEntity *pOther ); |
|
void Animate( void ); |
|
|
|
int m_nSquidSpitSprite; |
|
|
|
DECLARE_DATADESC(); |
|
|
|
void SetSprite( CBaseEntity *pSprite ) |
|
{ |
|
m_hSprite = pSprite; |
|
} |
|
|
|
CBaseEntity *GetSprite( void ) |
|
{ |
|
return m_hSprite.Get(); |
|
} |
|
|
|
private: |
|
EHANDLE m_hSprite; |
|
|
|
|
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( squidspit, CSquidSpit ); |
|
|
|
BEGIN_DATADESC( CSquidSpit ) |
|
DEFINE_FIELD( m_nSquidSpitSprite, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_hSprite, FIELD_EHANDLE ), |
|
END_DATADESC() |
|
|
|
|
|
void CSquidSpit::Precache( void ) |
|
{ |
|
m_nSquidSpitSprite = PrecacheModel("sprites/bigspit.vmt");// client side spittle. |
|
|
|
PrecacheScriptSound( "NPC_BigMomma.SpitTouch1" ); |
|
PrecacheScriptSound( "NPC_BigMomma.SpitHit1" ); |
|
PrecacheScriptSound( "NPC_BigMomma.SpitHit2" ); |
|
} |
|
|
|
void CSquidSpit:: Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
SetMoveType ( MOVETYPE_FLY ); |
|
SetClassname( "squidspit" ); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
|
|
m_nRenderMode = kRenderTransAlpha; |
|
SetRenderColorA( 255 ); |
|
SetModel( "" ); |
|
|
|
SetSprite( CSprite::SpriteCreate( "sprites/bigspit.vmt", GetAbsOrigin(), true ) ); |
|
|
|
UTIL_SetSize( this, Vector( 0, 0, 0), Vector(0, 0, 0) ); |
|
|
|
SetCollisionGroup( HL2COLLISION_GROUP_SPIT ); |
|
} |
|
|
|
void CSquidSpit::Shoot( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity ) |
|
{ |
|
CSquidSpit *pSpit = CREATE_ENTITY( CSquidSpit, "squidspit" ); |
|
pSpit->Spawn(); |
|
|
|
UTIL_SetOrigin( pSpit, vecStart ); |
|
pSpit->SetAbsVelocity( vecVelocity ); |
|
pSpit->SetOwnerEntity( pOwner ); |
|
|
|
CSprite *pSprite = (CSprite*)pSpit->GetSprite(); |
|
|
|
if ( pSprite ) |
|
{ |
|
pSprite->SetAttachment( pSpit, 0 ); |
|
pSprite->SetOwnerEntity( pSpit ); |
|
|
|
pSprite->SetScale( 0.5 ); |
|
pSprite->SetTransparency( pSpit->m_nRenderMode, pSpit->m_clrRender->r, pSpit->m_clrRender->g, pSpit->m_clrRender->b, pSpit->m_clrRender->a, pSpit->m_nRenderFX ); |
|
} |
|
|
|
|
|
CPVSFilter filter( vecStart ); |
|
|
|
VectorNormalize( vecVelocity ); |
|
te->SpriteSpray( filter, 0.0, &vecStart , &vecVelocity, pSpit->m_nSquidSpitSprite, 210, 25, 15 ); |
|
} |
|
|
|
void CSquidSpit::Touch ( CBaseEntity *pOther ) |
|
{ |
|
trace_t tr; |
|
int iPitch; |
|
|
|
if ( pOther->GetSolidFlags() & FSOLID_TRIGGER ) |
|
return; |
|
|
|
if ( pOther->GetCollisionGroup() == HL2COLLISION_GROUP_SPIT) |
|
{ |
|
return; |
|
} |
|
|
|
// splat sound |
|
iPitch = random->RandomFloat( 90, 110 ); |
|
|
|
EmitSound( "NPC_BigMomma.SpitTouch1" ); |
|
|
|
switch ( random->RandomInt( 0, 1 ) ) |
|
{ |
|
case 0: |
|
EmitSound( "NPC_BigMomma.SpitHit1" ); |
|
break; |
|
case 1: |
|
EmitSound( "NPC_BigMomma.SpitHit2" ); |
|
break; |
|
} |
|
|
|
if ( !pOther->m_takedamage ) |
|
{ |
|
// make a splat on the wall |
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + GetAbsVelocity() * 10, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
UTIL_DecalTrace(&tr, "BeerSplash" ); |
|
|
|
// make some flecks |
|
CPVSFilter filter( tr.endpos ); |
|
|
|
te->SpriteSpray( filter, 0.0, &tr.endpos, &tr.plane.normal, m_nSquidSpitSprite, 30, 8, 5 ); |
|
|
|
} |
|
else |
|
{ |
|
CTakeDamageInfo info( this, this, sk_bullsquid_dmg_spit.GetFloat(), DMG_BULLET ); |
|
CalculateBulletDamageForce( &info, GetAmmoDef()->Index("9mmRound"), GetAbsVelocity(), GetAbsOrigin() ); |
|
pOther->TakeDamage( info ); |
|
} |
|
|
|
UTIL_Remove( m_hSprite ); |
|
UTIL_Remove( this ); |
|
} |
|
|
|
|
|
BEGIN_DATADESC( CNPC_Bullsquid ) |
|
DEFINE_FIELD( m_fCanThreatDisplay, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flLastHurtTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNextSpitTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_flHungryTime, FIELD_TIME ), |
|
END_DATADESC() |
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CNPC_Bullsquid::Spawn() |
|
{ |
|
Precache( ); |
|
|
|
SetModel( "models/bullsquid.mdl"); |
|
SetHullType(HULL_WIDE_SHORT); |
|
SetHullSizeNormal(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
SetMoveType( MOVETYPE_STEP ); |
|
m_bloodColor = BLOOD_COLOR_GREEN; |
|
|
|
SetRenderColor( 255, 255, 255, 255 ); |
|
|
|
m_iHealth = sk_bullsquid_health.GetFloat(); |
|
m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) |
|
m_NPCState = NPC_STATE_NONE; |
|
|
|
CapabilitiesClear(); |
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 ); |
|
|
|
m_fCanThreatDisplay = TRUE; |
|
m_flNextSpitTime = gpGlobals->curtime; |
|
|
|
NPCInit(); |
|
|
|
m_flDistTooFar = 784; |
|
} |
|
|
|
//========================================================= |
|
// Precache - precaches all resources this monster needs |
|
//========================================================= |
|
void CNPC_Bullsquid::Precache() |
|
{ |
|
BaseClass::Precache(); |
|
|
|
PrecacheModel("models/bullsquid.mdl"); |
|
|
|
PrecacheModel("sprites/bigspit.vmt");// spit projectile. |
|
|
|
PrecacheScriptSound( "Bullsquid.Idle" ); |
|
PrecacheScriptSound( "Bullsquid.Pain" ); |
|
PrecacheScriptSound( "Bullsquid.Alert" ); |
|
PrecacheScriptSound( "Bullsquid.Die" ); |
|
PrecacheScriptSound( "Bullsquid.Attack" ); |
|
PrecacheScriptSound( "Bullsquid.Bite" ); |
|
PrecacheScriptSound( "Bullsquid.Growl" ); |
|
} |
|
|
|
|
|
int CNPC_Bullsquid::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch ( scheduleType ) |
|
{ |
|
case SCHED_CHASE_ENEMY: |
|
return SCHED_SQUID_CHASE_ENEMY; |
|
break; |
|
} |
|
|
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Indicates this monster's place in the relationship table. |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
Class_T CNPC_Bullsquid::Classify( void ) |
|
{ |
|
return CLASS_ALIEN_PREDATOR; |
|
} |
|
|
|
//========================================================= |
|
// IdleSound |
|
//========================================================= |
|
#define SQUID_ATTN_IDLE (float)1.5 |
|
void CNPC_Bullsquid::IdleSound( void ) |
|
{ |
|
CPASAttenuationFilter filter( this, SQUID_ATTN_IDLE ); |
|
EmitSound( filter, entindex(), "Bullsquid.Idle" ); |
|
} |
|
|
|
//========================================================= |
|
// PainSound |
|
//========================================================= |
|
void CNPC_Bullsquid::PainSound( const CTakeDamageInfo &info ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "Bullsquid.Pain" ); |
|
} |
|
|
|
//========================================================= |
|
// AlertSound |
|
//========================================================= |
|
void CNPC_Bullsquid::AlertSound( void ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "Bullsquid.Alert" ); |
|
} |
|
|
|
//========================================================= |
|
// DeathSound |
|
//========================================================= |
|
void CNPC_Bullsquid::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "Bullsquid.Die" ); |
|
} |
|
|
|
//========================================================= |
|
// AttackSound |
|
//========================================================= |
|
void CNPC_Bullsquid::AttackSound( void ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "Bullsquid.Attack" ); |
|
} |
|
|
|
//========================================================= |
|
// SetYawSpeed - allows each sequence to have a different |
|
// turn rate associated with it. |
|
//========================================================= |
|
float CNPC_Bullsquid::MaxYawSpeed( void ) |
|
{ |
|
float flYS = 0; |
|
|
|
switch ( GetActivity() ) |
|
{ |
|
case ACT_WALK: flYS = 90; break; |
|
case ACT_RUN: flYS = 90; break; |
|
case ACT_IDLE: flYS = 90; break; |
|
case ACT_RANGE_ATTACK1: flYS = 90; break; |
|
default: |
|
flYS = 90; |
|
break; |
|
} |
|
|
|
return flYS; |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
//========================================================= |
|
void CNPC_Bullsquid::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
switch( pEvent->event ) |
|
{ |
|
case BSQUID_AE_SPIT: |
|
{ |
|
if ( GetEnemy() ) |
|
{ |
|
Vector vecSpitOffset; |
|
Vector vecSpitDir; |
|
Vector vRight, vUp, vForward; |
|
|
|
AngleVectors ( GetAbsAngles(), &vForward, &vRight, &vUp ); |
|
|
|
// !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here. |
|
// we should be able to read the position of bones at runtime for this info. |
|
vecSpitOffset = ( vRight * 8 + vForward * 60 + vUp * 50 ); |
|
vecSpitOffset = ( GetAbsOrigin() + vecSpitOffset ); |
|
vecSpitDir = ( ( GetEnemy()->BodyTarget( GetAbsOrigin() ) ) - vecSpitOffset ); |
|
|
|
VectorNormalize( vecSpitDir ); |
|
|
|
vecSpitDir.x += random->RandomFloat( -0.05, 0.05 ); |
|
vecSpitDir.y += random->RandomFloat( -0.05, 0.05 ); |
|
vecSpitDir.z += random->RandomFloat( -0.05, 0 ); |
|
|
|
AttackSound(); |
|
|
|
CSquidSpit::Shoot( this, vecSpitOffset, vecSpitDir * 900 ); |
|
} |
|
} |
|
break; |
|
|
|
case BSQUID_AE_BITE: |
|
{ |
|
// SOUND HERE! |
|
CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_bite.GetFloat(), DMG_SLASH ); |
|
if ( pHurt ) |
|
{ |
|
Vector forward, up; |
|
AngleVectors( GetAbsAngles(), &forward, NULL, &up ); |
|
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - (forward * 100) ); |
|
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (up * 100) ); |
|
pHurt->SetGroundEntity( NULL ); |
|
} |
|
} |
|
break; |
|
|
|
case BSQUID_AE_TAILWHIP: |
|
{ |
|
CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), sk_bullsquid_dmg_whip.GetFloat(), DMG_SLASH | DMG_ALWAYSGIB ); |
|
if ( pHurt ) |
|
{ |
|
Vector right, up; |
|
AngleVectors( GetAbsAngles(), NULL, &right, &up ); |
|
|
|
if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) ) |
|
pHurt->ViewPunch( QAngle( 20, 0, -20 ) ); |
|
|
|
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (right * 200) ); |
|
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + (up * 100) ); |
|
} |
|
} |
|
break; |
|
|
|
case BSQUID_AE_BLINK: |
|
{ |
|
// close eye. |
|
m_nSkin = 1; |
|
} |
|
break; |
|
|
|
case BSQUID_AE_HOP: |
|
{ |
|
float flGravity = GetCurrentGravity(); |
|
|
|
// throw the squid up into the air on this frame. |
|
if ( GetFlags() & FL_ONGROUND ) |
|
{ |
|
SetGroundEntity( NULL ); |
|
} |
|
|
|
// jump into air for 0.8 (24/30) seconds |
|
Vector vecVel = GetAbsVelocity(); |
|
vecVel.z += ( 0.625 * flGravity ) * 0.5; |
|
SetAbsVelocity( vecVel ); |
|
} |
|
break; |
|
|
|
case BSQUID_AE_THROW: |
|
{ |
|
// squid throws its prey IF the prey is a client. |
|
CBaseEntity *pHurt = CheckTraceHullAttack( 70, Vector(-16,-16,-16), Vector(16,16,16), 0, 0 ); |
|
|
|
|
|
if ( pHurt ) |
|
{ |
|
// croonchy bite sound |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "Bullsquid.Bite" ); |
|
|
|
// screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels. |
|
UTIL_ScreenShake( pHurt->GetAbsOrigin(), 25.0, 1.5, 0.7, 2, SHAKE_START ); |
|
|
|
if ( pHurt->IsPlayer() ) |
|
{ |
|
Vector forward, up; |
|
AngleVectors( GetAbsAngles(), &forward, NULL, &up ); |
|
|
|
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() + forward * 300 + up * 300 ); |
|
} |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::HandleAnimEvent( pEvent ); |
|
} |
|
} |
|
|
|
int CNPC_Bullsquid::RangeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
if ( IsMoving() && flDist >= 512 ) |
|
{ |
|
// squid will far too far behind if he stops running to spit at this distance from the enemy. |
|
return ( COND_NONE ); |
|
} |
|
|
|
if ( flDist > 85 && flDist <= 784 && flDot >= 0.5 && gpGlobals->curtime >= m_flNextSpitTime ) |
|
{ |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
if ( fabs( GetAbsOrigin().z - GetEnemy()->GetAbsOrigin().z ) > 256 ) |
|
{ |
|
// don't try to spit at someone up really high or down really low. |
|
return( COND_NONE ); |
|
} |
|
} |
|
|
|
if ( IsMoving() ) |
|
{ |
|
// don't spit again for a long time, resume chasing enemy. |
|
m_flNextSpitTime = gpGlobals->curtime + 5; |
|
} |
|
else |
|
{ |
|
// not moving, so spit again pretty soon. |
|
m_flNextSpitTime = gpGlobals->curtime + 0.5; |
|
} |
|
|
|
return( COND_CAN_RANGE_ATTACK1 ); |
|
} |
|
|
|
return( COND_NONE ); |
|
} |
|
|
|
//========================================================= |
|
// MeleeAttack2Conditions - bullsquid is a big guy, so has a longer |
|
// melee range than most monsters. This is the tailwhip attack |
|
//========================================================= |
|
int CNPC_Bullsquid::MeleeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
if ( GetEnemy()->m_iHealth <= sk_bullsquid_dmg_whip.GetFloat() && flDist <= 85 && flDot >= 0.7 ) |
|
{ |
|
return ( COND_CAN_MELEE_ATTACK1 ); |
|
} |
|
|
|
return( COND_NONE ); |
|
} |
|
|
|
//========================================================= |
|
// MeleeAttack2Conditions - bullsquid is a big guy, so has a longer |
|
// melee range than most monsters. This is the bite attack. |
|
// this attack will not be performed if the tailwhip attack |
|
// is valid. |
|
//========================================================= |
|
int CNPC_Bullsquid::MeleeAttack2Conditions( float flDot, float flDist ) |
|
{ |
|
if ( flDist <= 85 && flDot >= 0.7 && !HasCondition( COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes |
|
return ( COND_CAN_MELEE_ATTACK2 ); |
|
|
|
return( COND_NONE ); |
|
} |
|
|
|
bool CNPC_Bullsquid::FValidateHintType ( CAI_Hint *pHint ) |
|
{ |
|
if ( pHint->HintType() == HINT_HL1_WORLD_HUMAN_BLOOD ) |
|
return true; |
|
|
|
Msg ( "Couldn't validate hint type" ); |
|
|
|
return false; |
|
} |
|
|
|
void CNPC_Bullsquid::RemoveIgnoredConditions( void ) |
|
{ |
|
if ( m_flHungryTime > gpGlobals->curtime ) |
|
ClearCondition( COND_SQUID_SMELL_FOOD ); |
|
|
|
if ( gpGlobals->curtime - m_flLastHurtTime <= 20 ) |
|
{ |
|
// haven't been hurt in 20 seconds, so let the squid care about stink. |
|
ClearCondition( COND_SMELL ); |
|
} |
|
|
|
if ( GetEnemy() != NULL ) |
|
{ |
|
// ( Unless after a tasty headcrab, yumm ^_^ ) |
|
if ( FClassnameIs( GetEnemy(), "monster_headcrab" ) ) |
|
ClearCondition( COND_SMELL ); |
|
} |
|
} |
|
|
|
Disposition_t CNPC_Bullsquid::IRelationType( CBaseEntity *pTarget ) |
|
{ |
|
if ( gpGlobals->curtime - m_flLastHurtTime < 5 && FClassnameIs ( pTarget, "monster_headcrab" ) ) |
|
{ |
|
// if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab, |
|
// tell squid to disregard crab. |
|
return D_NU; |
|
} |
|
|
|
return BaseClass::IRelationType ( pTarget ); |
|
} |
|
|
|
//========================================================= |
|
// TakeDamage - overridden for bullsquid so we can keep track |
|
// of how much time has passed since it was last injured |
|
//========================================================= |
|
int CNPC_Bullsquid::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo ) |
|
{ |
|
|
|
#if 0 //Fix later. |
|
|
|
float flDist; |
|
Vector vecApex, vOffset; |
|
|
|
// if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy, |
|
// it will swerve. (whew). |
|
if ( GetEnemy() != NULL && IsMoving() && pevAttacker == GetEnemy() && gpGlobals->curtime - m_flLastHurtTime > 3 ) |
|
{ |
|
flDist = ( GetAbsOrigin() - GetEnemy()->GetAbsOrigin() ).Length2D(); |
|
|
|
if ( flDist > SQUID_SPRINT_DIST ) |
|
{ |
|
AI_Waypoint_t* pRoute = GetNavigator()->GetPath()->Route(); |
|
|
|
if ( pRoute ) |
|
{ |
|
flDist = ( GetAbsOrigin() - pRoute[ pRoute->iNodeID ].vecLocation ).Length2D();// reusing flDist. |
|
|
|
if ( GetNavigator()->GetPath()->BuildTriangulationRoute( GetAbsOrigin(), pRoute[ pRoute->iNodeID ].vecLocation, flDist * 0.5, GetEnemy(), &vecApex, &vOffset, NAV_GROUND ) ) |
|
{ |
|
GetNavigator()->PrependWaypoint( vecApex, bits_WP_TO_DETOUR | bits_WP_DONT_SIMPLIFY ); |
|
} |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
if ( !FClassnameIs ( inputInfo.GetAttacker(), "monster_headcrab" ) ) |
|
{ |
|
// don't forget about headcrabs if it was a headcrab that hurt the squid. |
|
m_flLastHurtTime = gpGlobals->curtime; |
|
} |
|
|
|
return BaseClass::OnTakeDamage_Alive ( inputInfo ); |
|
} |
|
|
|
//========================================================= |
|
// GetSoundInterests - returns a bit mask indicating which types |
|
// of sounds this monster regards. In the base class implementation, |
|
// monsters care about all sounds, but no scents. |
|
//========================================================= |
|
int CNPC_Bullsquid::GetSoundInterests ( void ) |
|
{ |
|
return SOUND_WORLD | |
|
SOUND_COMBAT | |
|
SOUND_CARCASS | |
|
SOUND_MEAT | |
|
SOUND_GARBAGE | |
|
SOUND_PLAYER; |
|
} |
|
|
|
//========================================================= |
|
// OnListened - monsters dig through the active sound list for |
|
// any sounds that may interest them. (smells, too!) |
|
//========================================================= |
|
void CNPC_Bullsquid::OnListened( void ) |
|
{ |
|
AISoundIter_t iter; |
|
|
|
CSound *pCurrentSound; |
|
|
|
static int conditionsToClear[] = |
|
{ |
|
COND_SQUID_SMELL_FOOD, |
|
}; |
|
|
|
ClearConditions( conditionsToClear, ARRAYSIZE( conditionsToClear ) ); |
|
|
|
pCurrentSound = GetSenses()->GetFirstHeardSound( &iter ); |
|
|
|
while ( pCurrentSound ) |
|
{ |
|
// the npc cares about this sound, and it's close enough to hear. |
|
int condition = COND_NONE; |
|
|
|
if ( !pCurrentSound->FIsSound() ) |
|
{ |
|
// if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent |
|
if ( pCurrentSound->IsSoundType( SOUND_MEAT | SOUND_CARCASS ) ) |
|
{ |
|
// the detected scent is a food item |
|
condition = COND_SQUID_SMELL_FOOD; |
|
} |
|
} |
|
|
|
if ( condition != COND_NONE ) |
|
SetCondition( condition ); |
|
|
|
pCurrentSound = GetSenses()->GetNextHeardSound( &iter ); |
|
} |
|
|
|
BaseClass::OnListened(); |
|
} |
|
|
|
//======================================================== |
|
// RunAI - overridden for bullsquid because there are things |
|
// that need to be checked every think. |
|
//======================================================== |
|
void CNPC_Bullsquid::RunAI ( void ) |
|
{ |
|
// first, do base class stuff |
|
BaseClass::RunAI(); |
|
|
|
if ( m_nSkin != 0 ) |
|
{ |
|
// close eye if it was open. |
|
m_nSkin = 0; |
|
} |
|
|
|
if ( random->RandomInt( 0,39 ) == 0 ) |
|
{ |
|
m_nSkin = 1; |
|
} |
|
|
|
if ( GetEnemy() != NULL && GetActivity() == ACT_RUN ) |
|
{ |
|
// chasing enemy. Sprint for last bit |
|
if ( (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length2D() < SQUID_SPRINT_DIST ) |
|
{ |
|
m_flPlaybackRate = 1.25; |
|
} |
|
} |
|
|
|
} |
|
|
|
//========================================================= |
|
// GetSchedule |
|
//========================================================= |
|
int CNPC_Bullsquid::SelectSchedule( void ) |
|
{ |
|
switch ( m_NPCState ) |
|
{ |
|
case NPC_STATE_ALERT: |
|
{ |
|
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) ) |
|
{ |
|
return SCHED_SQUID_HURTHOP; |
|
} |
|
|
|
if ( HasCondition( COND_SQUID_SMELL_FOOD ) ) |
|
{ |
|
CSound *pSound; |
|
|
|
pSound = GetBestScent(); |
|
|
|
if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) ) |
|
{ |
|
// scent is behind or occluded |
|
return SCHED_SQUID_SNIFF_AND_EAT; |
|
} |
|
|
|
// food is right out in the open. Just go get it. |
|
return SCHED_SQUID_EAT; |
|
} |
|
|
|
if ( HasCondition( COND_SMELL ) ) |
|
{ |
|
// there's something stinky. |
|
CSound *pSound; |
|
|
|
pSound = GetBestScent(); |
|
if ( pSound ) |
|
return SCHED_SQUID_WALLOW; |
|
} |
|
|
|
break; |
|
} |
|
case NPC_STATE_COMBAT: |
|
{ |
|
// dead enemy |
|
if ( HasCondition( COND_ENEMY_DEAD ) ) |
|
{ |
|
// call base class, all code to handle dead enemies is centralized there. |
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
if ( HasCondition( COND_NEW_ENEMY ) ) |
|
{ |
|
if ( m_fCanThreatDisplay && IRelationType( GetEnemy() ) == D_HT && FClassnameIs( GetEnemy(), "monster_headcrab" ) ) |
|
{ |
|
// this means squid sees a headcrab! |
|
m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. |
|
return SCHED_SQUID_SEECRAB; |
|
} |
|
else |
|
{ |
|
return SCHED_WAKE_ANGRY; |
|
} |
|
} |
|
|
|
if ( HasCondition( COND_SQUID_SMELL_FOOD ) ) |
|
{ |
|
CSound *pSound; |
|
|
|
pSound = GetBestScent(); |
|
|
|
if ( pSound && (!FInViewCone ( pSound->GetSoundOrigin() ) || !FVisible ( pSound->GetSoundOrigin() )) ) |
|
{ |
|
// scent is behind or occluded |
|
return SCHED_SQUID_SNIFF_AND_EAT; |
|
} |
|
|
|
// food is right out in the open. Just go get it. |
|
return SCHED_SQUID_EAT; |
|
} |
|
|
|
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) |
|
{ |
|
return SCHED_RANGE_ATTACK1; |
|
} |
|
|
|
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) |
|
{ |
|
return SCHED_MELEE_ATTACK1; |
|
} |
|
|
|
if ( HasCondition( COND_CAN_MELEE_ATTACK2 ) ) |
|
{ |
|
return SCHED_MELEE_ATTACK2; |
|
} |
|
|
|
return SCHED_CHASE_ENEMY; |
|
|
|
break; |
|
} |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
//========================================================= |
|
// FInViewCone - returns true is the passed vector is in |
|
// the caller's forward view cone. The dot product is performed |
|
// in 2d, making the view cone infinitely tall. |
|
//========================================================= |
|
bool CNPC_Bullsquid::FInViewCone ( Vector pOrigin ) |
|
{ |
|
Vector los = ( pOrigin - GetAbsOrigin() ); |
|
|
|
// do this in 2D |
|
los.z = 0; |
|
VectorNormalize( los ); |
|
|
|
Vector facingDir = EyeDirection2D( ); |
|
|
|
float flDot = DotProduct( los, facingDir ); |
|
|
|
if ( flDot > m_flFieldOfView ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//========================================================= |
|
// FVisible - returns true if a line can be traced from |
|
// the caller's eyes to the target vector |
|
//========================================================= |
|
bool CNPC_Bullsquid::FVisible ( Vector vecOrigin ) |
|
{ |
|
trace_t tr; |
|
Vector vecLookerOrigin; |
|
|
|
vecLookerOrigin = EyePosition();//look through the caller's 'eyes' |
|
UTIL_TraceLine(vecLookerOrigin, vecOrigin, MASK_BLOCKLOS, this/*pentIgnore*/, COLLISION_GROUP_NONE, &tr); |
|
|
|
if ( tr.fraction != 1.0 ) |
|
return false; // Line of sight is not established |
|
else |
|
return true;// line of sight is valid. |
|
} |
|
|
|
//========================================================= |
|
// Start task - selects the correct activity and performs |
|
// any necessary calculations to start the next task on the |
|
// schedule. OVERRIDDEN for bullsquid because it needs to |
|
// know explicitly when the last attempt to chase the enemy |
|
// failed, since that impacts its attack choices. |
|
//========================================================= |
|
void CNPC_Bullsquid::StartTask ( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_MELEE_ATTACK2: |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
EmitSound( filter, entindex(), "Bullsquid.Growl" ); |
|
BaseClass::StartTask ( pTask ); |
|
break; |
|
} |
|
case TASK_SQUID_HOPTURN: |
|
{ |
|
SetActivity ( ACT_HOP ); |
|
|
|
if ( GetEnemy() ) |
|
{ |
|
Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); |
|
VectorNormalize( vecFacing ); |
|
|
|
GetMotor()->SetIdealYaw( vecFacing ); |
|
} |
|
|
|
break; |
|
} |
|
case TASK_SQUID_EAT: |
|
{ |
|
m_flHungryTime = gpGlobals->curtime + pTask->flTaskData; |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
BaseClass::StartTask ( pTask ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// RunTask |
|
//========================================================= |
|
void CNPC_Bullsquid::RunTask ( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_SQUID_HOPTURN: |
|
{ |
|
if ( GetEnemy() ) |
|
{ |
|
Vector vecFacing = ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); |
|
VectorNormalize( vecFacing ); |
|
GetMotor()->SetIdealYaw( vecFacing ); |
|
} |
|
|
|
if ( IsSequenceFinished() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
default: |
|
{ |
|
BaseClass::RunTask( pTask ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// GetIdealState - Overridden for Bullsquid to deal with |
|
// the feature that makes it lose interest in headcrabs for |
|
// a while if something injures it. |
|
//========================================================= |
|
NPC_STATE CNPC_Bullsquid::SelectIdealState ( void ) |
|
{ |
|
// If no schedule conditions, the new ideal state is probably the reason we're in here. |
|
switch ( m_NPCState ) |
|
{ |
|
case NPC_STATE_COMBAT: |
|
/* |
|
COMBAT goes to ALERT upon death of enemy |
|
*/ |
|
{ |
|
if ( GetEnemy() != NULL && ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition ( COND_HEAVY_DAMAGE ) ) && FClassnameIs( GetEnemy(), "monster_headcrab" ) ) |
|
{ |
|
// if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while. |
|
SetEnemy( NULL ); |
|
return NPC_STATE_ALERT; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
return BaseClass::SelectIdealState(); |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// |
|
// Schedules |
|
// |
|
//------------------------------------------------------------------------------ |
|
|
|
AI_BEGIN_CUSTOM_NPC( monster_bullchicken, CNPC_Bullsquid ) |
|
|
|
DECLARE_TASK ( TASK_SQUID_HOPTURN ) |
|
DECLARE_TASK ( TASK_SQUID_EAT ) |
|
|
|
DECLARE_CONDITION( COND_SQUID_SMELL_FOOD ) |
|
|
|
DECLARE_ACTIVITY( ACT_SQUID_EXCITED ) |
|
DECLARE_ACTIVITY( ACT_SQUID_EAT ) |
|
DECLARE_ACTIVITY( ACT_SQUID_DETECT_SCENT ) |
|
DECLARE_ACTIVITY( ACT_SQUID_INSPECT_FLOOR ) |
|
|
|
//========================================================= |
|
// > SCHED_SQUID_HURTHOP |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_SQUID_HURTHOP, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SOUND_WAKE 0" |
|
" TASK_SQUID_HOPTURN 0" |
|
" TASK_FACE_ENEMY 0" |
|
" " |
|
" Interrupts" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_SQUID_SEECRAB |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_SQUID_SEECRAB, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SOUND_WAKE 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EXCITED" |
|
" TASK_FACE_ENEMY 0" |
|
" " |
|
" Interrupts" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_SQUID_EAT |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_SQUID_EAT, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SQUID_EAT 10" |
|
" TASK_STORE_LASTPOSITION 0" |
|
" TASK_GET_PATH_TO_BESTSCENT 0" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT" |
|
" TASK_SQUID_EAT 50" |
|
" TASK_GET_PATH_TO_LASTPOSITION 0" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_CLEAR_LASTPOSITION 0" |
|
" " |
|
" Interrupts" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_NEW_ENEMY" |
|
" COND_SMELL" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_SQUID_SNIFF_AND_EAT |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_SQUID_SNIFF_AND_EAT, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SQUID_EAT 10" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_DETECT_SCENT" |
|
" TASK_STORE_LASTPOSITION 0" |
|
" TASK_GET_PATH_TO_BESTSCENT 0" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_EAT" |
|
" TASK_SQUID_EAT 50" |
|
" TASK_GET_PATH_TO_LASTPOSITION 0" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_CLEAR_LASTPOSITION 0" |
|
" " |
|
" Interrupts" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_NEW_ENEMY" |
|
" COND_SMELL" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_SQUID_WALLOW |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_SQUID_WALLOW, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SQUID_EAT 10" |
|
" TASK_STORE_LASTPOSITION 0" |
|
" TASK_GET_PATH_TO_BESTSCENT 0" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_SQUID_INSPECT_FLOOR" |
|
" TASK_SQUID_EAT 50" |
|
" TASK_GET_PATH_TO_LASTPOSITION 0" |
|
" TASK_WALK_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_CLEAR_LASTPOSITION 0" |
|
" " |
|
" Interrupts" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_NEW_ENEMY" |
|
) |
|
|
|
//========================================================= |
|
// > SCHED_SQUID_CHASE_ENEMY |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_SQUID_CHASE_ENEMY, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_RANGE_ATTACK1" |
|
" TASK_GET_PATH_TO_ENEMY 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" " |
|
" Interrupts" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_SMELL" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_CAN_MELEE_ATTACK1" |
|
" COND_CAN_MELEE_ATTACK2" |
|
" COND_TASK_FAILED" |
|
) |
|
|
|
AI_END_CUSTOM_NPC()
|
|
|