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.
1599 lines
38 KiB
1599 lines
38 KiB
//========= 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()
|
|
|