diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_bullsquid.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/hl1/hl1_npc_bullsquid.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_bullsquid.cpp | 1167 |
1 files changed, 1167 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_bullsquid.cpp b/game/server/hl1/hl1_npc_bullsquid.cpp new file mode 100644 index 0000000..373278d --- /dev/null +++ b/game/server/hl1/hl1_npc_bullsquid.cpp @@ -0,0 +1,1167 @@ +//========= 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() |