diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/NextBot/NextBot.cpp | |
| download | archived-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.cpp | 523 |
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 ); + } +} |