From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/hl2/npc_crow.cpp | 3198 +++++++++++++++++------------------ 1 file changed, 1599 insertions(+), 1599 deletions(-) (limited to 'mp/src/game/server/hl2/npc_crow.cpp') diff --git a/mp/src/game/server/hl2/npc_crow.cpp b/mp/src/game/server/hl2/npc_crow.cpp index 37b08f24..0b897b9e 100644 --- a/mp/src/game/server/hl2/npc_crow.cpp +++ b/mp/src/game/server/hl2/npc_crow.cpp @@ -1,1599 +1,1599 @@ -//========= 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() +//========= 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() -- cgit v1.2.3