diff options
Diffstat (limited to 'game/server/tf2/npc_bug_warrior.cpp')
| -rw-r--r-- | game/server/tf2/npc_bug_warrior.cpp | 1039 |
1 files changed, 1039 insertions, 0 deletions
diff --git a/game/server/tf2/npc_bug_warrior.cpp b/game/server/tf2/npc_bug_warrior.cpp new file mode 100644 index 0000000..af900d0 --- /dev/null +++ b/game/server/tf2/npc_bug_warrior.cpp @@ -0,0 +1,1039 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: The warrior bug +// +// $NoKeywords: $ +//=============================================================================// +#include "cbase.h" +#include "AI_Task.h" +#include "AI_Default.h" +#include "AI_Schedule.h" +#include "AI_Hull.h" +#include "AI_Hint.h" +#include "AI_Navigator.h" +#include "AI_Memory.h" +#include "AI_Squad.h" +#include "activitylist.h" +#include "soundent.h" +#include "game.h" +#include "NPCEvent.h" +#include "tf_player.h" +#include "EntityList.h" +#include "ndebugoverlay.h" +#include "shake.h" +#include "monstermaker.h" +#include "decals.h" +#include "vstdlib/random.h" +#include "tf_obj.h" +#include "engine/IEngineSound.h" +#include "IEffects.h" +#include "npc_bug_warrior.h" +#include "npc_bug_hole.h" + +ConVar npc_bug_warrior_health( "npc_bug_warrior_health", "150" ); +ConVar npc_bug_warrior_swipe_damage( "npc_bug_warrior_swipe_damage", "20" ); + +BEGIN_DATADESC( CNPC_Bug_Warrior ) + + DEFINE_FIELD( m_flIdleDelay, FIELD_FLOAT ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( npc_bug_warrior, CNPC_Bug_Warrior ); +IMPLEMENT_CUSTOM_AI( npc_bug_warrior, CNPC_Bug_Warrior ); + +// Bug interactions +int g_interactionBugSquadAttacking = 0; + +//================================================== +// Bug Conditions +//================================================== +enum BugConditions +{ + COND_WBUG_STOP_FLEEING = LAST_SHARED_CONDITION, + COND_WBUG_RETURN_TO_BUGHOLE, + COND_WBUG_ASSIST_FELLOW_BUG, +}; + +//================================================== +// Bug Schedules +//================================================== + +enum BugSchedules +{ + SCHED_WBUG_CHASE_ENEMY = LAST_SHARED_SCHEDULE, + SCHED_WBUG_FLEE_ENEMY, + SCHED_WBUG_CHASE_ENEMY_FAILED, + SCHED_WBUG_PATROL, + SCHED_WBUG_RETURN_TO_BUGHOLE, + SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE, + SCHED_WBUG_ASSIST_FELLOW_BUG, +}; + +//================================================== +// Bug Tasks +//================================================== + +enum BugTasks +{ + TASK_WBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK, + TASK_WBUG_GET_PATH_TO_PATROL, + TASK_WBUG_GET_PATH_TO_BUGHOLE, + TASK_WBUG_HOLE_REMOVE, + TASK_WBUG_GET_PATH_TO_ASSIST, +}; + +//================================================== +// Bug Activities +//================================================== + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CNPC_Bug_Warrior::CNPC_Bug_Warrior( void ) +{ + m_flFieldOfView = 0.5f; + m_flIdleDelay = 0.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: Setup our schedules and tasks, etc. +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::InitCustomSchedules( void ) +{ + INIT_CUSTOM_AI( CNPC_Bug_Warrior ); + + // Register our interactions + ADD_CUSTOM_INTERACTION( g_interactionBugSquadAttacking ); + + // Schedules + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_FLEE_ENEMY ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY_FAILED ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_PATROL ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE ); + ADD_CUSTOM_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_ASSIST_FELLOW_BUG ); + + // Conditions + ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_STOP_FLEEING ); + ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_RETURN_TO_BUGHOLE ); + ADD_CUSTOM_CONDITION( CNPC_Bug_Warrior, COND_WBUG_ASSIST_FELLOW_BUG ); + + // Tasks + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_FLEE ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_PATROL ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_BUGHOLE ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_HOLE_REMOVE ); + ADD_CUSTOM_TASK( CNPC_Bug_Warrior, TASK_WBUG_GET_PATH_TO_ASSIST ); + + // Activities + //ADD_CUSTOM_ACTIVITY( CNPC_Bug_Warrior, ACT_BUG_WARRIOR_DISTRACT ); + + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_FLEE_ENEMY ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_CHASE_ENEMY_FAILED ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_PATROL ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE ); + AI_LOAD_SCHEDULE( CNPC_Bug_Warrior, SCHED_WBUG_ASSIST_FELLOW_BUG ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Spawn( void ) +{ + Precache(); + + SetModel( BUG_WARRIOR_MODEL ); + + SetHullType(HULL_WIDE_HUMAN);//HULL_WIDE_SHORT; + SetHullSizeNormal(); + SetDefaultEyeOffset(); + SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 ); // See from my center + SetDistLook( 1024.0 ); + + SetNavType(NAV_GROUND); + m_NPCState = NPC_STATE_NONE; + SetBloodColor( BLOOD_COLOR_YELLOW ); + m_iHealth = npc_bug_warrior_health.GetFloat(); + + SetSolid( SOLID_BBOX ); + AddSolidFlags( FSOLID_NOT_STANDABLE ); + SetMoveType( MOVETYPE_STEP ); + + m_iszPatrolPathName = NULL_STRING; + + // Only do this if a squadname appears in the entity + if ( m_SquadName != NULL_STRING ) + { + CapabilitiesAdd( bits_CAP_SQUAD ); + } + + CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_MELEE_ATTACK1 ); + + NPCInit(); + + BaseClass::Spawn(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Precache( void ) +{ + PrecacheModel( BUG_WARRIOR_MODEL ); + + PrecacheScriptSound( "NPC_Bug_Warrior.AttackHit" ); + PrecacheScriptSound( "NPC_Bug_Warrior.AttackSingle" ); + PrecacheScriptSound( "NPC_Bug_Warrior.Idle" ); + PrecacheScriptSound( "NPC_Bug_Warrior.Pain" ); + + BaseClass::Precache(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::SelectSchedule( void ) +{ + ClearCondition( COND_WBUG_STOP_FLEEING ); + + // Turn towards sounds + if ( HasCondition(COND_HEAR_DANGER) || HasCondition(COND_HEAR_COMBAT) ) + { + CSound *pSound = GetBestSound(); + if ( pSound) + { + if ( !HasCondition( COND_SEE_ENEMY ) && ( pSound->m_iType & (SOUND_PLAYER | SOUND_COMBAT) ) ) + { + GetMotor()->SetIdealYawToTarget( pSound->GetSoundReactOrigin() ); + } + } + } + + switch ( m_NPCState ) + { + case NPC_STATE_IDLE: + { + // If I have an enemy, but I don't have any nearby friends, flee + if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() ) + { + SetState( NPC_STATE_ALERT ); + return SCHED_WBUG_FLEE_ENEMY; + } + + // Fellow bug might be requesting assistance + if ( HasCondition( COND_WBUG_ASSIST_FELLOW_BUG ) ) + return SCHED_WBUG_ASSIST_FELLOW_BUG; + + // BugHole might be requesting help + if ( HasCondition( COND_WBUG_RETURN_TO_BUGHOLE ) ) + return SCHED_WBUG_RETURN_TO_BUGHOLE; + + // Return to my bughole + return SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE; + break; + } + case NPC_STATE_ALERT: + { + // If I have an enemy, but I don't have any nearby friends, flee + if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() ) + { + SetState( NPC_STATE_ALERT ); + return SCHED_WBUG_FLEE_ENEMY; + } + + // Fellow bug might be requesting assistance + if ( HasCondition( COND_WBUG_ASSIST_FELLOW_BUG ) ) + return SCHED_WBUG_ASSIST_FELLOW_BUG; + + // BugHole might be requesting help + if ( HasCondition( COND_WBUG_RETURN_TO_BUGHOLE ) ) + return SCHED_WBUG_RETURN_TO_BUGHOLE; + + // If I have a patrol path, walk it + if ( m_iszPatrolPathName != NULL_STRING ) + return SCHED_WBUG_PATROL; + + break; + } + case NPC_STATE_COMBAT: + { + // Did I lose my enemy? + if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) ) + { + SetEnemy( NULL ); + m_bFightingToDeath = false; + SetState(NPC_STATE_IDLE); + return BaseClass::SelectSchedule(); + } + + // If I have an enemy, but I don't have any nearby friends, flee + if ( HasCondition( COND_SEE_ENEMY ) && ShouldFlee() ) + return SCHED_WBUG_FLEE_ENEMY; + + // If we're able to melee, do so + if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) ) + return BaseClass::SelectSchedule(); + } + break; + } + + return BaseClass::SelectSchedule(); +} + +//----------------------------------------------------------------------------- +// Purpose: override/translate a schedule by type +// Input : Type - schedule type +// Output : int - translated type +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::TranslateSchedule( int scheduleType ) +{ + if ( scheduleType == SCHED_CHASE_ENEMY ) + { + // Tell my squad that I'm attacking this guy + if ( m_pSquad ) + { + m_pSquad->BroadcastInteraction( g_interactionBugSquadAttacking, (void *)GetEnemy(), this ); + } + return SCHED_WBUG_CHASE_ENEMY; + } + + return BaseClass::TranslateSchedule(scheduleType); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::StartTask( const Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_WBUG_GET_PATH_TO_FLEE: + { + // Always tell our bughole that we're under attack + if ( m_hMyBugHole ) + { + m_hMyBugHole->IncomingFleeingBug( this ); + } + + // We're fleeing from an enemy. + // If I have a squadmate, run to him. + CAI_BaseNPC *pSquadMate; + if ( m_pSquad && (pSquadMate = m_pSquad->NearestSquadMember(this)) != false ) + { + SetTarget( pSquadMate ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole + if ( m_hMyBugHole ) + { + SetTarget( m_hMyBugHole ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + // Give up, and fight to the death + m_bFightingToDeath = true; + TaskComplete(); + } + break; + + case TASK_WBUG_GET_PATH_TO_PATROL: + { + // Get a path to the next point in the patrol. + // Abort if we have no patrol path + if ( !m_iszPatrolPathName ) + { + TaskFail( "Attempting to patrol without patrol path." ); + return; + } + + SetHintNode( CAI_HintManager::FindHint( this, HINT_BUG_PATROL_POINT, bits_HINT_NODE_RANDOM, 4096 ) ); + if( !GetHintNode() ) + { + TaskFail("Couldn't find patrol node"); + return; + } + + Vector vHintPos; + GetHintNode()->GetPosition( this, &vHintPos ); + AI_NavGoal_t goal( vHintPos, ACT_RUN ); + GetNavigator()->SetGoal( goal ); + TaskComplete(); + } + break; + + case TASK_WBUG_GET_PATH_TO_BUGHOLE: + { + // Get a path back to my bughole + // If we have no squad, or we couldn't get a path to our squadmate, look for a bughole + if ( m_hMyBugHole ) + { + SetTarget( m_hMyBugHole ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + TaskFail( "Couldn't get to bughole." ); + } + break; + + case TASK_WBUG_HOLE_REMOVE: + { + // Crawl inside the bughole and remove myself + AddEffects( EF_NODRAW ); + AddSolidFlags( FSOLID_NOT_SOLID ); + Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) ); + + // Tell the bughole + if ( m_hMyBugHole ) + { + m_hMyBugHole->BugReturned(); + } + + TaskComplete(); + } + break; + + case TASK_WBUG_GET_PATH_TO_ASSIST: + { + if ( m_hAssistTarget ) + { + SetTarget( m_hAssistTarget ); + AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN ); + if ( GetNavigator()->SetGoal( goal ) ) + { + TaskComplete(); + return; + } + } + + TaskFail( "Couldn't get to assist target." ); + } + break; + + default: + BaseClass::StartTask( pTask ); + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pTask - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::RunTask( const Task_t *pTask ) +{ + BaseClass::RunTask( pTask ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::FValidateHintType(CAI_Hint *pHint) +{ + if ( pHint->HintType() == HINT_BUG_PATROL_POINT ) + { + // Make sure the name matches the patrol path I'm on + if ( pHint->GetEntityName() == m_iszPatrolPathName ) + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Handle specific interactions with other NPCs +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sender ) +{ + // Check for a target found while burrowed + if ( interactionType == g_interactionBugSquadAttacking ) + { + // Ignore ones sent by me + if ( sender == this ) + return false; + + // Interrupt me if I'm fleeing + if ( IsCurSchedule(SCHED_WBUG_FLEE_ENEMY) || + IsCurSchedule(SCHED_WBUG_CHASE_ENEMY_FAILED) ) + { + SetCondition( COND_WBUG_STOP_FLEEING ); + } + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pVictim - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Event_Killed( const CTakeDamageInfo &info ) +{ + BaseClass::Event_Killed( info ); + + // Remove myself in a minute + if ( !ShouldFadeOnDeath() ) + { + SetThink( SUB_Remove ); + SetNextThink( gpGlobals->curtime + 20 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::MeleeAttack( float distance, float damage, QAngle& viewPunch, Vector& shove ) +{ + if ( GetEnemy() == NULL ) + return; + + // Trace directly at my target + Vector vStart = GetAbsOrigin(); + vStart.z += WorldAlignSize().z * 0.5; + Vector vecForward = (GetEnemy()->EyePosition() - vStart); + VectorNormalize( vecForward ); + Vector vEnd = vStart + (vecForward * distance ); + + // Use half the size of my target for the box + Vector vecHalfTraceBox = (GetEnemy()->WorldAlignMaxs() - GetEnemy()->WorldAlignMins()) * 0.25; + //NDebugOverlay::Box( vStart, -Vector(10,10,10), Vector(10,10,10), 0,255,0,20,1.0); + //NDebugOverlay::Box( GetEnemy()->EyePosition(), -Vector(10,10,10), Vector(10,10,10), 255,255,255,20,1.0); + CBaseEntity *pHurt = CheckTraceHullAttack( vStart, vEnd, -vecHalfTraceBox, vecHalfTraceBox, damage, DMG_SLASH ); + + if ( pHurt ) + { + CBasePlayer *pPlayer = ToBasePlayer( pHurt ); + + if ( pPlayer ) + { + //Kick the player angles + pPlayer->ViewPunch( viewPunch ); + + Vector dir = pHurt->GetAbsOrigin() - GetAbsOrigin(); + VectorNormalize(dir); + + QAngle angles; + VectorAngles( dir, angles ); + Vector forward, right; + AngleVectors( angles, &forward, &right, NULL ); + + // Push the target back + Vector vecImpulse; + VectorMultiply( right, -shove[1], vecImpulse ); + VectorMA( vecImpulse, -shove[0], forward, vecImpulse ); + pHurt->ApplyAbsVelocityImpulse( vecImpulse ); + } + + // Play a random attack hit sound + EmitSound( "NPC_Bug_Warrior.AttackHit" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pEvent - +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::HandleAnimEvent( animevent_t *pEvent ) +{ + switch ( pEvent->event ) + { + case BUG_WARRIOR_AE_MELEE_HIT1: + MeleeAttack( BUG_WARRIOR_MELEE1_RANGE, npc_bug_warrior_swipe_damage.GetFloat(), QAngle( 20.0f, 0.0f, 0.0f ), Vector( -350.0f, 1.0f, 1.0f ) ); + return; + break; + + case BUG_WARRIOR_AE_MELEE_SOUND1: + { + EmitSound( "NPC_Bug_Warrior.AttackSingle" ); + return; + } + break; + } + + BaseClass::HandleAnimEvent( pEvent ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pInflictor - +// *pAttacker - +// flDamage - +// bitsDamageType - +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + Vector faceDir; + + return BaseClass::OnTakeDamage_Alive( info ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::IdleSound( void ) +{ + EmitSound( "NPC_Bug_Warrior.Idle" ); + m_flIdleDelay = gpGlobals->curtime + 4.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::PainSound( const CTakeDamageInfo &info ) +{ + EmitSound( "NPC_Bug_Warrior.Pain" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vecTarget - +// Output : float +//----------------------------------------------------------------------------- +float CNPC_Bug_Warrior::CalcIdealYaw( const Vector &vecTarget ) +{ + //If we can see our enemy but not reach them, face them always + if ( ( GetEnemy() != NULL ) && ( HasCondition( COND_SEE_ENEMY ) && HasCondition( COND_ENEMY_UNREACHABLE ) ) ) + { + return UTIL_VecToYaw ( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() ); + } + + return BaseClass::CalcIdealYaw( vecTarget ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CNPC_Bug_Warrior::MaxYawSpeed( void ) +{ + switch ( GetActivity() ) + { + case ACT_IDLE: + return 15.0f; + break; + + case ACT_WALK: + return 15.0f; + break; + + default: + case ACT_RUN: + return 30.0f; + break; + } + + return 30.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::ShouldPlayIdleSound( void ) +{ + //Only do idles in the right states + if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) ) + return false; + + //Gagged monsters don't talk + if ( HasSpawnFlags( SF_NPC_GAG ) ) + return false; + + //Don't cut off another sound or play again too soon + if ( m_flIdleDelay > gpGlobals->curtime ) + return false; + + //Randomize it a bit + if ( random->RandomInt( 0, 20 ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flDot - +// flDist - +// Output : int +//----------------------------------------------------------------------------- +int CNPC_Bug_Warrior::MeleeAttack1Conditions( float flDot, float flDist ) +{ + if ( ( gpGlobals->curtime - m_flLastAttackTime ) < 1.0f ) + return 0; + +#if 0 + + float flPrDist, flPrDot; + Vector vecPrPos; + Vector2D vec2DPrDir; + + //Get our likely position in one second + UTIL_GetPredictedPosition( GetEnemy(), 0.5f, &vecPrPos ); + + flPrDist = ( vecPrPos - GetAbsOrigin() ).Length(); + + vec2DPrDir = ( vecPrPos - GetAbsOrigin() ).AsVector2D(); + + Vector vecBodyDir; + + BodyDirection2D( &vecBodyDir ); + + Vector2D vec2DBodyDir = vecBodyDir.AsVector2D(); + + flPrDot = DotProduct2D (vec2DPrDir, vec2DBodyDir ); + + if ( flPrDist > BUG_WARRIOR_MELEE1_RANGE ) + return COND_TOO_FAR_TO_ATTACK; + + if ( flPrDot < 0.5f ) + return COND_NOT_FACING_ATTACK; + +#else + + if ( flDist > BUG_WARRIOR_MELEE1_RANGE ) + return COND_TOO_FAR_TO_ATTACK; + + if ( flDot < 0.5f ) + return COND_NOT_FACING_ATTACK; + +#endif + + return COND_CAN_MELEE_ATTACK1; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this bug should flee +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::ShouldFlee( void ) +{ + // I don't flee if I'm fighting to the death + if ( m_bFightingToDeath ) + return false; + + // I don't flee if I'm not alone + if ( !IsAlone() ) + return false; + + // I don't flee if I'm within sight of a bughole + if ( m_hMyBugHole.Get() && FVisible( m_hMyBugHole ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::IsAlone( void ) +{ + // If I'm not in a squad, make me just fight + if ( !m_pSquad ) + return false; + + // If there aren't any visible squad members, I'm alone + if ( m_pSquad->GetVisibleSquadMembers( this ) == 0 ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::SetBugHole( CMaker_BugHole *pBugHole ) +{ + m_hMyBugHole = pBugHole; +} + +//----------------------------------------------------------------------------- +// Purpose: BugHole is calling me home to defend it +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::ReturnToBugHole( void ) +{ + SetCondition( COND_WBUG_RETURN_TO_BUGHOLE ); +} + +//----------------------------------------------------------------------------- +// Purpose: Assist a bug that's under attack and making it's way back to the bughole +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::Assist( CAI_BaseNPC *pBug ) +{ + // If I'm not idle, I can't assist + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_NONE ) + return; + + m_hAssistTarget = pBug; + SetCondition( COND_WBUG_ASSIST_FELLOW_BUG ); + SetCondition( COND_PROVOKED ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::StartPatrolling( string_t iszPatrolPathName ) +{ + // If I'm not idle, I can't patrol + if ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT && m_NPCState != NPC_STATE_NONE ) + return false; + + // If I'm patrolling already, I can't patrol + if ( IsPatrolling() ) + return false; + + SetState( NPC_STATE_ALERT ); + SetCondition( COND_PROVOKED ); + + // Store off my patrol name + m_iszPatrolPathName = iszPatrolPathName; + m_iPatrolPoint = 0; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this bug is currently patrolling +//----------------------------------------------------------------------------- +bool CNPC_Bug_Warrior::IsPatrolling( void ) +{ + return ( IsCurSchedule(SCHED_WBUG_PATROL) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CNPC_Bug_Warrior::AlertSound( void ) +{ + if ( GetEnemy() ) + { + //FIXME: We need a better solution for inner-squad alerts!! + //SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type. + CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Overridden for team handling +//----------------------------------------------------------------------------- +Disposition_t CNPC_Bug_Warrior::IRelationType( CBaseEntity *pTarget ) +{ + if ( pTarget->GetFlags() & FL_NOTARGET ) + return D_NU; + + if ( pTarget->Classify() == CLASS_PLAYER ) + { + // Ignore stealthed enemies + CBaseTFPlayer *pPlayer = (CBaseTFPlayer *)pTarget; + if ( pPlayer->IsCamouflaged() ) + return D_NU; + + return D_HT; + } + + // Attack objects + if ( pTarget->Classify() == CLASS_MILITARY ) + return D_HT; + + return D_NU; +} + +//------------------------------------------------------------------------------ +// Purpose : Hate sentryguns more than other types of objects +//------------------------------------------------------------------------------ +int CNPC_Bug_Warrior::IRelationPriority( CBaseEntity *pTarget ) +{ + if ( pTarget->Classify() == CLASS_MILITARY ) + { + CBaseObject* pBaseObject = dynamic_cast<CBaseObject*>(pTarget); + if ( pBaseObject && pBaseObject->IsSentrygun() ) + return ( BaseClass::IRelationPriority ( pTarget ) + 1 ); + } + + return BaseClass::IRelationPriority ( pTarget ); +} + + +//------------------------------------------------------------------------------ +// +// Schedules +// +//------------------------------------------------------------------------------ + +//========================================================= +// Chase Enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_CHASE_ENEMY, + + " Tasks" + //" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY_FAILED" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" + " TASK_SET_TOLERANCE_DISTANCE 24" + " TASK_GET_PATH_TO_ENEMY 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_ENEMY_UNREACHABLE" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_GIVE_WAY" + " COND_LOST_ENEMY" +); + +//========================================================= +// Failed to chase my enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_CHASE_ENEMY_FAILED, + + " Tasks" + " TASK_STOP_MOVING 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" + " TASK_WAIT_FACE_ENEMY 0.5" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_ENEMY_DEAD" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" + " COND_CAN_RANGE_ATTACK1" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_RANGE_ATTACK2" + " COND_CAN_MELEE_ATTACK2" + " COND_WBUG_STOP_FLEEING" +); + +//========================================================= +// Flee from our enemy +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_FLEE_ENEMY, + + " Tasks" + " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_FLEE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_TURN_RIGHT 180" + "" + " Interrupts" + " COND_ENEMY_DEAD" + " COND_WBUG_STOP_FLEEING" + " COND_LOST_ENEMY" +); + +//========================================================= +// Walk a set of patrol points +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_PATROL, + + " Tasks" + //" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_WBUG_CHASE_ENEMY" + " TASK_SET_TOLERANCE_DISTANCE 256" + " TASK_WBUG_GET_PATH_TO_PATROL 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Use look around + " TASK_WAIT 2" + " TASK_WAIT_RANDOM 4" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" + " COND_WBUG_RETURN_TO_BUGHOLE" +); + +//========================================================= +// Return to defend a bughole +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_RETURN_TO_BUGHOLE, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_BUGHOLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" +); + +//========================================================= +// Return to a bughole and remove myself +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_RETURN_TO_BUGHOLE_AND_REMOVE, + + " Tasks" + " TASK_WAIT 5" // Wait for 5-10 seconds to see if anything happens + " TASK_WAIT_RANDOM 5" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_BUGHOLE 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + " TASK_WBUG_HOLE_REMOVE 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_HEAR_COMBAT" + " COND_HEAR_DANGER" +); + +//========================================================= +// Move to assist a fellow bug +//========================================================= +AI_DEFINE_SCHEDULE +( + SCHED_WBUG_ASSIST_FELLOW_BUG, + + " Tasks" + " TASK_SET_TOLERANCE_DISTANCE 128" + " TASK_WBUG_GET_PATH_TO_ASSIST 0" + " TASK_RUN_PATH 0" + " TASK_WAIT_FOR_MOVEMENT 0" + "" + " Interrupts" + " COND_NEW_ENEMY" + " COND_CAN_MELEE_ATTACK1" + " COND_CAN_MELEE_ATTACK2" + " COND_GIVE_WAY" +);
\ No newline at end of file |