|
|
//========= 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()
|