aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_bullsquid.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_bullsquid.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_bullsquid.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_bullsquid.cpp1022
1 files changed, 1022 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_bullsquid.cpp b/mp/src/game/server/hl2/npc_bullsquid.cpp
new file mode 100644
index 00000000..c2faa6a3
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_bullsquid.cpp
@@ -0,0 +1,1022 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Implements the bullsquid
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "game.h"
+#include "AI_Default.h"
+#include "AI_Schedule.h"
+#include "AI_Hull.h"
+#include "AI_Navigator.h"
+#include "AI_Motor.h"
+#include "ai_squad.h"
+#include "npc_bullsquid.h"
+#include "npcevent.h"
+#include "soundent.h"
+#include "activitylist.h"
+#include "weapon_brickbat.h"
+#include "npc_headcrab.h"
+#include "player.h"
+#include "gamerules.h" // For g_pGameRules
+#include "ammodef.h"
+#include "grenade_spit.h"
+#include "grenade_brickbat.h"
+#include "entitylist.h"
+#include "shake.h"
+#include "vstdlib/random.h"
+#include "engine/IEngineSound.h"
+#include "movevars_shared.h"
+
+#include "AI_Hint.h"
+#include "AI_Senses.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.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" );
+
+//=========================================================
+// monster-specific schedule types
+//=========================================================
+enum
+{
+ SCHED_SQUID_HURTHOP = LAST_SHARED_SCHEDULE + 1,
+ SCHED_SQUID_SEECRAB,
+ SCHED_SQUID_EAT,
+ SCHED_SQUID_SNIFF_AND_EAT,
+ SCHED_SQUID_WALLOW,
+};
+
+//=========================================================
+// monster-specific tasks
+//=========================================================
+enum
+{
+ TASK_SQUID_HOPTURN = LAST_SHARED_TASK + 1,
+ TASK_SQUID_EAT,
+};
+
+//-----------------------------------------------------------------------------
+// Squid Conditions
+//-----------------------------------------------------------------------------
+enum
+{
+ COND_SQUID_SMELL_FOOD = LAST_SHARED_CONDITION + 1,
+};
+
+
+//=========================================================
+// Interactions
+//=========================================================
+int g_interactionBullsquidThrow = 0;
+
+//=========================================================
+// Monster's Anim Events Go Here
+//=========================================================
+#define BSQUID_AE_SPIT ( 1 )
+#define BSQUID_AE_BITE ( 2 )
+#define BSQUID_AE_BLINK ( 3 )
+#define BSQUID_AE_ROAR ( 4 )
+#define BSQUID_AE_HOP ( 5 )
+#define BSQUID_AE_THROW ( 6 )
+#define BSQUID_AE_WHIP_SND ( 7 )
+
+LINK_ENTITY_TO_CLASS( npc_bullsquid, CNPC_Bullsquid );
+
+int ACT_SQUID_EXCITED;
+int ACT_SQUID_EAT;
+int ACT_SQUID_DETECT_SCENT;
+int ACT_SQUID_INSPECT_FLOOR;
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+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_nSquidSpitSprite, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flHungryTime, FIELD_TIME ),
+ DEFINE_FIELD( m_nextSquidSoundTime, 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()
+{
+ PrecacheModel( "models/bullsquid.mdl" );
+ m_nSquidSpitSprite = PrecacheModel("sprites/greenspit1.vmt");// client side spittle.
+
+ UTIL_PrecacheOther( "grenade_spit" );
+
+ PrecacheScriptSound( "NPC_Bullsquid.Idle" );
+ PrecacheScriptSound( "NPC_Bullsquid.Pain" );
+ PrecacheScriptSound( "NPC_Bullsquid.Alert" );
+ PrecacheScriptSound( "NPC_Bullsquid.Death" );
+ PrecacheScriptSound( "NPC_Bullsquid.Attack1" );
+ PrecacheScriptSound( "NPC_Bullsquid.Growl" );
+ PrecacheScriptSound( "NPC_Bullsquid.TailWhip");
+
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this monster's place in the relationship table.
+// Output :
+//-----------------------------------------------------------------------------
+Class_T CNPC_Bullsquid::Classify( void )
+{
+ return CLASS_BULLSQUID;
+}
+
+//=========================================================
+// IdleSound
+//=========================================================
+#define SQUID_ATTN_IDLE (float)1.5
+void CNPC_Bullsquid::IdleSound( void )
+{
+ EmitSound( "NPC_Bullsquid.Idle" );
+}
+
+//=========================================================
+// PainSound
+//=========================================================
+void CNPC_Bullsquid::PainSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Bullsquid.Pain" );
+}
+
+//=========================================================
+// AlertSound
+//=========================================================
+void CNPC_Bullsquid::AlertSound( void )
+{
+ EmitSound( "NPC_Bullsquid.Alert" );
+}
+
+//=========================================================
+// DeathSound
+//=========================================================
+void CNPC_Bullsquid::DeathSound( const CTakeDamageInfo &info )
+{
+ EmitSound( "NPC_Bullsquid.Death" );
+}
+
+//=========================================================
+// AttackSound
+//=========================================================
+void CNPC_Bullsquid::AttackSound( void )
+{
+ EmitSound( "NPC_Bullsquid.Attack1" );
+}
+
+//=========================================================
+// GrowlSound
+//=========================================================
+void CNPC_Bullsquid::GrowlSound( void )
+{
+ if (gpGlobals->curtime >= m_nextSquidSoundTime)
+ {
+ EmitSound( "NPC_Bullsquid.Growl" );
+ m_nextSquidSoundTime = gpGlobals->curtime + random->RandomInt(1.5,3.0);
+ }
+}
+
+
+//=========================================================
+// 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 vSpitPos;
+
+ GetAttachment( "Mouth", vSpitPos );
+
+ Vector vTarget = GetEnemy()->GetAbsOrigin();
+ Vector vToss;
+ CBaseEntity* pBlocker;
+ float flGravity = SPIT_GRAVITY;
+ ThrowLimit(vSpitPos, vTarget, flGravity, 3, Vector(0,0,0), Vector(0,0,0), GetEnemy(), &vToss, &pBlocker);
+
+ CGrenadeSpit *pGrenade = (CGrenadeSpit*)CreateNoSpawn( "grenade_spit", vSpitPos, vec3_angle, this );
+ //pGrenade->KeyValue( "velocity", vToss );
+ pGrenade->Spawn( );
+ pGrenade->SetThrower( this );
+ pGrenade->SetOwnerEntity( this );
+ pGrenade->SetSpitSize( 2 );
+ pGrenade->SetAbsVelocity( vToss );
+
+ // Tumble through the air
+ pGrenade->SetLocalAngularVelocity(
+ QAngle(
+ random->RandomFloat( -100, -500 ),
+ random->RandomFloat( -100, -500 ),
+ random->RandomFloat( -100, -500 )
+ )
+ );
+
+ AttackSound();
+
+ CPVSFilter filter( vSpitPos );
+ te->SpriteSpray( filter, 0.0,
+ &vSpitPos, &vToss, m_nSquidSpitSprite, 5, 10, 15 );
+ }
+ }
+ 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->ApplyAbsVelocityImpulse( 100 * (up-forward) );
+ pHurt->SetGroundEntity( NULL );
+ }
+ }
+ break;
+
+ case BSQUID_AE_WHIP_SND:
+ {
+ EmitSound( "NPC_Bullsquid.TailWhip" );
+ 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->ApplyAbsVelocityImpulse( 100 * (up+2*right) );
+ }
+ }
+ 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 40 inches into the air
+ Vector vecVel = GetAbsVelocity();
+ vecVel.z += sqrt( flGravity * 2.0 * 40 );
+ 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 )
+ {
+ pHurt->ViewPunch( QAngle(20,0,-20) );
+
+ // 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 the player, throw him around
+ if ( pHurt->IsPlayer())
+ {
+ Vector forward, up;
+ AngleVectors( GetLocalAngles(), &forward, NULL, &up );
+ pHurt->ApplyAbsVelocityImpulse( forward * 300 + up * 300 );
+ }
+ // If not the player see if has bullsquid throw interatcion
+ else
+ {
+ CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pHurt );
+ if (pVictim)
+ {
+ if ( pVictim->DispatchInteraction( g_interactionBullsquidThrow, NULL, this ) )
+ {
+ Vector forward, up;
+ AngleVectors( GetLocalAngles(), &forward, NULL, &up );
+ pVictim->ApplyAbsVelocityImpulse( forward * 300 + up * 250 );
+ }
+ }
+ }
+ }
+ }
+ 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;
+
+ DevMsg( "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->m_iType & ( 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;
+}
+
+//=========================================================
+// 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:
+ {
+ if (GetEnemy())
+ {
+ GrowlSound();
+
+ m_flLastAttackTime = gpGlobals->curtime;
+
+ 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;
+ TaskComplete();
+ 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( npc_bullsquid, 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 )
+
+ DECLARE_INTERACTION( g_interactionBullsquidThrow )
+
+ //=========================================================
+ // > 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"
+ )
+
+AI_END_CUSTOM_NPC()