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.
1600 lines
38 KiB
1600 lines
38 KiB
![]()
4 years ago
|
//========= Copyright Valve Corporation, All rights reserved. ============//
|
||
|
//
|
||
|
// Purpose: Crows. Simple ambient birds that fly away when they hear gunfire or
|
||
|
// when anything gets too close to them.
|
||
|
//
|
||
|
// TODO: landing
|
||
|
// TODO: death
|
||
|
//
|
||
|
// $NoKeywords: $
|
||
|
//=============================================================================//
|
||
|
|
||
|
#include "cbase.h"
|
||
|
#include "game.h"
|
||
|
#include "ai_basenpc.h"
|
||
|
#include "ai_schedule.h"
|
||
|
#include "ai_hull.h"
|
||
|
#include "ai_hint.h"
|
||
|
#include "ai_motor.h"
|
||
|
#include "ai_navigator.h"
|
||
|
#include "hl2_shareddefs.h"
|
||
|
#include "ai_route.h"
|
||
|
#include "npcevent.h"
|
||
|
#include "gib.h"
|
||
|
#include "ai_interactions.h"
|
||
|
#include "ndebugoverlay.h"
|
||
|
#include "soundent.h"
|
||
|
#include "vstdlib/random.h"
|
||
|
#include "engine/IEngineSound.h"
|
||
|
#include "movevars_shared.h"
|
||
|
#include "npc_crow.h"
|
||
|
#include "ai_moveprobe.h"
|
||
|
|
||
|
// memdbgon must be the last include file in a .cpp file!!!
|
||
|
#include "tier0/memdbgon.h"
|
||
|
|
||
|
//
|
||
|
// Custom activities.
|
||
|
//
|
||
|
static int ACT_CROW_TAKEOFF;
|
||
|
static int ACT_CROW_SOAR;
|
||
|
static int ACT_CROW_LAND;
|
||
|
|
||
|
//
|
||
|
// Animation events.
|
||
|
//
|
||
|
static int AE_CROW_TAKEOFF;
|
||
|
static int AE_CROW_FLY;
|
||
|
static int AE_CROW_HOP;
|
||
|
|
||
|
//
|
||
|
// Skill settings.
|
||
|
//
|
||
|
ConVar sk_crow_health( "sk_crow_health","1");
|
||
|
ConVar sk_crow_melee_dmg( "sk_crow_melee_dmg","0");
|
||
|
|
||
|
LINK_ENTITY_TO_CLASS( npc_crow, CNPC_Crow );
|
||
|
LINK_ENTITY_TO_CLASS( npc_seagull, CNPC_Seagull );
|
||
|
LINK_ENTITY_TO_CLASS( npc_pigeon, CNPC_Pigeon );
|
||
|
|
||
|
BEGIN_DATADESC( CNPC_Crow )
|
||
|
|
||
|
DEFINE_FIELD( m_flGroundIdleMoveTime, FIELD_TIME ),
|
||
|
DEFINE_FIELD( m_bOnJeep, FIELD_BOOLEAN ),
|
||
|
DEFINE_FIELD( m_flEnemyDist, FIELD_FLOAT ),
|
||
|
DEFINE_FIELD( m_nMorale, FIELD_INTEGER ),
|
||
|
DEFINE_FIELD( m_bReachedMoveGoal, FIELD_BOOLEAN ),
|
||
|
DEFINE_FIELD( m_flHopStartZ, FIELD_FLOAT ),
|
||
|
DEFINE_FIELD( m_vDesiredTarget, FIELD_VECTOR ),
|
||
|
DEFINE_FIELD( m_vCurrentTarget, FIELD_VECTOR ),
|
||
|
DEFINE_FIELD( m_flSoarTime, FIELD_TIME ),
|
||
|
DEFINE_FIELD( m_bSoar, FIELD_BOOLEAN ),
|
||
|
DEFINE_FIELD( m_bPlayedLoopingSound, FIELD_BOOLEAN ),
|
||
|
DEFINE_FIELD( m_iBirdType, FIELD_INTEGER ),
|
||
|
DEFINE_FIELD( m_vLastStoredOrigin, FIELD_POSITION_VECTOR ),
|
||
|
DEFINE_FIELD( m_flLastStuckCheck, FIELD_TIME ),
|
||
|
DEFINE_FIELD( m_flDangerSoundTime, FIELD_TIME ),
|
||
|
DEFINE_KEYFIELD( m_bIsDeaf, FIELD_BOOLEAN, "deaf" ),
|
||
|
|
||
|
// Inputs
|
||
|
DEFINE_INPUTFUNC( FIELD_STRING, "FlyAway", InputFlyAway ),
|
||
|
|
||
|
END_DATADESC()
|
||
|
|
||
|
static ConVar birds_debug( "birds_debug", "0" );
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::Spawn( void )
|
||
|
{
|
||
|
BaseClass::Spawn();
|
||
|
|
||
|
#ifdef _XBOX
|
||
|
// Always fade the corpse
|
||
|
AddSpawnFlags( SF_NPC_FADE_CORPSE );
|
||
|
#endif // _XBOX
|
||
|
|
||
|
char *szModel = (char *)STRING( GetModelName() );
|
||
|
if (!szModel || !*szModel)
|
||
|
{
|
||
|
szModel = "models/crow.mdl";
|
||
|
SetModelName( AllocPooledString(szModel) );
|
||
|
}
|
||
|
|
||
|
Precache();
|
||
|
SetModel( szModel );
|
||
|
|
||
|
m_iHealth = sk_crow_health.GetFloat();
|
||
|
|
||
|
SetHullType(HULL_TINY);
|
||
|
SetHullSizeNormal();
|
||
|
|
||
|
SetSolid( SOLID_BBOX );
|
||
|
SetMoveType( MOVETYPE_STEP );
|
||
|
|
||
|
m_flFieldOfView = VIEW_FIELD_FULL;
|
||
|
SetViewOffset( Vector(6, 0, 11) ); // Position of the eyes relative to NPC's origin.
|
||
|
|
||
|
m_flGroundIdleMoveTime = gpGlobals->curtime + random->RandomFloat( 0.0f, 5.0f );
|
||
|
|
||
|
SetBloodColor( BLOOD_COLOR_RED );
|
||
|
m_NPCState = NPC_STATE_NONE;
|
||
|
|
||
|
m_nMorale = random->RandomInt( 0, 12 );
|
||
|
|
||
|
SetCollisionGroup( HL2COLLISION_GROUP_CROW );
|
||
|
|
||
|
CapabilitiesClear();
|
||
|
|
||
|
bool bFlying = ( ( m_spawnflags & SF_CROW_FLYING ) != 0 );
|
||
|
SetFlyingState( bFlying ? FlyState_Flying : FlyState_Walking );
|
||
|
|
||
|
// We don't mind zombies so much. They smell good!
|
||
|
AddClassRelationship( CLASS_ZOMBIE, D_NU, 0 );
|
||
|
|
||
|
m_bSoar = false;
|
||
|
m_bOnJeep = false;
|
||
|
m_flSoarTime = gpGlobals->curtime;
|
||
|
|
||
|
NPCInit();
|
||
|
|
||
|
m_iBirdType = BIRDTYPE_CROW;
|
||
|
|
||
|
m_vLastStoredOrigin = vec3_origin;
|
||
|
m_flLastStuckCheck = gpGlobals->curtime;
|
||
|
|
||
|
m_flDangerSoundTime = gpGlobals->curtime;
|
||
|
|
||
|
SetGoalEnt( NULL );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Returns this monster's classification in the relationship table.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Class_T CNPC_Crow::Classify( void )
|
||
|
{
|
||
|
return( CLASS_EARTH_FAUNA );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : pEnemy -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::GatherEnemyConditions( CBaseEntity *pEnemy )
|
||
|
{
|
||
|
m_flEnemyDist = (GetLocalOrigin() - pEnemy->GetLocalOrigin()).Length();
|
||
|
|
||
|
if ( m_flEnemyDist < 512 )
|
||
|
{
|
||
|
SetCondition( COND_CROW_ENEMY_WAY_TOO_CLOSE );
|
||
|
}
|
||
|
|
||
|
if ( m_flEnemyDist < 1024 )
|
||
|
{
|
||
|
SetCondition( COND_CROW_ENEMY_TOO_CLOSE );
|
||
|
}
|
||
|
|
||
|
BaseClass::GatherEnemyConditions(pEnemy);
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : posSrc -
|
||
|
// Output : Vector
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Vector CNPC_Crow::BodyTarget( const Vector &posSrc, bool bNoisy )
|
||
|
{
|
||
|
Vector vecResult;
|
||
|
vecResult = GetAbsOrigin();
|
||
|
vecResult.z += 6;
|
||
|
return vecResult;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::StopLoopingSounds( void )
|
||
|
{
|
||
|
//
|
||
|
// Stop whatever flap sound might be playing.
|
||
|
//
|
||
|
if ( m_bPlayedLoopingSound )
|
||
|
{
|
||
|
StopSound( "NPC_Crow.Flap" );
|
||
|
}
|
||
|
BaseClass::StopLoopingSounds();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Catches the monster-specific messages that occur when tagged
|
||
|
// animation frames are played.
|
||
|
// Input : pEvent -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::HandleAnimEvent( animevent_t *pEvent )
|
||
|
{
|
||
|
if ( pEvent->event == AE_CROW_TAKEOFF )
|
||
|
{
|
||
|
if ( GetNavigator()->GetPath()->GetCurWaypoint() )
|
||
|
{
|
||
|
Takeoff( GetNavigator()->GetCurWaypointPos() );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( pEvent->event == AE_CROW_HOP )
|
||
|
{
|
||
|
SetGroundEntity( NULL );
|
||
|
|
||
|
//
|
||
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
|
||
|
//
|
||
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
|
||
|
|
||
|
//
|
||
|
// How fast does the crow need to travel to reach the hop goal given gravity?
|
||
|
//
|
||
|
float flHopDistance = ( m_vSavePosition - GetLocalOrigin() ).Length();
|
||
|
float gravity = GetCurrentGravity();
|
||
|
if ( gravity <= 1 )
|
||
|
{
|
||
|
gravity = 1;
|
||
|
}
|
||
|
|
||
|
float height = 0.25 * flHopDistance;
|
||
|
float speed = sqrt( 2 * gravity * height );
|
||
|
float time = speed / gravity;
|
||
|
|
||
|
//
|
||
|
// Scale the sideways velocity to get there at the right time
|
||
|
//
|
||
|
Vector vecJumpDir = m_vSavePosition - GetLocalOrigin();
|
||
|
vecJumpDir = vecJumpDir / time;
|
||
|
|
||
|
//
|
||
|
// Speed to offset gravity at the desired height.
|
||
|
//
|
||
|
vecJumpDir.z = speed;
|
||
|
|
||
|
//
|
||
|
// Don't jump too far/fast.
|
||
|
//
|
||
|
float distance = vecJumpDir.Length();
|
||
|
if ( distance > 650 )
|
||
|
{
|
||
|
vecJumpDir = vecJumpDir * ( 650.0 / distance );
|
||
|
}
|
||
|
|
||
|
m_nMorale -= random->RandomInt( 1, 6 );
|
||
|
if ( m_nMorale <= 0 )
|
||
|
{
|
||
|
m_nMorale = 0;
|
||
|
}
|
||
|
|
||
|
// Play a hop flap sound.
|
||
|
EmitSound( "NPC_Crow.Hop" );
|
||
|
|
||
|
SetAbsVelocity( vecJumpDir );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if( pEvent->event == AE_CROW_FLY )
|
||
|
{
|
||
|
//
|
||
|
// Start flying.
|
||
|
//
|
||
|
SetActivity( ACT_FLY );
|
||
|
|
||
|
m_bSoar = false;
|
||
|
m_flSoarTime = gpGlobals->curtime + random->RandomFloat( 3, 5 );
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CAI_BaseNPC::HandleAnimEvent( pEvent );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : eNewActivity -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::OnChangeActivity( Activity eNewActivity )
|
||
|
{
|
||
|
// if ( eNewActivity == ACT_FLY )
|
||
|
// {
|
||
|
// m_flGroundSpeed = CROW_AIRSPEED;
|
||
|
// }
|
||
|
//
|
||
|
bool fRandomize = false;
|
||
|
if ( eNewActivity == ACT_FLY )
|
||
|
{
|
||
|
fRandomize = true;
|
||
|
}
|
||
|
|
||
|
BaseClass::OnChangeActivity( eNewActivity );
|
||
|
if ( fRandomize )
|
||
|
{
|
||
|
SetCycle( random->RandomFloat( 0.0, 0.75 ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Input handler that makes the crow fly away.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::InputFlyAway( inputdata_t &inputdata )
|
||
|
{
|
||
|
string_t sTarget = MAKE_STRING( inputdata.value.String() );
|
||
|
|
||
|
if ( sTarget != NULL_STRING )// this npc has a target
|
||
|
{
|
||
|
CBaseEntity *pEnt = gEntList.FindEntityByName( NULL, sTarget );
|
||
|
|
||
|
if ( pEnt )
|
||
|
{
|
||
|
trace_t tr;
|
||
|
AI_TraceLine ( EyePosition(), pEnt->GetAbsOrigin(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
|
||
|
|
||
|
if ( tr.fraction != 1.0f )
|
||
|
return;
|
||
|
|
||
|
// Find the npc's initial target entity, stash it
|
||
|
SetGoalEnt( pEnt );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
SetGoalEnt( NULL );
|
||
|
|
||
|
SetCondition( COND_CROW_FORCED_FLY );
|
||
|
SetCondition( COND_PROVOKED );
|
||
|
|
||
|
}
|
||
|
|
||
|
void CNPC_Crow::UpdateEfficiency( bool bInPVS )
|
||
|
{
|
||
|
if ( IsFlying() )
|
||
|
{
|
||
|
SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL );
|
||
|
SetMoveEfficiency( AIME_NORMAL );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
BaseClass::UpdateEfficiency( bInPVS );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Implements "deafness"
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CNPC_Crow::QueryHearSound( CSound *pSound )
|
||
|
{
|
||
|
if( IsDeaf() )
|
||
|
return false;
|
||
|
|
||
|
return BaseClass::QueryHearSound( pSound );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Handles all flight movement because we don't ever build paths when
|
||
|
// when we are flying.
|
||
|
// Input : flInterval - Seconds to simulate.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CNPC_Crow::OverrideMove( float flInterval )
|
||
|
{
|
||
|
if ( GetNavigator()->GetPath()->CurWaypointNavType() == NAV_FLY && GetNavigator()->GetNavType() != NAV_FLY )
|
||
|
{
|
||
|
SetNavType( NAV_FLY );
|
||
|
}
|
||
|
|
||
|
if ( IsFlying() )
|
||
|
{
|
||
|
if ( GetNavigator()->GetPath()->GetCurWaypoint() )
|
||
|
{
|
||
|
if ( m_flLastStuckCheck <= gpGlobals->curtime )
|
||
|
{
|
||
|
if ( m_vLastStoredOrigin == GetAbsOrigin() )
|
||
|
{
|
||
|
if ( GetAbsVelocity() == vec3_origin )
|
||
|
{
|
||
|
float flDamage = m_iHealth;
|
||
|
|
||
|
CTakeDamageInfo dmgInfo( this, this, flDamage, DMG_GENERIC );
|
||
|
GuessDamageForce( &dmgInfo, vec3_origin - Vector( 0, 0, 0.1 ), GetAbsOrigin() );
|
||
|
TakeDamage( dmgInfo );
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_vLastStoredOrigin = GetAbsOrigin();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_vLastStoredOrigin = GetAbsOrigin();
|
||
|
}
|
||
|
|
||
|
m_flLastStuckCheck = gpGlobals->curtime + 1.0f;
|
||
|
}
|
||
|
|
||
|
if (m_bReachedMoveGoal )
|
||
|
{
|
||
|
SetIdealActivity( (Activity)ACT_CROW_LAND );
|
||
|
SetFlyingState( FlyState_Landing );
|
||
|
TaskMovementComplete();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SetIdealActivity ( ACT_FLY );
|
||
|
MoveCrowFly( flInterval );
|
||
|
}
|
||
|
|
||
|
}
|
||
|
else if ( !GetTask() || GetTask()->iTask == TASK_WAIT_FOR_MOVEMENT )
|
||
|
{
|
||
|
SetSchedule( SCHED_CROW_IDLE_FLY );
|
||
|
SetFlyingState( FlyState_Flying );
|
||
|
SetIdealActivity ( ACT_FLY );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Activity CNPC_Crow::NPC_TranslateActivity( Activity eNewActivity )
|
||
|
{
|
||
|
if ( IsFlying() && eNewActivity == ACT_IDLE )
|
||
|
{
|
||
|
return ACT_FLY;
|
||
|
}
|
||
|
|
||
|
if ( eNewActivity == ACT_FLY )
|
||
|
{
|
||
|
if ( m_flSoarTime < gpGlobals->curtime )
|
||
|
{
|
||
|
//Adrian: This should be revisited.
|
||
|
if ( random->RandomInt( 0, 100 ) <= 50 && m_bSoar == false && GetAbsVelocity().z < 0 )
|
||
|
{
|
||
|
m_bSoar = true;
|
||
|
m_flSoarTime = gpGlobals->curtime + random->RandomFloat( 1, 4 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_bSoar = false;
|
||
|
m_flSoarTime = gpGlobals->curtime + random->RandomFloat( 3, 5 );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( m_bSoar == true )
|
||
|
{
|
||
|
return (Activity)ACT_CROW_SOAR;
|
||
|
}
|
||
|
else
|
||
|
return ACT_FLY;
|
||
|
}
|
||
|
|
||
|
return BaseClass::NPC_TranslateActivity( eNewActivity );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Handles all flight movement.
|
||
|
// Input : flInterval - Seconds to simulate.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::MoveCrowFly( float flInterval )
|
||
|
{
|
||
|
//
|
||
|
// Bound interval so we don't get ludicrous motion when debugging
|
||
|
// or when framerate drops catastrophically.
|
||
|
//
|
||
|
if (flInterval > 1.0)
|
||
|
{
|
||
|
flInterval = 1.0;
|
||
|
}
|
||
|
|
||
|
m_flDangerSoundTime = gpGlobals->curtime + 5.0f;
|
||
|
|
||
|
//
|
||
|
// Determine the goal of our movement.
|
||
|
//
|
||
|
Vector vecMoveGoal = GetAbsOrigin();
|
||
|
|
||
|
if ( GetNavigator()->IsGoalActive() )
|
||
|
{
|
||
|
vecMoveGoal = GetNavigator()->GetCurWaypointPos();
|
||
|
|
||
|
if ( GetNavigator()->CurWaypointIsGoal() == false )
|
||
|
{
|
||
|
AI_ProgressFlyPathParams_t params( MASK_NPCSOLID );
|
||
|
params.bTrySimplify = false;
|
||
|
|
||
|
GetNavigator()->ProgressFlyPath( params ); // ignore result, crow handles completion directly
|
||
|
|
||
|
// Fly towards the hint.
|
||
|
if ( GetNavigator()->GetPath()->GetCurWaypoint() )
|
||
|
{
|
||
|
vecMoveGoal = GetNavigator()->GetCurWaypointPos();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// No movement goal.
|
||
|
vecMoveGoal = GetAbsOrigin();
|
||
|
SetAbsVelocity( vec3_origin );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Vector vecMoveDir = ( vecMoveGoal - GetAbsOrigin() );
|
||
|
Vector vForward;
|
||
|
AngleVectors( GetAbsAngles(), &vForward );
|
||
|
|
||
|
//
|
||
|
// Fly towards the movement goal.
|
||
|
//
|
||
|
float flDistance = ( vecMoveGoal - GetAbsOrigin() ).Length();
|
||
|
|
||
|
if ( vecMoveGoal != m_vDesiredTarget )
|
||
|
{
|
||
|
m_vDesiredTarget = vecMoveGoal;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_vCurrentTarget = ( m_vDesiredTarget - GetAbsOrigin() );
|
||
|
VectorNormalize( m_vCurrentTarget );
|
||
|
}
|
||
|
|
||
|
float flLerpMod = 0.25f;
|
||
|
|
||
|
if ( flDistance <= 256.0f )
|
||
|
{
|
||
|
flLerpMod = 1.0f - ( flDistance / 256.0f );
|
||
|
}
|
||
|
|
||
|
|
||
|
VectorLerp( vForward, m_vCurrentTarget, flLerpMod, vForward );
|
||
|
|
||
|
|
||
|
if ( flDistance < CROW_AIRSPEED * flInterval )
|
||
|
{
|
||
|
if ( GetNavigator()->IsGoalActive() )
|
||
|
{
|
||
|
if ( GetNavigator()->CurWaypointIsGoal() )
|
||
|
{
|
||
|
m_bReachedMoveGoal = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GetNavigator()->AdvancePath();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
m_bReachedMoveGoal = true;
|
||
|
}
|
||
|
|
||
|
if ( GetHintNode() )
|
||
|
{
|
||
|
AIMoveTrace_t moveTrace;
|
||
|
GetMoveProbe()->MoveLimit( NAV_FLY, GetAbsOrigin(), GetNavigator()->GetCurWaypointPos(), MASK_NPCSOLID, GetNavTargetEntity(), &moveTrace );
|
||
|
|
||
|
//See if it succeeded
|
||
|
if ( IsMoveBlocked( moveTrace.fStatus ) )
|
||
|
{
|
||
|
Vector vNodePos = vecMoveGoal;
|
||
|
GetHintNode()->GetPosition(this, &vNodePos);
|
||
|
|
||
|
GetNavigator()->SetGoal( vNodePos );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Look to see if we are going to hit anything.
|
||
|
//
|
||
|
VectorNormalize( vForward );
|
||
|
Vector vecDeflect;
|
||
|
if ( Probe( vForward, CROW_AIRSPEED * flInterval, vecDeflect ) )
|
||
|
{
|
||
|
vForward = vecDeflect;
|
||
|
VectorNormalize( vForward );
|
||
|
}
|
||
|
|
||
|
SetAbsVelocity( vForward * CROW_AIRSPEED );
|
||
|
|
||
|
if ( GetAbsVelocity().Length() > 0 && GetNavigator()->CurWaypointIsGoal() && flDistance < CROW_AIRSPEED )
|
||
|
{
|
||
|
SetIdealActivity( (Activity)ACT_CROW_LAND );
|
||
|
}
|
||
|
|
||
|
|
||
|
//Bank and set angles.
|
||
|
Vector vRight;
|
||
|
QAngle vRollAngle;
|
||
|
|
||
|
VectorAngles( vForward, vRollAngle );
|
||
|
vRollAngle.z = 0;
|
||
|
|
||
|
AngleVectors( vRollAngle, NULL, &vRight, NULL );
|
||
|
|
||
|
float flRoll = DotProduct( vRight, vecMoveDir ) * 45;
|
||
|
flRoll = clamp( flRoll, -45, 45 );
|
||
|
|
||
|
vRollAngle[ROLL] = flRoll;
|
||
|
SetAbsAngles( vRollAngle );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Looks ahead to see if we are going to hit something. If we are, a
|
||
|
// recommended avoidance path is returned.
|
||
|
// Input : vecMoveDir -
|
||
|
// flSpeed -
|
||
|
// vecDeflect -
|
||
|
// Output : Returns true if we hit something and need to deflect our course,
|
||
|
// false if all is well.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CNPC_Crow::Probe( const Vector &vecMoveDir, float flSpeed, Vector &vecDeflect )
|
||
|
{
|
||
|
//
|
||
|
// Look 1/2 second ahead.
|
||
|
//
|
||
|
trace_t tr;
|
||
|
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + vecMoveDir * flSpeed, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, HL2COLLISION_GROUP_CROW, &tr );
|
||
|
if ( tr.fraction < 1.0f )
|
||
|
{
|
||
|
//
|
||
|
// If we hit something, deflect flight path parallel to surface hit.
|
||
|
//
|
||
|
Vector vecUp;
|
||
|
CrossProduct( vecMoveDir, tr.plane.normal, vecUp );
|
||
|
CrossProduct( tr.plane.normal, vecUp, vecDeflect );
|
||
|
VectorNormalize( vecDeflect );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
vecDeflect = vec3_origin;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Switches between flying mode and ground mode.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::SetFlyingState( FlyState_t eState )
|
||
|
{
|
||
|
if ( eState == FlyState_Flying )
|
||
|
{
|
||
|
// Flying
|
||
|
SetGroundEntity( NULL );
|
||
|
AddFlag( FL_FLY );
|
||
|
SetNavType( NAV_FLY );
|
||
|
CapabilitiesRemove( bits_CAP_MOVE_GROUND );
|
||
|
CapabilitiesAdd( bits_CAP_MOVE_FLY );
|
||
|
SetMoveType( MOVETYPE_STEP );
|
||
|
m_vLastStoredOrigin = GetAbsOrigin();
|
||
|
m_flLastStuckCheck = gpGlobals->curtime + 3.0f;
|
||
|
m_flGroundIdleMoveTime = gpGlobals->curtime + random->RandomFloat( 5.0f, 10.0f );
|
||
|
}
|
||
|
else if ( eState == FlyState_Walking )
|
||
|
{
|
||
|
// Walking
|
||
|
QAngle angles = GetAbsAngles();
|
||
|
angles[PITCH] = 0.0f;
|
||
|
angles[ROLL] = 0.0f;
|
||
|
SetAbsAngles( angles );
|
||
|
|
||
|
RemoveFlag( FL_FLY );
|
||
|
SetNavType( NAV_GROUND );
|
||
|
CapabilitiesRemove( bits_CAP_MOVE_FLY );
|
||
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
|
||
|
SetMoveType( MOVETYPE_STEP );
|
||
|
m_vLastStoredOrigin = vec3_origin;
|
||
|
m_flGroundIdleMoveTime = gpGlobals->curtime + random->RandomFloat( 5.0f, 10.0f );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Falling
|
||
|
RemoveFlag( FL_FLY );
|
||
|
SetNavType( NAV_GROUND );
|
||
|
CapabilitiesRemove( bits_CAP_MOVE_FLY );
|
||
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
|
||
|
SetMoveType( MOVETYPE_STEP );
|
||
|
m_flGroundIdleMoveTime = gpGlobals->curtime + random->RandomFloat( 5.0f, 10.0f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Performs a takeoff. Called via an animation event at the moment
|
||
|
// our feet leave the ground.
|
||
|
// Input : pGoalEnt - The entity that we are going to fly toward.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::Takeoff( const Vector &vGoal )
|
||
|
{
|
||
|
if ( vGoal != vec3_origin )
|
||
|
{
|
||
|
//
|
||
|
// Lift us off ground so engine doesn't instantly reset FL_ONGROUND.
|
||
|
//
|
||
|
UTIL_SetOrigin( this, GetAbsOrigin() + Vector( 0 , 0 , 1 ));
|
||
|
|
||
|
//
|
||
|
// Fly straight at the goal entity at our maximum airspeed.
|
||
|
//
|
||
|
Vector vecMoveDir = vGoal - GetAbsOrigin();
|
||
|
VectorNormalize( vecMoveDir );
|
||
|
|
||
|
// FIXME: pitch over time
|
||
|
|
||
|
SetFlyingState( FlyState_Flying );
|
||
|
|
||
|
QAngle angles;
|
||
|
VectorAngles( vecMoveDir, angles );
|
||
|
SetAbsAngles( angles );
|
||
|
|
||
|
SetAbsVelocity( vecMoveDir * CROW_TAKEOFF_SPEED );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CNPC_Crow::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
|
||
|
{
|
||
|
CTakeDamageInfo newInfo = info;
|
||
|
|
||
|
if ( info.GetDamageType() & DMG_PHYSGUN )
|
||
|
{
|
||
|
Vector puntDir = ( info.GetDamageForce() * 5000.0f );
|
||
|
|
||
|
newInfo.SetDamage( m_iMaxHealth );
|
||
|
|
||
|
PainSound( newInfo );
|
||
|
newInfo.SetDamageForce( puntDir );
|
||
|
}
|
||
|
|
||
|
BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator );
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNPC_Crow::StartTargetHandling( CBaseEntity *pTargetEnt )
|
||
|
{
|
||
|
AI_NavGoal_t goal( GOALTYPE_PATHCORNER, pTargetEnt->GetAbsOrigin(),
|
||
|
ACT_FLY,
|
||
|
AIN_DEF_TOLERANCE, AIN_YAW_TO_DEST);
|
||
|
|
||
|
if ( !GetNavigator()->SetGoal( goal ) )
|
||
|
{
|
||
|
DevWarning( 2, "Can't Create Route!\n" );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : pTask -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::StartTask( const Task_t *pTask )
|
||
|
{
|
||
|
switch ( pTask->iTask )
|
||
|
{
|
||
|
//
|
||
|
// This task enables us to build a path that requires flight.
|
||
|
//
|
||
|
// case TASK_CROW_PREPARE_TO_FLY:
|
||
|
// {
|
||
|
// SetFlyingState( FlyState_Flying );
|
||
|
// TaskComplete();
|
||
|
// break;
|
||
|
// }
|
||
|
|
||
|
case TASK_CROW_TAKEOFF:
|
||
|
{
|
||
|
if ( random->RandomInt( 1, 4 ) == 1 )
|
||
|
{
|
||
|
AlertSound();
|
||
|
}
|
||
|
|
||
|
FlapSound();
|
||
|
|
||
|
SetIdealActivity( ( Activity )ACT_CROW_TAKEOFF );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_PICK_EVADE_GOAL:
|
||
|
{
|
||
|
if ( GetEnemy() != NULL )
|
||
|
{
|
||
|
//
|
||
|
// Get our enemy's position in x/y.
|
||
|
//
|
||
|
Vector vecEnemyOrigin = GetEnemy()->GetAbsOrigin();
|
||
|
vecEnemyOrigin.z = GetAbsOrigin().z;
|
||
|
|
||
|
//
|
||
|
// Pick a hop goal a random distance along a vector away from our enemy.
|
||
|
//
|
||
|
m_vSavePosition = GetAbsOrigin() - vecEnemyOrigin;
|
||
|
VectorNormalize( m_vSavePosition );
|
||
|
m_vSavePosition = GetAbsOrigin() + m_vSavePosition * ( 32 + random->RandomInt( 0, 32 ) );
|
||
|
|
||
|
GetMotor()->SetIdealYawToTarget( m_vSavePosition );
|
||
|
TaskComplete();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TaskFail( "No enemy" );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_FALL_TO_GROUND:
|
||
|
{
|
||
|
SetFlyingState( FlyState_Falling );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_FIND_HINTNODE:
|
||
|
{
|
||
|
if ( GetGoalEnt() )
|
||
|
{
|
||
|
TaskComplete();
|
||
|
return;
|
||
|
}
|
||
|
// Overloaded because we search over a greater distance.
|
||
|
if ( !GetHintNode() )
|
||
|
{
|
||
|
SetHintNode(CAI_HintManager::FindHint( this, HINT_CROW_FLYTO_POINT, bits_HINT_NODE_NEAREST | bits_HINT_NODE_USE_GROUP, 10000 ));
|
||
|
}
|
||
|
|
||
|
if ( GetHintNode() )
|
||
|
{
|
||
|
TaskComplete();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TaskFail( FAIL_NO_HINT_NODE );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_GET_PATH_TO_HINTNODE:
|
||
|
{
|
||
|
//How did this happen?!
|
||
|
if ( GetGoalEnt() == this )
|
||
|
{
|
||
|
SetGoalEnt( NULL );
|
||
|
}
|
||
|
|
||
|
if ( GetGoalEnt() )
|
||
|
{
|
||
|
SetFlyingState( FlyState_Flying );
|
||
|
StartTargetHandling( GetGoalEnt() );
|
||
|
|
||
|
m_bReachedMoveGoal = false;
|
||
|
TaskComplete();
|
||
|
SetHintNode( NULL );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( GetHintNode() )
|
||
|
{
|
||
|
Vector vHintPos;
|
||
|
GetHintNode()->GetPosition(this, &vHintPos);
|
||
|
|
||
|
SetNavType( NAV_FLY );
|
||
|
CapabilitiesAdd( bits_CAP_MOVE_FLY );
|
||
|
// @HACKHACK: Force allow triangulation. Too many HL2 maps were relying on this feature WRT fly nodes (toml 8/1/2007)
|
||
|
NPC_STATE state = GetState();
|
||
|
m_NPCState = NPC_STATE_SCRIPT;
|
||
|
bool bFoundPath = GetNavigator()->SetGoal( vHintPos );
|
||
|
m_NPCState = state;
|
||
|
if ( !bFoundPath )
|
||
|
{
|
||
|
GetHintNode()->DisableForSeconds( .3 );
|
||
|
SetHintNode(NULL);
|
||
|
}
|
||
|
CapabilitiesRemove( bits_CAP_MOVE_FLY );
|
||
|
}
|
||
|
|
||
|
if ( GetHintNode() )
|
||
|
{
|
||
|
m_bReachedMoveGoal = false;
|
||
|
TaskComplete();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
TaskFail( FAIL_NO_ROUTE );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We have failed to fly normally. Pick a random "up" direction and fly that way.
|
||
|
//
|
||
|
case TASK_CROW_FLY:
|
||
|
{
|
||
|
float flYaw = UTIL_AngleMod( random->RandomInt( -180, 180 ) );
|
||
|
|
||
|
Vector vecNewVelocity( cos( DEG2RAD( flYaw ) ), sin( DEG2RAD( flYaw ) ), random->RandomFloat( 0.1f, 0.5f ) );
|
||
|
vecNewVelocity *= CROW_AIRSPEED;
|
||
|
SetAbsVelocity( vecNewVelocity );
|
||
|
|
||
|
SetIdealActivity( ACT_FLY );
|
||
|
|
||
|
m_bSoar = false;
|
||
|
m_flSoarTime = gpGlobals->curtime + random->RandomFloat( 2, 5 );
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_PICK_RANDOM_GOAL:
|
||
|
{
|
||
|
m_vSavePosition = GetLocalOrigin() + Vector( random->RandomFloat( -48.0f, 48.0f ), random->RandomFloat( -48.0f, 48.0f ), 0 );
|
||
|
TaskComplete();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_HOP:
|
||
|
{
|
||
|
SetIdealActivity( ACT_HOP );
|
||
|
m_flHopStartZ = GetLocalOrigin().z;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_WAIT_FOR_BARNACLE_KILL:
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
BaseClass::StartTask( pTask );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : pTask -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::RunTask( const Task_t *pTask )
|
||
|
{
|
||
|
switch ( pTask->iTask )
|
||
|
{
|
||
|
case TASK_CROW_TAKEOFF:
|
||
|
{
|
||
|
if ( GetNavigator()->IsGoalActive() )
|
||
|
{
|
||
|
GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + GetNavigator()->GetCurWaypointPos(), AI_KEEP_YAW_SPEED );
|
||
|
}
|
||
|
else
|
||
|
TaskFail( FAIL_NO_ROUTE );
|
||
|
|
||
|
if ( IsActivityFinished() )
|
||
|
{
|
||
|
TaskComplete();
|
||
|
SetIdealActivity( ACT_FLY );
|
||
|
|
||
|
m_bSoar = false;
|
||
|
m_flSoarTime = gpGlobals->curtime + random->RandomFloat( 2, 5 );
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_HOP:
|
||
|
{
|
||
|
if ( IsActivityFinished() )
|
||
|
{
|
||
|
TaskComplete();
|
||
|
SetIdealActivity( ACT_IDLE );
|
||
|
}
|
||
|
|
||
|
if ( ( GetAbsOrigin().z < m_flHopStartZ ) && ( !( GetFlags() & FL_ONGROUND ) ) )
|
||
|
{
|
||
|
//
|
||
|
// We've hopped off of something! See if we're going to fall very far.
|
||
|
//
|
||
|
trace_t tr;
|
||
|
AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector( 0, 0, -32 ), MASK_SOLID, this, HL2COLLISION_GROUP_CROW, &tr );
|
||
|
if ( tr.fraction == 1.0f )
|
||
|
{
|
||
|
//
|
||
|
// We're falling! Better fly away. SelectSchedule will check ONGROUND and do the right thing.
|
||
|
//
|
||
|
TaskComplete();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//
|
||
|
// We'll be okay. Don't check again unless what we're hopping onto moves
|
||
|
// out from under us.
|
||
|
//
|
||
|
m_flHopStartZ = GetAbsOrigin().z - ( 32 * tr.fraction );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Face the direction we are flying.
|
||
|
//
|
||
|
case TASK_CROW_FLY:
|
||
|
{
|
||
|
GetMotor()->SetIdealYawToTargetAndUpdate( GetAbsOrigin() + GetAbsVelocity(), AI_KEEP_YAW_SPEED );
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_FALL_TO_GROUND:
|
||
|
{
|
||
|
if ( GetFlags() & FL_ONGROUND )
|
||
|
{
|
||
|
SetFlyingState( FlyState_Walking );
|
||
|
TaskComplete();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TASK_CROW_WAIT_FOR_BARNACLE_KILL:
|
||
|
{
|
||
|
if ( m_flNextFlinchTime < gpGlobals->curtime )
|
||
|
{
|
||
|
m_flNextFlinchTime = gpGlobals->curtime + random->RandomFloat( 0.5f, 2.0f );
|
||
|
// dvs: TODO: squirm
|
||
|
// dvs: TODO: spawn feathers
|
||
|
EmitSound( "NPC_Crow.Squawk" );
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
CAI_BaseNPC::RunTask( pTask );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------------
|
||
|
// Purpose: Override to do crow specific gibs.
|
||
|
// Output : Returns true to gib, false to not gib.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CNPC_Crow::CorpseGib( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
EmitSound( "NPC_Crow.Gib" );
|
||
|
|
||
|
// TODO: crow gibs?
|
||
|
//CGib::SpawnSpecificGibs( this, CROW_GIB_COUNT, 300, 400, "models/gibs/crow_gibs.mdl");
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Don't allow ridiculous forces to be applied to the crow. It only weighs
|
||
|
// 1.5kg, so extreme forces will give it ridiculous velocity.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
#define CROW_RAGDOLL_SPEED_LIMIT 1000.0f // Crow ragdoll speed limit in inches per second.
|
||
|
bool CNPC_Crow::BecomeRagdollOnClient( const Vector &force )
|
||
|
{
|
||
|
Vector newForce = force;
|
||
|
|
||
|
if( VPhysicsGetObject() )
|
||
|
{
|
||
|
float flMass = VPhysicsGetObject()->GetMass();
|
||
|
float speed = VectorNormalize( newForce );
|
||
|
speed = MIN( speed, (CROW_RAGDOLL_SPEED_LIMIT * flMass) );
|
||
|
newForce *= speed;
|
||
|
}
|
||
|
|
||
|
return BaseClass::BecomeRagdollOnClient( newForce );
|
||
|
}
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CNPC_Crow::FValidateHintType( CAI_Hint *pHint )
|
||
|
{
|
||
|
return( pHint->HintType() == HINT_CROW_FLYTO_POINT );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Returns the activity for the given hint type.
|
||
|
// Input : sHintType -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
Activity CNPC_Crow::GetHintActivity( short sHintType, Activity HintsActivity )
|
||
|
{
|
||
|
if ( sHintType == HINT_CROW_FLYTO_POINT )
|
||
|
{
|
||
|
return ACT_FLY;
|
||
|
}
|
||
|
|
||
|
return BaseClass::GetHintActivity( sHintType, HintsActivity );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
// Input : pevInflictor -
|
||
|
// pevAttacker -
|
||
|
// flDamage -
|
||
|
// bitsDamageType -
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int CNPC_Crow::OnTakeDamage_Alive( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
// TODO: spew a feather or two
|
||
|
return BaseClass::OnTakeDamage_Alive( info );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Returns the best new schedule for this NPC based on current conditions.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int CNPC_Crow::SelectSchedule( void )
|
||
|
{
|
||
|
if ( HasCondition( COND_CROW_BARNACLED ) )
|
||
|
{
|
||
|
// Caught by a barnacle!
|
||
|
return SCHED_CROW_BARNACLED;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we're flying, just find somewhere to fly to.
|
||
|
//
|
||
|
if ( IsFlying() )
|
||
|
{
|
||
|
return SCHED_CROW_IDLE_FLY;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we were told to fly away via our FlyAway input, do so ASAP.
|
||
|
//
|
||
|
if ( HasCondition( COND_CROW_FORCED_FLY ) )
|
||
|
{
|
||
|
ClearCondition( COND_CROW_FORCED_FLY );
|
||
|
return SCHED_CROW_FLY_AWAY;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we're not flying but we're not on the ground, start flying.
|
||
|
// Maybe we hopped off of something? Don't do this immediately upon
|
||
|
// because we may be falling to the ground on spawn.
|
||
|
//
|
||
|
if ( !( GetFlags() & FL_ONGROUND ) && ( gpGlobals->curtime > 2.0 ) && m_bOnJeep == false )
|
||
|
{
|
||
|
return SCHED_CROW_FLY_AWAY;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we heard a gunshot or have taken damage, fly away.
|
||
|
//
|
||
|
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
|
||
|
{
|
||
|
return SCHED_CROW_FLY_AWAY;
|
||
|
}
|
||
|
|
||
|
if ( m_flDangerSoundTime <= gpGlobals->curtime )
|
||
|
{
|
||
|
if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_COMBAT ) )
|
||
|
{
|
||
|
m_flDangerSoundTime = gpGlobals->curtime + 10.0f;
|
||
|
return SCHED_CROW_FLY_AWAY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If someone we hate is getting WAY too close for comfort, fly away.
|
||
|
//
|
||
|
if ( HasCondition( COND_CROW_ENEMY_WAY_TOO_CLOSE ) )
|
||
|
{
|
||
|
ClearCondition( COND_CROW_ENEMY_WAY_TOO_CLOSE );
|
||
|
|
||
|
m_nMorale = 0;
|
||
|
return SCHED_CROW_FLY_AWAY;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If someone we hate is getting a little too close for comfort, avoid them.
|
||
|
//
|
||
|
if ( HasCondition( COND_CROW_ENEMY_TOO_CLOSE ) && m_flDangerSoundTime <= gpGlobals->curtime )
|
||
|
{
|
||
|
ClearCondition( COND_CROW_ENEMY_TOO_CLOSE );
|
||
|
|
||
|
if ( m_bOnJeep == true )
|
||
|
{
|
||
|
m_nMorale = 0;
|
||
|
return SCHED_CROW_FLY_AWAY;
|
||
|
}
|
||
|
|
||
|
if ( m_flEnemyDist > 400 )
|
||
|
{
|
||
|
return SCHED_CROW_WALK_AWAY;
|
||
|
}
|
||
|
else if ( m_flEnemyDist > 300 )
|
||
|
{
|
||
|
m_nMorale -= 1;
|
||
|
return SCHED_CROW_RUN_AWAY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch ( m_NPCState )
|
||
|
{
|
||
|
case NPC_STATE_IDLE:
|
||
|
case NPC_STATE_ALERT:
|
||
|
case NPC_STATE_COMBAT:
|
||
|
{
|
||
|
if ( !IsFlying() )
|
||
|
{
|
||
|
if ( m_bOnJeep == true )
|
||
|
return SCHED_IDLE_STAND;
|
||
|
|
||
|
//
|
||
|
// If we are hanging out on the ground, see if it is time to pick a new place to walk to.
|
||
|
//
|
||
|
if ( gpGlobals->curtime > m_flGroundIdleMoveTime )
|
||
|
{
|
||
|
m_flGroundIdleMoveTime = gpGlobals->curtime + random->RandomFloat( 10.0f, 20.0f );
|
||
|
return SCHED_CROW_IDLE_WALK;
|
||
|
}
|
||
|
|
||
|
return SCHED_IDLE_STAND;
|
||
|
}
|
||
|
|
||
|
// TODO: need idle flying behaviors!
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return BaseClass::SelectSchedule();
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::Precache( void )
|
||
|
{
|
||
|
BaseClass::Precache();
|
||
|
|
||
|
PrecacheModel( "models/crow.mdl" );
|
||
|
PrecacheModel( "models/pigeon.mdl" );
|
||
|
PrecacheModel( "models/seagull.mdl" );
|
||
|
|
||
|
//Crow
|
||
|
PrecacheScriptSound( "NPC_Crow.Hop" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Squawk" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Gib" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Idle" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Alert" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Die" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Pain" );
|
||
|
PrecacheScriptSound( "NPC_Crow.Flap" );
|
||
|
|
||
|
//Seagull
|
||
|
PrecacheScriptSound( "NPC_Seagull.Pain" );
|
||
|
PrecacheScriptSound( "NPC_Seagull.Idle" );
|
||
|
|
||
|
//Pigeon
|
||
|
PrecacheScriptSound( "NPC_Pigeon.Idle");
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Sounds.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
void CNPC_Crow::IdleSound( void )
|
||
|
{
|
||
|
if ( m_iBirdType != BIRDTYPE_CROW )
|
||
|
return;
|
||
|
|
||
|
EmitSound( "NPC_Crow.Idle" );
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNPC_Crow::AlertSound( void )
|
||
|
{
|
||
|
if ( m_iBirdType != BIRDTYPE_CROW )
|
||
|
return;
|
||
|
|
||
|
EmitSound( "NPC_Crow.Alert" );
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNPC_Crow::PainSound( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
if ( m_iBirdType != BIRDTYPE_CROW )
|
||
|
return;
|
||
|
|
||
|
EmitSound( "NPC_Crow.Pain" );
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNPC_Crow::DeathSound( const CTakeDamageInfo &info )
|
||
|
{
|
||
|
if ( m_iBirdType != BIRDTYPE_CROW )
|
||
|
return;
|
||
|
|
||
|
EmitSound( "NPC_Crow.Die" );
|
||
|
}
|
||
|
|
||
|
void CNPC_Crow::FlapSound( void )
|
||
|
{
|
||
|
EmitSound( "NPC_Crow.Flap" );
|
||
|
m_bPlayedLoopingSound = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: This is a generic function (to be implemented by sub-classes) to
|
||
|
// handle specific interactions between different types of characters
|
||
|
// (For example the barnacle grabbing an NPC)
|
||
|
// Input : Constant for the type of interaction
|
||
|
// Output : true - if sub-class has a response for the interaction
|
||
|
// false - if sub-class has no response
|
||
|
//-----------------------------------------------------------------------------
|
||
|
bool CNPC_Crow::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sourceEnt )
|
||
|
{
|
||
|
if ( interactionType == g_interactionBarnacleVictimDangle )
|
||
|
{
|
||
|
// Die instantly
|
||
|
return false;
|
||
|
}
|
||
|
else if ( interactionType == g_interactionBarnacleVictimGrab )
|
||
|
{
|
||
|
if ( GetFlags() & FL_ONGROUND )
|
||
|
{
|
||
|
SetGroundEntity( NULL );
|
||
|
}
|
||
|
|
||
|
// return ideal grab position
|
||
|
if (data)
|
||
|
{
|
||
|
// FIXME: need a good way to ensure this contract
|
||
|
*((Vector *)data) = GetAbsOrigin() + Vector( 0, 0, 5 );
|
||
|
}
|
||
|
|
||
|
StopLoopingSounds();
|
||
|
|
||
|
SetThink( NULL );
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose:
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int CNPC_Crow::DrawDebugTextOverlays( void )
|
||
|
{
|
||
|
int nOffset = BaseClass::DrawDebugTextOverlays();
|
||
|
|
||
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
||
|
{
|
||
|
char tempstr[512];
|
||
|
Q_snprintf( tempstr, sizeof( tempstr ), "morale: %d", m_nMorale );
|
||
|
EntityText( nOffset, tempstr, 0 );
|
||
|
nOffset++;
|
||
|
|
||
|
if ( GetEnemy() != NULL )
|
||
|
{
|
||
|
Q_snprintf( tempstr, sizeof( tempstr ), "enemy (dist): %s (%g)", GetEnemy()->GetClassname(), ( double )m_flEnemyDist );
|
||
|
EntityText( nOffset, tempstr, 0 );
|
||
|
nOffset++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nOffset;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
// Purpose: Determines which sounds the crow cares about.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
int CNPC_Crow::GetSoundInterests( void )
|
||
|
{
|
||
|
return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER;
|
||
|
}
|
||
|
|
||
|
|
||
|
//-----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Schedules
|
||
|
//
|
||
|
//-----------------------------------------------------------------------------
|
||
|
|
||
|
AI_BEGIN_CUSTOM_NPC( npc_crow, CNPC_Crow )
|
||
|
|
||
|
DECLARE_TASK( TASK_CROW_FIND_FLYTO_NODE )
|
||
|
//DECLARE_TASK( TASK_CROW_PREPARE_TO_FLY )
|
||
|
DECLARE_TASK( TASK_CROW_TAKEOFF )
|
||
|
DECLARE_TASK( TASK_CROW_FLY )
|
||
|
DECLARE_TASK( TASK_CROW_PICK_RANDOM_GOAL )
|
||
|
DECLARE_TASK( TASK_CROW_HOP )
|
||
|
DECLARE_TASK( TASK_CROW_PICK_EVADE_GOAL )
|
||
|
DECLARE_TASK( TASK_CROW_WAIT_FOR_BARNACLE_KILL )
|
||
|
|
||
|
// experiment
|
||
|
DECLARE_TASK( TASK_CROW_FALL_TO_GROUND )
|
||
|
DECLARE_TASK( TASK_CROW_PREPARE_TO_FLY_RANDOM )
|
||
|
|
||
|
DECLARE_ACTIVITY( ACT_CROW_TAKEOFF )
|
||
|
DECLARE_ACTIVITY( ACT_CROW_SOAR )
|
||
|
DECLARE_ACTIVITY( ACT_CROW_LAND )
|
||
|
|
||
|
DECLARE_ANIMEVENT( AE_CROW_HOP )
|
||
|
DECLARE_ANIMEVENT( AE_CROW_FLY )
|
||
|
DECLARE_ANIMEVENT( AE_CROW_TAKEOFF )
|
||
|
|
||
|
|
||
|
DECLARE_CONDITION( COND_CROW_ENEMY_TOO_CLOSE )
|
||
|
DECLARE_CONDITION( COND_CROW_ENEMY_WAY_TOO_CLOSE )
|
||
|
DECLARE_CONDITION( COND_CROW_FORCED_FLY )
|
||
|
DECLARE_CONDITION( COND_CROW_BARNACLED )
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_IDLE_WALK,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_IDLE_STAND"
|
||
|
" TASK_CROW_PICK_RANDOM_GOAL 0"
|
||
|
" TASK_GET_PATH_TO_SAVEPOSITION 0"
|
||
|
" TASK_WALK_PATH 0"
|
||
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
||
|
" TASK_WAIT_PVS 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
" COND_CROW_FORCED_FLY"
|
||
|
" COND_PROVOKED"
|
||
|
" COND_CROW_ENEMY_TOO_CLOSE"
|
||
|
" COND_NEW_ENEMY"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_LIGHT_DAMAGE"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_HEAR_DANGER"
|
||
|
" COND_HEAR_COMBAT"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_WALK_AWAY,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CROW_FLY_AWAY"
|
||
|
" TASK_CROW_PICK_EVADE_GOAL 0"
|
||
|
" TASK_GET_PATH_TO_SAVEPOSITION 0"
|
||
|
" TASK_WALK_PATH 0"
|
||
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
" COND_CROW_FORCED_FLY"
|
||
|
" COND_CROW_ENEMY_WAY_TOO_CLOSE"
|
||
|
" COND_NEW_ENEMY"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_LIGHT_DAMAGE"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_HEAR_DANGER"
|
||
|
" COND_HEAR_COMBAT"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_RUN_AWAY,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CROW_FLY_AWAY"
|
||
|
" TASK_CROW_PICK_EVADE_GOAL 0"
|
||
|
" TASK_GET_PATH_TO_SAVEPOSITION 0"
|
||
|
" TASK_RUN_PATH 0"
|
||
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
" COND_CROW_FORCED_FLY"
|
||
|
" COND_CROW_ENEMY_WAY_TOO_CLOSE"
|
||
|
" COND_NEW_ENEMY"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_LIGHT_DAMAGE"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_HEAR_DANGER"
|
||
|
" COND_HEAR_COMBAT"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_HOP_AWAY,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CROW_FLY_AWAY"
|
||
|
" TASK_STOP_MOVING 0"
|
||
|
" TASK_CROW_PICK_EVADE_GOAL 0"
|
||
|
" TASK_FACE_IDEAL 0"
|
||
|
" TASK_CROW_HOP 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
" COND_CROW_FORCED_FLY"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_LIGHT_DAMAGE"
|
||
|
" COND_HEAVY_DAMAGE"
|
||
|
" COND_HEAR_DANGER"
|
||
|
" COND_HEAR_COMBAT"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_IDLE_FLY,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_FIND_HINTNODE 0"
|
||
|
" TASK_GET_PATH_TO_HINTNODE 0"
|
||
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_FLY_AWAY,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CROW_FLY_FAIL"
|
||
|
" TASK_STOP_MOVING 0"
|
||
|
" TASK_FIND_HINTNODE 0"
|
||
|
" TASK_GET_PATH_TO_HINTNODE 0"
|
||
|
" TASK_CROW_TAKEOFF 0"
|
||
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_FLY,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CROW_FLY_FAIL"
|
||
|
" TASK_STOP_MOVING 0"
|
||
|
" TASK_CROW_TAKEOFF 0"
|
||
|
" TASK_CROW_FLY 0"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_FLY_FAIL,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_CROW_FALL_TO_GROUND 0"
|
||
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_CROW_IDLE_WALK"
|
||
|
" "
|
||
|
" Interrupts"
|
||
|
)
|
||
|
|
||
|
//=========================================================
|
||
|
// Crow is in the clutches of a barnacle
|
||
|
DEFINE_SCHEDULE
|
||
|
(
|
||
|
SCHED_CROW_BARNACLED,
|
||
|
|
||
|
" Tasks"
|
||
|
" TASK_STOP_MOVING 0"
|
||
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_HOP"
|
||
|
" TASK_CROW_WAIT_FOR_BARNACLE_KILL 0"
|
||
|
|
||
|
" Interrupts"
|
||
|
)
|
||
|
|
||
|
|
||
|
AI_END_CUSTOM_NPC()
|