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