summaryrefslogtreecommitdiff
path: root/game/server/NextBot/NextBot.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/NextBot/NextBot.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'game/server/NextBot/NextBot.cpp')
-rw-r--r--game/server/NextBot/NextBot.cpp523
1 files changed, 523 insertions, 0 deletions
diff --git a/game/server/NextBot/NextBot.cpp b/game/server/NextBot/NextBot.cpp
new file mode 100644
index 0000000..0165a03
--- /dev/null
+++ b/game/server/NextBot/NextBot.cpp
@@ -0,0 +1,523 @@
+// NextBotCombatCharacter.cpp
+// Next generation bot system
+// Author: Michael Booth, April 2005
+//========= Copyright Valve Corporation, All rights reserved. ============//
+
+#include "cbase.h"
+
+#include "team.h"
+#include "CRagdollMagnet.h"
+
+#include "NextBot.h"
+#include "NextBotLocomotionInterface.h"
+#include "NextBotBodyInterface.h"
+
+#ifdef TERROR
+#include "TerrorGamerules.h"
+#endif
+
+#include "vprof.h"
+#include "datacache/imdlcache.h"
+#include "EntityFlame.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+ConVar NextBotStop( "nb_stop", "0", FCVAR_CHEAT | FCVAR_REPLICATED, "Stop all NextBots" );
+
+
+//--------------------------------------------------------------------------------------------------------
+class CSendBotCommand
+{
+public:
+ CSendBotCommand( const char *command )
+ {
+ m_command = command;
+ }
+
+ bool operator() ( INextBot *bot )
+ {
+ bot->OnCommandString( m_command );
+ return true;
+ }
+
+ const char *m_command;
+};
+
+
+CON_COMMAND_F( nb_command, "Sends a command string to all bots", FCVAR_CHEAT )
+{
+ if ( args.ArgC() <= 1 )
+ {
+ Msg( "Missing command string" );
+ return;
+ }
+
+ CSendBotCommand sendCmd( args.ArgS() );
+ TheNextBots().ForEachBot( sendCmd );
+}
+
+
+
+//-----------------------------------------------------------------------------------------------------
+BEGIN_DATADESC( NextBotCombatCharacter )
+
+ DEFINE_THINKFUNC( DoThink ),
+
+END_DATADESC()
+
+
+//-----------------------------------------------------------------------------------------------------
+IMPLEMENT_SERVERCLASS_ST( NextBotCombatCharacter, DT_NextBot )
+END_SEND_TABLE()
+
+
+//-----------------------------------------------------------------------------------------------------
+NextBotDestroyer::NextBotDestroyer( int team )
+{
+ m_team = team;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+bool NextBotDestroyer::operator() ( INextBot *bot )
+{
+ if ( m_team == TEAM_ANY || bot->GetEntity()->GetTeamNumber() == m_team )
+ {
+ // players need to be kicked, not deleted
+ if ( bot->GetEntity()->IsPlayer() )
+ {
+ CBasePlayer *player = dynamic_cast< CBasePlayer * >( bot->GetEntity() );
+ engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", player->GetUserID() ) );
+ }
+ else
+ {
+ UTIL_Remove( bot->GetEntity() );
+ }
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+CON_COMMAND_F( nb_delete_all, "Delete all non-player NextBot entities.", FCVAR_CHEAT )
+{
+ // Listenserver host or rcon access only!
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ CTeam *team = NULL;
+
+ if ( args.ArgC() == 2 )
+ {
+ const char *teamName = args[1];
+
+ for( int i=0; i < g_Teams.Count(); ++i )
+ {
+ if ( FStrEq( teamName, g_Teams[i]->GetName() ) )
+ {
+ // delete all bots on this team
+ team = g_Teams[i];
+ break;
+ }
+ }
+
+ if ( team == NULL )
+ {
+ Msg( "Invalid team '%s'\n", teamName );
+ return;
+ }
+ }
+
+ // delete all bots on all teams
+ NextBotDestroyer destroyer( team ? team->GetTeamNumber() : TEAM_ANY );
+ TheNextBots().ForEachBot( destroyer );
+}
+
+
+//-----------------------------------------------------------------------------------------------------
+class NextBotApproacher
+{
+public:
+ NextBotApproacher( void )
+ {
+ CBasePlayer *player = UTIL_GetListenServerHost();
+ if ( player )
+ {
+ Vector forward;
+ player->EyeVectors( &forward );
+
+ trace_t result;
+ unsigned int mask = MASK_BLOCKLOS_AND_NPCS|CONTENTS_IGNORE_NODRAW_OPAQUE | CONTENTS_GRATE | CONTENTS_WINDOW;
+ UTIL_TraceLine( player->EyePosition(), player->EyePosition() + 999999.9f * forward, mask, player, COLLISION_GROUP_NONE, &result );
+ if ( result.DidHit() )
+ {
+ NDebugOverlay::Cross3D( result.endpos, 5, 0, 255, 0, true, 10.0f );
+ m_isGoalValid = true;
+ m_goal = result.endpos;
+ }
+ else
+ {
+ m_isGoalValid = false;
+ }
+ }
+ }
+
+ bool operator() ( INextBot *bot )
+ {
+ if ( TheNextBots().IsDebugFilterMatch( bot ) )
+ {
+ bot->OnCommandApproach( m_goal );
+ }
+ return true;
+ }
+
+ bool m_isGoalValid;
+ Vector m_goal;
+};
+
+CON_COMMAND_F( nb_move_to_cursor, "Tell all NextBots to move to the cursor position", FCVAR_CHEAT )
+{
+ // Listenserver host or rcon access only!
+ if ( !UTIL_IsCommandIssuedByServerAdmin() )
+ return;
+
+ NextBotApproacher approach;
+ TheNextBots().ForEachBot( approach );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------
+bool IgnoreActorsTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
+{
+ CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
+ return ( entity->MyCombatCharacterPointer() == NULL ); // includes all bots, npcs, players, and TF2 buildings
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------
+bool VisionTraceFilterFunction( IHandleEntity *pServerEntity, int contentsMask )
+{
+ // Honor BlockLOS also to allow seeing through partially-broken doors
+ CBaseEntity *entity = EntityFromEntityHandle( pServerEntity );
+ return ( entity->MyCombatCharacterPointer() == NULL && entity->BlocksLOS() );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+//----------------------------------------------------------------------------------------------------------
+
+NextBotCombatCharacter::NextBotCombatCharacter( void )
+
+{
+ m_lastAttacker = NULL;
+ m_didModelChange = false;
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::Spawn( void )
+{
+ BaseClass::Spawn();
+
+ // reset bot components
+ Reset();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+
+ SetMoveType( MOVETYPE_CUSTOM );
+
+ SetCollisionGroup( COLLISION_GROUP_PLAYER );
+
+ m_iMaxHealth = m_iHealth;
+ m_takedamage = DAMAGE_YES;
+
+ MDLCACHE_CRITICAL_SECTION();
+ InitBoneControllers( );
+
+ // set up think callback
+ SetThink( &NextBotCombatCharacter::DoThink );
+ SetNextThink( gpGlobals->curtime );
+
+ m_lastAttacker = NULL;
+}
+
+
+bool NextBotCombatCharacter::IsAreaTraversable( const CNavArea *area ) const
+{
+ if ( !area )
+ return false;
+ ILocomotion *mover = GetLocomotionInterface();
+ if ( mover && !mover->IsAreaTraversable( area ) )
+ return false;
+ return BaseClass::IsAreaTraversable( area );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::DoThink( void )
+{
+ VPROF_BUDGET( "NextBotCombatCharacter::DoThink", "NextBot" );
+
+ SetNextThink( gpGlobals->curtime );
+
+ if ( BeginUpdate() )
+ {
+ // emit model change event
+ if ( m_didModelChange )
+ {
+ m_didModelChange = false;
+
+ OnModelChanged();
+
+ // propagate model change into NextBot event responders
+ for ( INextBotEventResponder *sub = FirstContainedResponder(); sub; sub = NextContainedResponder( sub ) )
+ {
+ sub->OnModelChanged();
+ }
+ }
+
+ UpdateLastKnownArea();
+
+ // update bot components
+ if ( !NextBotStop.GetBool() && (GetFlags() & FL_FROZEN) == 0 )
+ {
+ Update();
+ }
+
+ EndUpdate();
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::Touch( CBaseEntity *other )
+{
+ if ( ShouldTouch( other ) )
+ {
+ // propagate touch into NextBot event responders
+ trace_t result;
+ result = GetTouchTrace();
+
+ // OnContact refers to *physical* contact, not triggers or other non-physical entities
+ if ( result.DidHit() || other->MyCombatCharacterPointer() != NULL )
+ {
+ OnContact( other, &result );
+ }
+ }
+
+ BaseClass::Touch( other );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::SetModel( const char *szModelName )
+{
+ // actually change the model
+ BaseClass::SetModel( szModelName );
+
+ // need to do a lazy-check because precache system also invokes this
+ m_didModelChange = true;
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
+{
+ BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
+
+ // propagate event to components
+ OnIgnite();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::Ignite( float flFlameLifetime, CBaseEntity *pAttacker )
+{
+ if ( IsOnFire() )
+ return;
+
+ // BaseClass::Ignite stuff, plus SetAttacker on the flame, so our attacker gets credit
+ CEntityFlame *pFlame = CEntityFlame::Create( this );
+ if ( pFlame )
+ {
+ pFlame->SetLifetime( flFlameLifetime );
+ AddFlag( FL_ONFIRE );
+
+ SetEffectEntity( pFlame );
+ }
+ m_OnIgnite.FireOutput( this, this );
+
+ // propagate event to components
+ OnIgnite();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+int NextBotCombatCharacter::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // track our last attacker
+ if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
+ {
+ m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
+ }
+
+ // propagate event to components
+ OnInjured( info );
+
+ return CBaseCombatCharacter::OnTakeDamage_Alive( info );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+int NextBotCombatCharacter::OnTakeDamage_Dying( const CTakeDamageInfo &info )
+{
+ // track our last attacker
+ if ( info.GetAttacker()->MyCombatCharacterPointer() )
+ {
+ m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
+ }
+
+ // propagate event to components
+ OnInjured( info );
+
+ return CBaseCombatCharacter::OnTakeDamage_Dying( info );
+}
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Can't use CBaseCombatCharacter's Event_Killed because it will immediately ragdoll us
+ */
+static int g_DeathStartEvent = 0;
+void NextBotCombatCharacter::Event_Killed( const CTakeDamageInfo &info )
+{
+ // track our last attacker
+ if ( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
+ {
+ m_lastAttacker = info.GetAttacker()->MyCombatCharacterPointer();
+ }
+
+ // propagate event to my components
+ OnKilled( info );
+
+ // Advance life state to dying
+ m_lifeState = LIFE_DYING;
+
+#ifdef TERROR
+ /*
+ * TODO: Make this game-generic
+ */
+ // Create the death event just like players do.
+ TerrorGameRules()->DeathNoticeForEntity( this, info );
+
+ // Infected specific event
+ TerrorGameRules()->DeathNoticeForInfected( this, info );
+#endif
+
+ if ( GetOwnerEntity() != NULL )
+ {
+ GetOwnerEntity()->DeathNotice( this );
+ }
+
+ // inform the other bots
+ TheNextBots().OnKilled( this, info );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::PerformCustomPhysics( Vector *pNewPosition, Vector *pNewVelocity, QAngle *pNewAngles, QAngle *pNewAngVelocity )
+{
+ ILocomotion *mover = GetLocomotionInterface();
+ if ( mover )
+ {
+ // hack to keep ground entity from being NULL'd when Z velocity is positive
+ SetGroundEntity( mover->GetGround() );
+ }
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+bool NextBotCombatCharacter::BecomeRagdoll( const CTakeDamageInfo &info, const Vector &forceVector )
+{
+ // See if there's a ragdoll magnet that should influence our force.
+ Vector adjustedForceVector = forceVector;
+ CRagdollMagnet *magnet = CRagdollMagnet::FindBestMagnet( this );
+ if ( magnet )
+ {
+ adjustedForceVector += magnet->GetForceVector( this );
+ }
+
+ // clear the deceased's sound channels.(may have been firing or reloading when killed)
+ EmitSound( "BaseCombatCharacter.StopWeaponSounds" );
+
+ return BaseClass::BecomeRagdoll( info, adjustedForceVector );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::HandleAnimEvent( animevent_t *event )
+{
+ // propagate event to components
+ OnAnimationEvent( event );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Propagate event into NextBot event responders
+ */
+void NextBotCombatCharacter::OnNavAreaChanged( CNavArea *enteredArea, CNavArea *leftArea )
+{
+ INextBotEventResponder::OnNavAreaChanged( enteredArea, leftArea );
+
+ BaseClass::OnNavAreaChanged( enteredArea, leftArea );
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+Vector NextBotCombatCharacter::EyePosition( void )
+{
+ if ( GetBodyInterface() )
+ {
+ return GetBodyInterface()->GetEyePosition();
+ }
+
+ return BaseClass::EyePosition();
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+/**
+ * Return true if this object can be +used by the bot
+ */
+bool NextBotCombatCharacter::IsUseableEntity( CBaseEntity *entity, unsigned int requiredCaps )
+{
+ if ( entity )
+ {
+ int caps = entity->ObjectCaps();
+ if ( caps & (FCAP_IMPULSE_USE|FCAP_CONTINUOUS_USE|FCAP_ONOFF_USE|FCAP_DIRECTIONAL_USE) )
+ {
+ if ( (caps & requiredCaps) == requiredCaps )
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//----------------------------------------------------------------------------------------------------------
+void NextBotCombatCharacter::UseEntity( CBaseEntity *entity, USE_TYPE useType )
+{
+ if ( IsUseableEntity( entity ) )
+ {
+ variant_t emptyVariant;
+ entity->AcceptInput( "Use", this, this, emptyVariant, useType );
+ }
+}