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.
470 lines
13 KiB
470 lines
13 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// tf_bot_generator.cpp |
|
// Entity to spawn a collection of TFBots |
|
// Michael Booth, September 2009 |
|
|
|
#include "cbase.h" |
|
|
|
#include "tf_bot_generator.h" |
|
|
|
#include "bot/tf_bot.h" |
|
#include "bot/tf_bot_manager.h" |
|
#include "tf_gamerules.h" |
|
#include "tier3/tier3.h" |
|
#include "vgui/ILocalize.h" |
|
|
|
extern ConVar tf_bot_prefix_name_with_difficulty; |
|
extern ConVar tf_bot_difficulty; |
|
|
|
extern void CreateBotName( int iTeam, int iClassIndex, CTFBot::DifficultyType skill, char* pBuffer, int iBufferSize ); |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
BEGIN_DATADESC( CTFBotGenerator ) |
|
DEFINE_KEYFIELD( m_spawnCount, FIELD_INTEGER, "count" ), |
|
DEFINE_KEYFIELD( m_maxActiveCount, FIELD_INTEGER, "maxActive" ), |
|
DEFINE_KEYFIELD( m_spawnInterval, FIELD_FLOAT, "interval" ), |
|
DEFINE_KEYFIELD( m_className, FIELD_STRING, "class" ), |
|
DEFINE_KEYFIELD( m_teamName, FIELD_STRING, "team" ), |
|
DEFINE_KEYFIELD( m_actionPointName, FIELD_STRING, "action_point" ), |
|
DEFINE_KEYFIELD( m_initialCommand, FIELD_STRING, "initial_command" ), |
|
DEFINE_KEYFIELD( m_bSuppressFire, FIELD_BOOLEAN, "suppressFire" ), |
|
DEFINE_KEYFIELD( m_bDisableDodge, FIELD_BOOLEAN, "disableDodge" ), |
|
DEFINE_KEYFIELD( m_iOnDeathAction, FIELD_INTEGER, "actionOnDeath" ), |
|
DEFINE_KEYFIELD( m_bUseTeamSpawnpoint, FIELD_BOOLEAN, "useTeamSpawnPoint" ), |
|
DEFINE_KEYFIELD( m_difficulty, FIELD_INTEGER, "difficulty" ), |
|
DEFINE_KEYFIELD( m_bRetainBuildings, FIELD_BOOLEAN, "retainBuildings" ), |
|
DEFINE_KEYFIELD( m_bSpawnOnlyWhenTriggered, FIELD_BOOLEAN, "spawnOnlyWhenTriggered" ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), |
|
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetSuppressFire", InputSetSuppressFire ), |
|
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetDisableDodge", InputSetDisableDodge ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDifficulty", InputSetDifficulty ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "CommandGotoActionPoint", InputCommandGotoActionPoint ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetAttentionFocus", InputSetAttentionFocus ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "ClearAttentionFocus", InputClearAttentionFocus ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "SpawnBot", InputSpawnBot ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "RemoveBots", InputRemoveBots ), |
|
|
|
DEFINE_OUTPUT( m_onSpawned, "OnSpawned" ), |
|
DEFINE_OUTPUT( m_onExpended, "OnExpended" ), |
|
DEFINE_OUTPUT( m_onBotKilled, "OnBotKilled" ), |
|
|
|
DEFINE_THINKFUNC( GeneratorThink ), |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( bot_generator, CTFBotGenerator ); |
|
|
|
enum |
|
{ |
|
kOnDeath_Respawn, |
|
kOnDeath_RemoveSelf, |
|
kOnDeath_MoveToSpectatorTeam, |
|
}; |
|
|
|
//------------------------------------------------------------------------------ |
|
CTFBotGenerator::CTFBotGenerator( void ) |
|
: m_bBotChoosesClass(false) |
|
, m_bSuppressFire(false) |
|
, m_bDisableDodge(false) |
|
, m_bUseTeamSpawnpoint(false) |
|
, m_bRetainBuildings(false) |
|
, m_bExpended(false) |
|
, m_iOnDeathAction(kOnDeath_RemoveSelf) |
|
, m_difficulty(CTFBot::UNDEFINED) |
|
, m_spawnCountRemaining(0) |
|
, m_bSpawnOnlyWhenTriggered(false) |
|
, m_bEnabled(true) |
|
{ |
|
SetThink( NULL ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
m_bEnabled = true; |
|
|
|
if ( m_bExpended ) |
|
{ |
|
return; |
|
} |
|
|
|
SetThink( &CTFBotGenerator::GeneratorThink ); |
|
|
|
if ( m_spawnCountRemaining ) |
|
{ |
|
// already generating - don't restart count |
|
return; |
|
} |
|
SetNextThink( gpGlobals->curtime ); |
|
m_spawnCountRemaining = m_spawnCount; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
m_bEnabled = false; |
|
|
|
// just stop thinking |
|
SetThink( NULL ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputSetSuppressFire( inputdata_t &inputdata ) |
|
{ |
|
m_bSuppressFire = inputdata.value.Bool(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputSetDisableDodge( inputdata_t &inputdata ) |
|
{ |
|
m_bDisableDodge = inputdata.value.Bool(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputSetDifficulty( inputdata_t &inputdata ) |
|
{ |
|
m_difficulty = clamp( inputdata.value.Int(), (int) CTFBot::UNDEFINED, (int) CTFBot::EXPERT ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputCommandGotoActionPoint( inputdata_t &inputdata ) |
|
{ |
|
CTFBotActionPoint *pActionPoint = dynamic_cast<CTFBotActionPoint *>( gEntList.FindEntityByName( NULL, inputdata.value.String() ) ); |
|
if ( pActionPoint == NULL ) |
|
{ |
|
return; |
|
} |
|
for ( int i = 0; i < m_spawnedBotVector.Count(); ) |
|
{ |
|
CHandle< CTFBot > hBot = m_spawnedBotVector[i]; |
|
if ( hBot == NULL ) |
|
{ |
|
m_spawnedBotVector.FastRemove(i); |
|
continue; |
|
} |
|
if ( hBot->GetTeamNumber() == TEAM_SPECTATOR ) |
|
{ |
|
m_spawnedBotVector.FastRemove(i); |
|
continue; |
|
} |
|
hBot->SetActionPoint( pActionPoint ); |
|
hBot->OnCommandString( "goto action point" ); |
|
++i; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputSetAttentionFocus( inputdata_t &inputdata ) |
|
{ |
|
CBaseEntity *focus = gEntList.FindEntityByName( NULL, inputdata.value.String() ); |
|
|
|
if ( focus == NULL ) |
|
{ |
|
return; |
|
} |
|
|
|
for( int i = 0; i < m_spawnedBotVector.Count(); ) |
|
{ |
|
CTFBot *bot = m_spawnedBotVector[i]; |
|
|
|
if ( !bot || bot->GetTeamNumber() == TEAM_SPECTATOR ) |
|
{ |
|
m_spawnedBotVector.FastRemove(i); |
|
continue; |
|
} |
|
|
|
bot->SetAttentionFocus( focus ); |
|
|
|
++i; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputClearAttentionFocus( inputdata_t &inputdata ) |
|
{ |
|
for( int i = 0; i < m_spawnedBotVector.Count(); ) |
|
{ |
|
CTFBot *bot = m_spawnedBotVector[i]; |
|
|
|
if ( !bot || bot->GetTeamNumber() == TEAM_SPECTATOR ) |
|
{ |
|
m_spawnedBotVector.FastRemove(i); |
|
continue; |
|
} |
|
|
|
bot->ClearAttentionFocus(); |
|
|
|
++i; |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputSpawnBot( inputdata_t &inputdata ) |
|
{ |
|
if ( m_bEnabled ) |
|
{ |
|
SpawnBot(); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::InputRemoveBots( inputdata_t &inputdata ) |
|
{ |
|
for( int i = 0; i < m_spawnedBotVector.Count(); i++ ) |
|
{ |
|
CTFBot *pBot = m_spawnedBotVector[i]; |
|
if ( pBot ) |
|
{ |
|
pBot->Remove(); |
|
engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pBot->GetUserID() ) ); |
|
} |
|
|
|
m_spawnedBotVector.FastRemove(i); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::OnBotKilled( CTFBot *pBot ) |
|
{ |
|
m_onBotKilled.FireOutput( pBot, this ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
void CTFBotGenerator::Activate() |
|
{ |
|
BaseClass::Activate(); |
|
m_bBotChoosesClass = FStrEq( m_className.ToCStr(), "auto" ); |
|
m_moveGoal = gEntList.FindEntityByName( NULL, m_actionPointName.ToCStr() ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::GeneratorThink( void ) |
|
{ |
|
// still waiting for the real game to start? |
|
gamerules_roundstate_t roundState = TFGameRules()->State_Get(); |
|
if ( roundState >= GR_STATE_TEAM_WIN || roundState < GR_STATE_PREROUND || TFGameRules()->IsInWaitingForPlayers() ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 1.0f ); |
|
return; |
|
} |
|
|
|
// create the bot finally... |
|
if ( !m_bSpawnOnlyWhenTriggered ) |
|
{ |
|
SpawnBot(); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
void CTFBotGenerator::SpawnBot( void ) |
|
{ |
|
// did we exceed the max active count? |
|
for ( int i = 0; i < m_spawnedBotVector.Count(); ) |
|
{ |
|
CHandle< CTFBot > hBot = m_spawnedBotVector[i]; |
|
if ( hBot == NULL ) |
|
{ |
|
m_spawnedBotVector.FastRemove(i); |
|
continue; |
|
} |
|
if ( hBot->GetTeamNumber() == TEAM_SPECTATOR ) |
|
{ |
|
m_spawnedBotVector.FastRemove(i); |
|
continue; |
|
} |
|
++i; |
|
} |
|
|
|
if ( m_spawnedBotVector.Count() >= m_maxActiveCount ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
return; |
|
} |
|
|
|
char name[256]; |
|
CTFBot *bot = TheTFBots().GetAvailableBotFromPool(); |
|
if ( bot == NULL ) |
|
{ |
|
CreateBotName( TEAM_UNASSIGNED, TF_CLASS_UNDEFINED, (CTFBot::DifficultyType)m_difficulty, name, sizeof(name) ); |
|
bot = NextBotCreatePlayerBot< CTFBot >( name ); |
|
} |
|
|
|
if ( bot ) |
|
{ |
|
m_spawnedBotVector.AddToTail( bot ); |
|
|
|
#ifdef TF_RAID_MODE |
|
if ( TFGameRules()->IsRaidMode() ) |
|
{ |
|
bot->SetAttribute( CTFBot::IS_NPC ); |
|
} |
|
#endif // TF_RAID_MODE |
|
|
|
bot->SetSpawner( this ); |
|
|
|
if ( m_bUseTeamSpawnpoint == false ) |
|
{ |
|
bot->SetSpawnPoint( this ); |
|
} |
|
|
|
if ( m_bSuppressFire ) |
|
{ |
|
bot->SetAttribute( CTFBot::SUPPRESS_FIRE ); |
|
} |
|
|
|
if ( m_bRetainBuildings ) |
|
{ |
|
bot->SetAttribute( CTFBot::RETAIN_BUILDINGS ); |
|
} |
|
|
|
if ( m_bDisableDodge ) |
|
{ |
|
bot->SetAttribute( CTFBot::DISABLE_DODGE ); |
|
} |
|
|
|
if ( m_difficulty != CTFBot::UNDEFINED ) |
|
{ |
|
bot->SetDifficulty( (CTFBot::DifficultyType )m_difficulty ); |
|
} |
|
|
|
// propagate the generator's spawn flags into all bots generated |
|
bot->ClearBehaviorFlag( TFBOT_ALL_BEHAVIOR_FLAGS ); |
|
bot->SetBehaviorFlag( m_spawnflags ); |
|
|
|
switch ( m_iOnDeathAction ) |
|
{ |
|
case kOnDeath_RemoveSelf: |
|
bot->SetAttribute( CTFBot::REMOVE_ON_DEATH ); |
|
break; |
|
case kOnDeath_MoveToSpectatorTeam: |
|
bot->SetAttribute( CTFBot::BECOME_SPECTATOR_ON_DEATH ); |
|
break; |
|
} // switch |
|
|
|
bot->SetActionPoint( dynamic_cast<CTFBotActionPoint *>( m_moveGoal.Get() ) ); |
|
|
|
// pick a team and force the team change |
|
// HandleCommand_JoinTeam() may fail, but this should always succeed |
|
int iTeam = TEAM_UNASSIGNED; |
|
if ( FStrEq( m_teamName.ToCStr(), "auto" ) ) |
|
{ |
|
iTeam = bot->GetAutoTeam(); |
|
} |
|
else if ( FStrEq( m_teamName.ToCStr(), "spectate" ) ) |
|
{ |
|
iTeam = TEAM_SPECTATOR; |
|
} |
|
else |
|
{ |
|
for ( int i = 0; i < TF_TEAM_COUNT; ++i ) |
|
{ |
|
COMPILE_TIME_ASSERT( TF_TEAM_COUNT == ARRAYSIZE( g_aTeamNames ) ); |
|
if ( FStrEq( m_teamName.ToCStr(), g_aTeamNames[i] ) ) |
|
{ |
|
iTeam = i; |
|
break; |
|
} |
|
} |
|
} |
|
if ( iTeam == TEAM_UNASSIGNED ) |
|
{ |
|
iTeam = bot->GetAutoTeam(); |
|
} |
|
bot->ChangeTeam( iTeam, false, false ); |
|
|
|
const char* pClassName = m_bBotChoosesClass ? bot->GetNextSpawnClassname() : m_className.ToCStr(); |
|
bot->HandleCommand_JoinClass( pClassName ); |
|
|
|
// in training, reset the after the bot joins the class |
|
if ( TFGameRules()->IsInTraining() ) |
|
{ |
|
CTFBot::DifficultyType skill = bot->GetDifficulty(); |
|
CreateBotName( iTeam, bot->GetPlayerClass()->GetClassIndex(), skill, name, sizeof(name) ); |
|
engine->SetFakeClientConVarValue( bot->edict(), "name", name ); |
|
} |
|
|
|
if ( bot->IsAlive() == false ) |
|
{ |
|
bot->ForceRespawn(); |
|
} |
|
|
|
// make sure the bot is facing the right way. |
|
// @todo Tom Bui: for some reason it is still turning towards another direction...need to investigate |
|
bot->SnapEyeAngles( GetAbsAngles() ); |
|
|
|
if ( FStrEq( m_initialCommand.ToCStr(), "" ) == false ) |
|
{ |
|
// @note Tom Bui: we call Update() once here to make sure the bot is ready to receive commands |
|
bot->Update(); |
|
bot->OnCommandString( m_initialCommand.ToCStr() ); |
|
} |
|
m_onSpawned.FireOutput( bot, this ); |
|
|
|
--m_spawnCountRemaining; |
|
if ( m_spawnCountRemaining ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + m_spawnInterval ); |
|
} |
|
else |
|
{ |
|
SetThink( NULL ); |
|
m_onExpended.FireOutput( this, this ); |
|
m_bExpended = true; |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
BEGIN_DATADESC( CTFBotActionPoint ) |
|
DEFINE_KEYFIELD( m_stayTime, FIELD_FLOAT, "stay_time" ), |
|
DEFINE_KEYFIELD( m_desiredDistance, FIELD_FLOAT, "desired_distance" ), |
|
DEFINE_KEYFIELD( m_nextActionPointName, FIELD_STRING, "next_action_point" ), |
|
DEFINE_KEYFIELD( m_command, FIELD_STRING, "command" ), |
|
DEFINE_OUTPUT( m_onReachedActionPoint, "OnBotReached" ), |
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( bot_action_point, CTFBotActionPoint ); |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
CTFBotActionPoint::CTFBotActionPoint() |
|
: m_stayTime( 0.0f ) |
|
, m_desiredDistance( 1.0f ) |
|
|
|
{ |
|
|
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
void CTFBotActionPoint::Activate() |
|
{ |
|
BaseClass::Activate(); |
|
m_moveGoal = gEntList.FindEntityByName( NULL, m_nextActionPointName.ToCStr() ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
bool CTFBotActionPoint::IsWithinRange( CBaseEntity *entity ) |
|
{ |
|
return ( entity->GetAbsOrigin() - GetAbsOrigin() ).IsLengthLessThan( m_desiredDistance ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
|
|
void CTFBotActionPoint::ReachedActionPoint( CTFBot* pBot ) |
|
{ |
|
if ( FStrEq( m_command.ToCStr(), "" ) == false ) |
|
{ |
|
pBot->OnCommandString( m_command.ToCStr() ); |
|
} |
|
m_onReachedActionPoint.FireOutput( pBot, this ); |
|
} |
|
|
|
//------------------------------------------------------------------------------
|
|
|