//========= Copyright Valve Corporation, All rights reserved. ============// // bot_npc_archer.cpp // A NextBot non-player derived archer // Michael Booth, November 2010 #include "cbase.h" #include "tf_player.h" #include "tf_gamerules.h" #include "tf_team.h" #include "tf_projectile_arrow.h" #include "tf_weapon_grenade_pipebomb.h" #include "nav_mesh/tf_nav_area.h" #include "bot_npc_archer.h" #include "NextBot/Path/NextBotChasePath.h" #include "econ_wearable.h" #include "team_control_point_master.h" #include "particle_parse.h" #include "CRagdollMagnet.h" #include "NextBot/Behavior/BehaviorMoveTo.h" ConVar tf_bot_npc_archer_health( "tf_bot_npc_archer_health", "100", FCVAR_CHEAT ); ConVar tf_bot_npc_archer_speed( "tf_bot_npc_archer_speed", "100", FCVAR_CHEAT ); ConVar tf_bot_npc_archer_shoot_interval( "tf_bot_npc_archer_shoot_interval", "2", FCVAR_CHEAT ); // 2 ConVar tf_bot_npc_archer_arrow_damage( "tf_bot_npc_archer_arrow_damage", "75", FCVAR_CHEAT ); //----------------------------------------------------------------- // The Bot NPC //----------------------------------------------------------------------------------------------------- LINK_ENTITY_TO_CLASS( bot_npc_archer, CBotNPCArcher ); PRECACHE_REGISTER( bot_npc_archer ); //----------------------------------------------------------------------------------------------------- CBotNPCArcher::CBotNPCArcher() { ALLOCATE_INTENTION_INTERFACE( CBotNPCArcher ); m_locomotor = new NextBotGroundLocomotion( this ); m_body = new CBotNPCBody( this ); m_eyeOffset = vec3_origin; m_homePos = vec3_origin; } //----------------------------------------------------------------------------------------------------- CBotNPCArcher::~CBotNPCArcher() { DEALLOCATE_INTENTION_INTERFACE; if ( m_locomotor ) delete m_locomotor; if ( m_body ) delete m_body; } //----------------------------------------------------------------------------------------------------- void CBotNPCArcher::Precache() { BaseClass::Precache(); PrecacheModel( "models/player/sniper.mdl" ); PrecacheModel( "models/weapons/c_models/c_bow/c_bow.mdl" ); } //----------------------------------------------------------------------------------------------------- void CBotNPCArcher::Spawn( void ) { BaseClass::Spawn(); SetModel( "models/player/sniper.mdl" ); m_bow = (CBaseAnimating *)CreateEntityByName( "prop_dynamic" ); if ( m_bow ) { m_bow->SetModel( "models/weapons/c_models/c_bow/c_bow.mdl" ); // bonemerge into our model m_bow->FollowEntity( this, true ); } int health = tf_bot_npc_archer_health.GetInt(); SetHealth( health ); SetMaxHealth( health ); ChangeTeam( TF_TEAM_RED ); Vector headPos; QAngle headAngles; if ( GetAttachment( "head", headPos, headAngles ) ) { m_eyeOffset = headPos - GetAbsOrigin(); } m_homePos = GetAbsOrigin(); } //--------------------------------------------------------------------------------------------- unsigned int CBotNPCArcher::PhysicsSolidMaskForEntity( void ) const { // Only collide with the other team int teamContents = ( GetTeamNumber() == TF_TEAM_RED ) ? CONTENTS_BLUETEAM : CONTENTS_REDTEAM; return BaseClass::PhysicsSolidMaskForEntity() | teamContents; } //--------------------------------------------------------------------------------------------- bool CBotNPCArcher::ShouldCollide( int collisionGroup, int contentsMask ) const { if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT ) { switch( GetTeamNumber() ) { case TF_TEAM_RED: if ( !( contentsMask & CONTENTS_REDTEAM ) ) return false; break; case TF_TEAM_BLUE: if ( !( contentsMask & CONTENTS_BLUETEAM ) ) return false; break; } } return BaseClass::ShouldCollide( collisionGroup, contentsMask ); } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- class CBotNPCArcherSurrender : public Action< CBotNPCArcher > { public: virtual ActionResult< CBotNPCArcher > OnStart( CBotNPCArcher *me, Action< CBotNPCArcher > *priorAction ); virtual const char *GetName( void ) const { return "Surrender"; } // return name of this action }; inline ActionResult< CBotNPCArcher > CBotNPCArcherSurrender::OnStart( CBotNPCArcher *me, Action< CBotNPCArcher > *priorAction ) { CBaseAnimating *bow = me->GetBow(); if ( bow ) { bow->AddEffects( EF_NODRAW ); } me->GetBodyInterface()->StartActivity( ACT_MP_STAND_LOSERSTATE ); return Continue(); } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- class CBotNPCArcherShootBow : public Action< CBotNPCArcher > { public: CBotNPCArcherShootBow( CTFPlayer *target ) { m_target = target; } virtual ActionResult< CBotNPCArcher > OnStart( CBotNPCArcher *me, Action< CBotNPCArcher > *priorAction ); virtual ActionResult< CBotNPCArcher > Update( CBotNPCArcher *me, float interval ); virtual const char *GetName( void ) const { return "ShootBow"; } // return name of this action private: CHandle< CTFPlayer > m_target; }; //--------------------------------------------------------------------------------------------- ActionResult< CBotNPCArcher > CBotNPCArcherShootBow::OnStart( CBotNPCArcher *me, Action< CBotNPCArcher > *priorAction ) { if ( !m_target ) { return Done( "No target" ); } me->GetLocomotionInterface()->FaceTowards( m_target->WorldSpaceCenter() ); me->AddGesture( ACT_MP_ATTACK_STAND_ITEM2 ); // fire arrow const float arrowSpeed = 2000.0f; const float arrowGravity = 0.2f; Vector muzzleOrigin; QAngle muzzleAngles; if ( me->GetBow()->GetAttachment( "muzzle", muzzleOrigin, muzzleAngles ) == false ) { return Done( "No muzzle attachment!" ); } // lead target float range = me->GetRangeTo( m_target->EyePosition() ); float flightTime = range / arrowSpeed; Vector aimSpot = m_target->EyePosition() + m_target->GetAbsVelocity() * flightTime; Vector to = aimSpot - muzzleOrigin; VectorAngles( to, muzzleAngles ); CTFProjectile_Arrow *arrow = CTFProjectile_Arrow::Create( muzzleOrigin, muzzleAngles, arrowSpeed, arrowGravity, TF_PROJECTILE_ARROW, me, me ); if ( arrow ) { arrow->SetLauncher( me ); arrow->SetCritical( false ); arrow->SetDamage( tf_bot_npc_archer_arrow_damage.GetFloat() ); me->EmitSound( "Weapon_CompoundBow.Single" ); } return Continue(); } //--------------------------------------------------------------------------------------------- ActionResult< CBotNPCArcher > CBotNPCArcherShootBow::Update( CBotNPCArcher *me, float interval ) { if ( me->IsSequenceFinished() ) { return Done(); } return Continue(); } //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- class CBotNPCArcherGuardSpot : public Action< CBotNPCArcher > { public: virtual ActionResult< CBotNPCArcher > OnStart( CBotNPCArcher *me, Action< CBotNPCArcher > *priorAction ) { me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM2 ); return Continue(); } CTFPlayer *GetVictim( CBotNPCArcher *me ) { CUtlVector< CTFPlayer * > playerVector; CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS ); CTFPlayer *closeVictim = NULL; float victimRangeSq = FLT_MAX; for( int i=0; iGetRangeSquaredTo( playerVector[i] ); if ( rangeSq < victimRangeSq ) { if ( playerVector[i]->m_Shared.IsStealthed() ) { continue; } if ( me->IsLineOfSightClear( playerVector[i] ) ) { closeVictim = playerVector[i]; victimRangeSq = rangeSq; } } } return closeVictim; } virtual ActionResult< CBotNPCArcher > Update( CBotNPCArcher *me, float interval ) { if ( TFGameRules()->GetActiveBoss() == NULL ) { // the Boss has been defeated - give up return ChangeTo( new CBotNPCArcherSurrender, "The Boss is dead! I give up!" ); } CTFPlayer *victim = GetVictim( me ); if ( victim ) { // look at visible victim out of range me->GetLocomotionInterface()->FaceTowards( victim->WorldSpaceCenter() ); if ( m_shootTimer.IsElapsed() ) { m_shootTimer.Start( tf_bot_npc_archer_shoot_interval.GetFloat() ); return SuspendFor( new CBotNPCArcherShootBow( victim ), "Fire!" ); } } if ( me->GetLocomotionInterface()->IsAttemptingToMove() ) { // play running animation if ( !me->GetBodyInterface()->IsActivity( ACT_MP_DEPLOYED_IDLE_ITEM2 ) ) { me->GetBodyInterface()->StartActivity( ACT_MP_DEPLOYED_IDLE_ITEM2 ); } } else { // standing still if ( !me->GetBodyInterface()->IsActivity( ACT_MP_STAND_ITEM2 ) ) { me->GetBodyInterface()->StartActivity( ACT_MP_STAND_ITEM2 ); } } return Continue(); } virtual const char *GetName( void ) const { return "GuardSpot"; } // return name of this action private: CountdownTimer m_shootTimer; }; //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- class CBotNPCArcherMoveToMark : public Action< CBotNPCArcher > { public: virtual ActionResult< CBotNPCArcher > OnStart( CBotNPCArcher *me, Action< CBotNPCArcher > *priorAction ) { ShortestPathCost cost; m_path.Compute( me, me->GetHomePosition(), cost ); me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM2 ); return Continue(); } virtual ActionResult< CBotNPCArcher > Update( CBotNPCArcher *me, float interval ) { m_path.Update( me ); if ( !m_path.IsValid() ) { return ChangeTo( new CBotNPCArcherGuardSpot, "Reached my mark" ); } return Continue(); } virtual const char *GetName( void ) const { return "MoveToMark"; } // return name of this action private: PathFollower m_path; }; //--------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------- class CBotNPCArcherBehavior : public Action< CBotNPCArcher > { public: virtual Action< CBotNPCArcher > *InitialContainedAction( CBotNPCArcher *me ) { return new CBotNPCArcherMoveToMark; } virtual ActionResult< CBotNPCArcher > Update( CBotNPCArcher *me, float interval ) { return Continue(); } virtual EventDesiredResult< CBotNPCArcher > OnKilled( CBotNPCArcher *me, const CTakeDamageInfo &info ) { // Calculate death force Vector forceVector = me->CalcDamageForceVector( info ); // See if there's a ragdoll magnet that should influence our force. CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( me ); if ( magnet ) { forceVector += magnet->GetForceVector( me ); } me->BecomeRagdoll( info, forceVector ); return TryDone(); } virtual const char *GetName( void ) const { return "Behavior"; } // return name of this action }; IMPLEMENT_INTENTION_INTERFACE( CBotNPCArcher, CBotNPCArcherBehavior );