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