You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
11 KiB
402 lines
11 KiB
//========= 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; i<playerVector.Count(); ++i ) |
|
{ |
|
float rangeSq = me->GetRangeSquaredTo( 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 );
|
|
|