diff options
Diffstat (limited to 'game/server/tf/bot/map_entities')
20 files changed, 2064 insertions, 0 deletions
diff --git a/game/server/tf/bot/map_entities/tf_bot_generator.cpp b/game/server/tf/bot/map_entities/tf_bot_generator.cpp new file mode 100644 index 0000000..38fbc3b --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_generator.cpp @@ -0,0 +1,470 @@ +//========= 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 ); +} + +//------------------------------------------------------------------------------ diff --git a/game/server/tf/bot/map_entities/tf_bot_generator.h b/game/server/tf/bot/map_entities/tf_bot_generator.h new file mode 100644 index 0000000..dfbc52a --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_generator.h @@ -0,0 +1,100 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_generator.h +// Entity to spawn a collection of TFBots +// Michael Booth, September 2009 + +#ifndef TF_BOT_GENERATOR_H +#define TF_BOT_GENERATOR_H + +#include "bot/tf_bot.h" + + +class CTFBotGenerator : public CPointEntity +{ +public: + DECLARE_CLASS( CTFBotGenerator, CPointEntity ); + DECLARE_DATADESC(); + + CTFBotGenerator( void ); + virtual ~CTFBotGenerator() { } + + virtual void Activate(); + + void GeneratorThink( void ); + void SpawnBot( void ); + + // Input. + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputSetSuppressFire( inputdata_t &inputdata ); + void InputSetDisableDodge( inputdata_t &inputdata ); + void InputSetDifficulty( inputdata_t &inputdata ); + void InputCommandGotoActionPoint( inputdata_t &inputdata ); + void InputSetAttentionFocus( inputdata_t &inputdata ); + void InputClearAttentionFocus( inputdata_t &inputdata ); + void InputSpawnBot( inputdata_t &inputdata ); + void InputRemoveBots( inputdata_t &inputdata ); + + // Output + void OnBotKilled( CTFBot *pBot ); + +private: + bool m_bBotChoosesClass; + bool m_bSuppressFire; + bool m_bDisableDodge; + bool m_bUseTeamSpawnpoint; + bool m_bRetainBuildings; + bool m_bExpended; + int m_iOnDeathAction; + int m_spawnCount; + int m_spawnCountRemaining; + int m_maxActiveCount; + float m_spawnInterval; + string_t m_className; + string_t m_teamName; + string_t m_actionPointName; + string_t m_initialCommand; + CHandle< CBaseEntity > m_moveGoal; + int m_difficulty; + bool m_bSpawnOnlyWhenTriggered; + bool m_bEnabled; + + COutputEvent m_onSpawned; + COutputEvent m_onExpended; + COutputEvent m_onBotKilled; + + CUtlVector< CHandle< CTFBot > > m_spawnedBotVector; +}; + +//--------------------------------------------------------------- +// +// Bot generator may have one of these as an argument, which +// means "tell the bot I created to move here and do what this node says". +// Things like "stay here", "move to <next task point>", "face towards <X>", "shoot at <Y>", etc +// +class CTFBotActionPoint : public CPointEntity +{ + DECLARE_CLASS( CTFBotActionPoint, CPointEntity ); +public: + DECLARE_DATADESC(); + + CTFBotActionPoint( void ); + virtual ~CTFBotActionPoint() { } + + virtual void Activate(); + + bool IsWithinRange( CBaseEntity *entity ); + void ReachedActionPoint( CTFBot* pBot ); + + CHandle< CBaseEntity > m_moveGoal; + + // reflected + float m_stayTime; + float m_desiredDistance; + string_t m_nextActionPointName; + string_t m_command; + + COutputEvent m_onReachedActionPoint; +}; + +#endif // TF_BOT_GENERATOR_H diff --git a/game/server/tf/bot/map_entities/tf_bot_hint.cpp b/game/server/tf/bot/map_entities/tf_bot_hint.cpp new file mode 100644 index 0000000..b4dfa11 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint.cpp @@ -0,0 +1,128 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_hint.cpp +// Designer-placed hint for TFBots + +#include "cbase.h" +#include "bot/tf_bot.h" +#include "tf_bot_hint.h" + +BEGIN_DATADESC( CTFBotHint ) + DEFINE_KEYFIELD( m_team, FIELD_INTEGER, "team" ), + DEFINE_KEYFIELD( m_hint, FIELD_INTEGER, "hint" ), + DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_tfbot_hint, CTFBotHint ); + +// +// NOTE: For simplicity and runtime efficiency, this will not +// play nice with nav area hints stored in the mesh, +// nor will overlapping hints of the same type work well. +// + +//------------------------------------------------------------------------------ +CTFBotHint::CTFBotHint( void ) +{ + m_isDisabled = false; +} + + +//-------------------------------------------------------------------------------------------------------- +// Return true if this hint applies to the given entity +bool CTFBotHint::IsFor( CTFBot *who ) const +{ + if ( m_isDisabled ) + { + return false; + } + + if ( m_team > 0 && who->GetTeamNumber() != m_team ) + { + return false; + } + + return true; +} + + +//-------------------------------------------------------------------------------------------------------- +void CTFBotHint::Spawn( void ) +{ + BaseClass::Spawn(); + + SetSolid( SOLID_BSP ); + AddSolidFlags( FSOLID_NOT_SOLID ); + + SetMoveType( MOVETYPE_NONE ); + SetModel( STRING( GetModelName() ) ); + AddEffects( EF_NODRAW ); + SetCollisionGroup( COLLISION_GROUP_NONE ); + + VPhysicsInitShadow( false, false ); + + UpdateNavDecoration(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTFBotHint::UpdateOnRemove( void ) +{ + BaseClass::UpdateOnRemove(); + + UpdateNavDecoration(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTFBotHint::InputEnable( inputdata_t &inputdata ) +{ + m_isDisabled = false; + UpdateNavDecoration(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTFBotHint::InputDisable( inputdata_t &inputdata ) +{ + m_isDisabled = true; + UpdateNavDecoration(); +} + + +//-------------------------------------------------------------------------------------------------------- +void CTFBotHint::UpdateNavDecoration( void ) +{ + Extent extent; + extent.Init( this ); + + CUtlVector< CTFNavArea * > overlapVector; + TheNavMesh->CollectAreasOverlappingExtent( extent, &overlapVector ); + + int attributeBits = 0; + switch( m_hint ) + { + case HINT_SNIPER_SPOT: + attributeBits = TF_NAV_SNIPER_SPOT; + break; + + case HINT_SENTRY_SPOT: + attributeBits = TF_NAV_SENTRY_SPOT; + break; + } + + for( int j=0; j<overlapVector.Count(); ++j ) + { + if ( m_isDisabled ) + { + overlapVector[j]->ClearAttributeTF( attributeBits ); + } + else + { + overlapVector[j]->SetAttributeTF( attributeBits ); + } + } +} + + diff --git a/game/server/tf/bot/map_entities/tf_bot_hint.h b/game/server/tf/bot/map_entities/tf_bot_hint.h new file mode 100644 index 0000000..4945145 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_hint.h +// Designer-placed hint for TFBots + +#ifndef TF_BOT_HINT_H +#define TF_BOT_HINT_H + +class CTFBot; + +//----------------------------------------------------------------------------------------------------- +/** + * An entity that specifies TFBot behavior hints. + */ +class CTFBotHint : public CBaseEntity +{ +public: + DECLARE_DATADESC(); + DECLARE_CLASS( CTFBotHint, CBaseEntity ); + + CTFBotHint( void ); + virtual ~CTFBotHint() { } + + enum HintType + { + HINT_SNIPER_SPOT = 0, + HINT_SENTRY_SPOT = 1, + }; + + bool IsA( HintType type ) const; + + bool IsFor( CTFBot *who ) const; // return true if this hint applies to the given entity + + virtual void Spawn( void ); + virtual void UpdateOnRemove( void ); + + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + bool IsEnabled( void ) const { return !m_isDisabled; } + +protected: + int m_team; + int m_hint; + bool m_isDisabled; + + void UpdateNavDecoration( void ); +}; + +inline bool CTFBotHint::IsA( HintType type ) const +{ + return ( m_hint == type ); +} + + +#endif // TF_BOT_HINT_H diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_engineer_nest.cpp b/game/server/tf/bot/map_entities/tf_bot_hint_engineer_nest.cpp new file mode 100644 index 0000000..7f21a71 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_engineer_nest.cpp @@ -0,0 +1,160 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" +#include "tf_bot_hint_engineer_nest.h" +#include "tf_obj.h" +#include "tf_obj_teleporter.h" + +IMPLEMENT_SERVERCLASS_ST( CTFBotHintEngineerNest, DT_TFBotHintEngineerNest ) + SendPropBool( SENDINFO(m_bHasActiveTeleporter) ), +END_SEND_TABLE() + +BEGIN_DATADESC( CTFBotHintEngineerNest ) +END_DATADESC() + +LINK_ENTITY_TO_CLASS( bot_hint_engineer_nest, CTFBotHintEngineerNest ); + +//------------------------------------------------------------------------------ +CTFBotHintEngineerNest::CTFBotHintEngineerNest( void ) +{ + m_bHasActiveTeleporter = false; +} + + +void CTFBotHintEngineerNest::Spawn() +{ + BaseClass::Spawn(); + + SetThink( &CTFBotHintEngineerNest::HintThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + + +void CTFBotHintEngineerNest::HintThink() +{ + // find sentry and teleporter hint + for ( int i=0; i<ITFBotHintEntityAutoList::AutoList().Count(); ++i ) + { + CBaseTFBotHintEntity *pHint = static_cast< CBaseTFBotHintEntity* >( ITFBotHintEntityAutoList::AutoList()[i] ); + if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_SENTRYGUN ) && pHint->GetEntityName() == GetEntityName() ) + { + m_sentries.AddToTail( pHint ); + } + else if ( pHint->IsHintType( CBaseTFBotHintEntity::HINT_TELEPORTER_EXIT ) && pHint->GetEntityName() == GetEntityName() ) + { + m_teleporters.AddToTail( pHint ); + } + } + + if ( m_sentries.Count() == 0 && m_teleporters.Count() == 0 ) + { + AssertMsg( 0, "Must have a teleporter and/or a sentry hint with the same name." ); + Warning( "Must have a teleporter and/or a sentry hint with the same name.\n" ); + } + + SetThink( &CTFBotHintEngineerNest::HintTeleporterThink ); + SetNextThink( gpGlobals->curtime + 0.1f ); +} + + +void CTFBotHintEngineerNest::HintTeleporterThink() +{ + bool bFoundActiveTeleporter = false; + for ( int i=0; i<m_teleporters.Count(); ++i ) + { + CBaseEntity* pOwner = m_teleporters[i]->GetOwnerEntity(); + if ( pOwner && pOwner->IsBaseObject() ) + { + CObjectTeleporter *pTeleporter = assert_cast< CObjectTeleporter* >( pOwner ); + if ( pTeleporter ) + { + bFoundActiveTeleporter |= !pTeleporter->IsBuilding(); + } + } + } + + // update particle bool + m_bHasActiveTeleporter = bFoundActiveTeleporter; + + SetNextThink( gpGlobals->curtime + 0.1f ); +} + + +bool CTFBotHintEngineerNest::IsStaleNest() const +{ + for ( int i=0; i<m_sentries.Count(); ++i ) + { + if ( m_sentries[i]->OwnerObjectHasNoOwner() ) + { + return true; + } + } + + for ( int i=0; i<m_teleporters.Count(); ++i ) + { + if ( m_teleporters[i]->OwnerObjectHasNoOwner() ) + { + return true; + } + } + + return false; +} + + +void CTFBotHintEngineerNest::DetonateStaleNest() +{ + DetonateObjectsFromHints( m_sentries ); + DetonateObjectsFromHints( m_teleporters ); +} + + +void CTFBotHintEngineerNest::DetonateObjectsFromHints( const HintVector_t& hints ) +{ + for ( int i=0; i<hints.Count(); ++i ) + { + if ( hints[i]->OwnerObjectHasNoOwner() ) + { + CBaseObject* pObj = assert_cast< CBaseObject* >( hints[i]->GetOwnerEntity() ); + if ( pObj ) + { + pObj->DetonateObject(); + } + } + } +} + + +CBaseTFBotHintEntity* CTFBotHintEngineerNest::GetHint( const HintVector_t& hints ) const +{ + if ( hints.Count() == 0 ) + { + return NULL; + } + + for ( int i=0; i<hints.Count(); ++i ) + { + if ( hints[i]->OwnerObjectHasNoOwner() ) + { + return hints[i]; + } + } + + int which = RandomInt( 0, hints.Count() - 1 ); + return hints[ which ]; +} + + +CTFBotHintSentrygun* CTFBotHintEngineerNest::GetSentryHint() const +{ + return (CTFBotHintSentrygun*)GetHint( m_sentries ); +} + + +CTFBotHintTeleporterExit* CTFBotHintEngineerNest::GetTeleporterHint() const +{ + return (CTFBotHintTeleporterExit*)GetHint( m_teleporters ); +} diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_engineer_nest.h b/game/server/tf/bot/map_entities/tf_bot_hint_engineer_nest.h new file mode 100644 index 0000000..9e5fdf5 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_engineer_nest.h @@ -0,0 +1,53 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef TF_BOT_HINT_ENGINEER_NEST_H +#define TF_BOT_HINT_ENGINEER_NEST_H + +#include "tf_bot_hint_entity.h" + +typedef CUtlVector< CHandle< CBaseTFBotHintEntity > > HintVector_t; + +class CTFBotHintSentrygun; +class CTFBotHintTeleporterExit; + +class CTFBotHintEngineerNest : public CBaseTFBotHintEntity +{ + DECLARE_CLASS( CTFBotHintEngineerNest, CBaseTFBotHintEntity ); +public: + DECLARE_SERVERCLASS(); + DECLARE_DATADESC(); + + CTFBotHintEngineerNest( void ); + virtual ~CTFBotHintEngineerNest() { } + + virtual void Spawn() OVERRIDE; + + virtual HintType GetHintType() const OVERRIDE { return HINT_ENGINEER_NEST; } + + virtual int UpdateTransmitState() + { + return SetTransmitState( FL_EDICT_ALWAYS ); + } + + void HintThink(); + void HintTeleporterThink(); + + bool IsStaleNest() const; + void DetonateStaleNest(); + + CTFBotHintSentrygun* GetSentryHint() const; + CTFBotHintTeleporterExit* GetTeleporterHint() const; +private: + void DetonateObjectsFromHints( const HintVector_t& hints ); + CBaseTFBotHintEntity* GetHint( const HintVector_t& hints ) const; + + HintVector_t m_sentries; + HintVector_t m_teleporters; + + CNetworkVar( bool, m_bHasActiveTeleporter ); +}; + +#endif // TF_BOT_HINT_ENGINEER_NEST_H diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_entity.cpp b/game/server/tf/bot/map_entities/tf_bot_hint_entity.cpp new file mode 100644 index 0000000..ffd2c74 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_entity.cpp @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" +#include "tf_bot_hint_entity.h" +#include "tf_obj.h" +#include "tf_player.h" + + +BEGIN_DATADESC( CBaseTFBotHintEntity ) + DEFINE_KEYFIELD( m_isDisabled, FIELD_BOOLEAN, "StartDisabled" ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +END_DATADESC() + +IMPLEMENT_AUTO_LIST( ITFBotHintEntityAutoList ); + +//------------------------------------------------------------------------------ +CBaseTFBotHintEntity::CBaseTFBotHintEntity( void ) + : m_isDisabled( false ), + m_hintType( HINT_INVALID ) +{ +} + + +bool CBaseTFBotHintEntity::OwnerObjectHasNoOwner() const +{ + CBaseEntity* pOwner = GetOwnerEntity(); + if ( pOwner && pOwner->IsBaseObject() ) + { + CBaseObject *pObj = static_cast< CBaseObject* >( pOwner ); + if ( pObj->GetBuilder() == NULL ) + { + return true; + } + else + { + if ( !pObj->GetBuilder()->IsPlayerClass( TF_CLASS_ENGINEER ) ) + { + AssertMsg( 0, "Object has an owner that's not engineer." ); + Warning( "Object has an owner that's not engineer." ); + } + } + } + return false; +} + + +bool CBaseTFBotHintEntity::OwnerObjectFinishBuilding() const +{ + CBaseEntity* pOwner = GetOwnerEntity(); + if ( pOwner && pOwner->IsBaseObject() ) + { + CBaseObject *pObj = static_cast< CBaseObject* >( pOwner ); + return !pObj->IsBuilding(); + } + return false; +} diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_entity.h b/game/server/tf/bot/map_entities/tf_bot_hint_entity.h new file mode 100644 index 0000000..af0df70 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_entity.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef TF_BOT_HINT_ENTITY_H +#define TF_BOT_HINT_ENTITY_H + +DECLARE_AUTO_LIST( ITFBotHintEntityAutoList ); + +class CBaseTFBotHintEntity : public CPointEntity, public ITFBotHintEntityAutoList +{ + DECLARE_CLASS( CBaseTFBotHintEntity, CPointEntity ); +public: + DECLARE_DATADESC(); + + CBaseTFBotHintEntity( void ); + virtual ~CBaseTFBotHintEntity() { } + + enum HintType + { + HINT_INVALID = -1, + HINT_TELEPORTER_EXIT, + HINT_SENTRYGUN, + HINT_ENGINEER_NEST, + }; + virtual HintType GetHintType() const = 0; + bool IsHintType( HintType hintType ) { return GetHintType() == hintType; } + + bool OwnerObjectHasNoOwner() const; + bool OwnerObjectFinishBuilding() const; + + bool IsEnabled() const; + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + +private: + + bool m_isDisabled; + HintType m_hintType; +}; + + +inline void CBaseTFBotHintEntity::InputEnable( inputdata_t &inputdata ) +{ + m_isDisabled = false; +} + +inline void CBaseTFBotHintEntity::InputDisable( inputdata_t &inputdata ) +{ + m_isDisabled = true; +} + +inline bool CBaseTFBotHintEntity::IsEnabled() const +{ + return !m_isDisabled; +} + +#endif // TF_BOT_HINT_ENTITY_H diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_sentrygun.cpp b/game/server/tf/bot/map_entities/tf_bot_hint_sentrygun.cpp new file mode 100644 index 0000000..4cc804d --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_sentrygun.cpp @@ -0,0 +1,42 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_hint_sentrygun.cpp +// Designer-placed hint for bot sentry placement +// Michael Booth, October 2009 + +#include "cbase.h" +#include "bot/tf_bot.h" +#include "tf_bot_hint_sentrygun.h" + + +BEGIN_DATADESC( CTFBotHintSentrygun ) + DEFINE_KEYFIELD( m_isSticky, FIELD_BOOLEAN, "sticky" ), + DEFINE_OUTPUT( m_outputOnSentryGunDestroyed, "OnSentryGunDestroyed" ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( bot_hint_sentrygun, CTFBotHintSentrygun ); + +//------------------------------------------------------------------------------ +CTFBotHintSentrygun::CTFBotHintSentrygun( void ) + : m_isSticky( false ) + , m_iUseCount( 0 ) +{ +} + +//------------------------------------------------------------------------------ +void CTFBotHintSentrygun::OnSentryGunDestroyed( CBaseEntity *pEntity ) +{ + m_outputOnSentryGunDestroyed.FireOutput( pEntity, pEntity ); +} + +//------------------------------------------------------------------------------ +bool CTFBotHintSentrygun::IsAvailableForSelection( CTFPlayer *pRequestingPlayer ) const +{ + // sentry hint is eligible as long as there is no owner (or the owner is no longer an engineer) + // if the hint is enabled and the hint is not in use and it is on the same team as me + if ( ( GetPlayerOwner() == NULL || !GetPlayerOwner()->IsPlayerClass( TF_CLASS_ENGINEER ) ) && + ( IsEnabled() && IsInUse() == false && InSameTeam( pRequestingPlayer ) ) ) + { + return true; + } + return false; +} diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_sentrygun.h b/game/server/tf/bot/map_entities/tf_bot_hint_sentrygun.h new file mode 100644 index 0000000..e2b0440 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_sentrygun.h @@ -0,0 +1,75 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_hint_sentrygun.h +// Designer-placed hint for bot sentry placement +// Michael Booth, October 2009 + +#ifndef TF_BOT_HINT_SENTRYGUN_H +#define TF_BOT_HINT_SENTRYGUN_H + +#include "tf_bot_hint_entity.h" + +class CTFPlayer; + +class CTFBotHintSentrygun : public CBaseTFBotHintEntity +{ +public: + DECLARE_CLASS( CTFBotHintSentrygun, CBaseTFBotHintEntity ); + DECLARE_DATADESC(); + + CTFBotHintSentrygun( void ); + virtual ~CTFBotHintSentrygun() { } + + bool IsSticky() const; + bool IsInUse() const; + + CTFPlayer *GetPlayerOwner() const; + void SetPlayerOwner( CTFPlayer *pPlayerOwner ); + + void IncrementUseCount(); + void DecrementUseCount(); + + void OnSentryGunDestroyed( CBaseEntity *pBaseEntity ); + + bool IsAvailableForSelection( CTFPlayer *pRequestingPlayer ) const; + + virtual HintType GetHintType() const OVERRIDE { return HINT_SENTRYGUN; } + +private: + bool m_isSticky; + int m_iUseCount; + COutputEvent m_outputOnSentryGunDestroyed; + + CHandle< CTFPlayer > m_playerOwner; +}; + +inline bool CTFBotHintSentrygun::IsSticky() const +{ + return m_isSticky; +} + +inline bool CTFBotHintSentrygun::IsInUse() const +{ + return m_iUseCount != 0; +} + +inline CTFPlayer *CTFBotHintSentrygun::GetPlayerOwner() const +{ + return m_playerOwner; +} + +inline void CTFBotHintSentrygun::SetPlayerOwner( CTFPlayer *pPlayerOwner ) +{ + m_playerOwner = pPlayerOwner; +} + +inline void CTFBotHintSentrygun::IncrementUseCount() +{ + ++m_iUseCount; +} + +inline void CTFBotHintSentrygun::DecrementUseCount() +{ + --m_iUseCount; +} + +#endif // TF_BOT_HINT_SENTRYGUN_H diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_teleporter_exit.cpp b/game/server/tf/bot/map_entities/tf_bot_hint_teleporter_exit.cpp new file mode 100644 index 0000000..32beca2 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_teleporter_exit.cpp @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_hint_teleporter_exit.cpp +// Designer-placed hint for bot teleporter exit placement +// Michael Booth, May 2010 + +#include "cbase.h" +#include "tf_bot_hint_teleporter_exit.h" + + +BEGIN_DATADESC( CTFBotHintTeleporterExit ) +END_DATADESC() + +LINK_ENTITY_TO_CLASS( bot_hint_teleporter_exit, CTFBotHintTeleporterExit ); + +//------------------------------------------------------------------------------ +CTFBotHintTeleporterExit::CTFBotHintTeleporterExit( void ) +{ +} + diff --git a/game/server/tf/bot/map_entities/tf_bot_hint_teleporter_exit.h b/game/server/tf/bot/map_entities/tf_bot_hint_teleporter_exit.h new file mode 100644 index 0000000..a1af04a --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_hint_teleporter_exit.h @@ -0,0 +1,23 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_hint_teleporter_exit.h +// Designer-placed hint for bot teleporter exit placement +// Michael Booth, May 2010 + +#ifndef TF_BOT_HINT_TELEPORTER_EXIT_H +#define TF_BOT_HINT_TELEPORTER_EXIT_H + +#include "tf_bot_hint_entity.h" + +class CTFBotHintTeleporterExit : public CBaseTFBotHintEntity +{ + DECLARE_CLASS( CTFBotHintTeleporterExit, CBaseTFBotHintEntity ); +public: + DECLARE_DATADESC(); + + CTFBotHintTeleporterExit( void ); + virtual ~CTFBotHintTeleporterExit() { } + + virtual HintType GetHintType() const OVERRIDE { return HINT_TELEPORTER_EXIT; } +}; + +#endif // TF_BOT_HINT_TELEPORTER_EXIT_H diff --git a/game/server/tf/bot/map_entities/tf_bot_proxy.cpp b/game/server/tf/bot/map_entities/tf_bot_proxy.cpp new file mode 100644 index 0000000..560b341 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_proxy.cpp @@ -0,0 +1,167 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_proxy.cpp +// A Hammer entity that spawns a TFBot and relays events to/from it +// Michael Booth, November 2009 + +#include "cbase.h" + +#include "bot/tf_bot.h" +#include "tf_bot_proxy.h" +#include "tf_bot_generator.h" + + +BEGIN_DATADESC( CTFBotProxy ) + DEFINE_KEYFIELD( m_botName, FIELD_STRING, "bot_name" ), + DEFINE_KEYFIELD( m_className, FIELD_STRING, "class" ), + DEFINE_KEYFIELD( m_teamName, FIELD_STRING, "team" ), + DEFINE_KEYFIELD( m_respawnInterval, FIELD_FLOAT, "respawn_interval" ), + DEFINE_KEYFIELD( m_actionPointName, FIELD_STRING, "action_point" ), + DEFINE_KEYFIELD( m_spawnOnStart, FIELD_STRING, "spawn_on_start" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetTeam", InputSetTeam ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetClass", InputSetClass ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetMovementGoal", InputSetMovementGoal ), + DEFINE_INPUTFUNC( FIELD_VOID, "Spawn", InputSpawn ), + DEFINE_INPUTFUNC( FIELD_VOID, "Delete", InputDelete ), + + DEFINE_OUTPUT( m_onSpawned, "OnSpawned" ), + DEFINE_OUTPUT( m_onInjured, "OnInjured" ), + DEFINE_OUTPUT( m_onKilled, "OnKilled" ), + DEFINE_OUTPUT( m_onAttackingEnemy, "OnAttackingEnemy" ), + DEFINE_OUTPUT( m_onKilledEnemy, "OnKilledEnemy" ), + + DEFINE_THINKFUNC( Think ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( bot_proxy, CTFBotProxy ); + + + +//------------------------------------------------------------------------------ +CTFBotProxy::CTFBotProxy( void ) +{ + V_strcpy_safe( m_botName, "TFBot" ); + V_strcpy_safe( m_teamName, "auto" ); + V_strcpy_safe( m_className, "auto" ); + m_bot = NULL; + m_moveGoal = NULL; + SetThink( NULL ); +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::Think( void ) +{ + +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::InputSetTeam( inputdata_t &inputdata ) +{ + const char *teamName = inputdata.value.String(); + if ( teamName && teamName[0] ) + { + V_strcpy_safe( m_teamName, teamName ); + + // if m_bot exists, tell it to change team + if ( m_bot != NULL ) + { + m_bot->HandleCommand_JoinTeam( m_teamName ); + } + } +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::InputSetClass( inputdata_t &inputdata ) +{ + const char *className = inputdata.value.String(); + if ( className && className[0] ) + { + V_strcpy_safe( m_className, className ); + + // if m_bot exists, tell it to change class + if ( m_bot != NULL ) + { + m_bot->HandleCommand_JoinClass( m_className ); + } + } +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::InputSetMovementGoal( inputdata_t &inputdata ) +{ + const char *entityName = inputdata.value.String(); + if ( entityName && entityName[0] ) + { + m_moveGoal = dynamic_cast< CTFBotActionPoint * >( gEntList.FindEntityByName( NULL, entityName ) ); + + // if m_bot exists, tell it to move to the new action point + if ( m_bot != NULL ) + { + m_bot->SetActionPoint( (CTFBotActionPoint *)m_moveGoal.Get() ); + } + } +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::InputSpawn( inputdata_t &inputdata ) +{ + m_bot = NextBotCreatePlayerBot< CTFBot >( m_botName ); + if ( m_bot != NULL ) + { + m_bot->SetSpawnPoint( this ); + m_bot->SetAttribute( CTFBot::REMOVE_ON_DEATH ); + m_bot->SetAttribute( CTFBot::IS_NPC ); + + m_bot->SetActionPoint( (CTFBotActionPoint *)m_moveGoal.Get() ); + + m_bot->HandleCommand_JoinTeam( m_teamName ); + m_bot->HandleCommand_JoinClass( m_className ); + + m_onSpawned.FireOutput( m_bot, m_bot ); + } +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::InputDelete( inputdata_t &inputdata ) +{ + if ( m_bot != NULL ) + { + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", m_bot->GetUserID() ) ); + m_bot = NULL; + } +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::OnInjured( void ) +{ + m_onInjured.FireOutput( this, this ); +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::OnKilled( void ) +{ + m_onKilled.FireOutput( this, this ); +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::OnAttackingEnemy( void ) +{ + m_onAttackingEnemy.FireOutput( this, this ); +} + + +//------------------------------------------------------------------------------ +void CTFBotProxy::OnKilledEnemy( void ) +{ + m_onKilledEnemy.FireOutput( this, this ); +} + diff --git a/game/server/tf/bot/map_entities/tf_bot_proxy.h b/game/server/tf/bot/map_entities/tf_bot_proxy.h new file mode 100644 index 0000000..6cdfbf4 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_proxy.h @@ -0,0 +1,58 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_proxy.h +// A Hammer entity that spawns a TFBot and relays events to/from it +// Michael Booth, November 2009 + +#ifndef TF_BOT_PROXY_H +#define TF_BOT_PROXY_H + + +class CTFBot; +class CTFBotActionPoint; + + +class CTFBotProxy : public CPointEntity +{ + DECLARE_CLASS( CTFBotProxy, CPointEntity ); +public: + DECLARE_DATADESC(); + + CTFBotProxy( void ); + virtual ~CTFBotProxy() { } + + void Think( void ); + + // Input + void InputSetTeam( inputdata_t &inputdata ); + void InputSetClass( inputdata_t &inputdata ); + void InputSetMovementGoal( inputdata_t &inputdata ); + void InputSpawn( inputdata_t &inputdata ); + void InputDelete( inputdata_t &inputdata ); + + void OnInjured( void ); + void OnKilled( void ); + void OnAttackingEnemy( void ); + void OnKilledEnemy( void ); + +protected: + // Output + COutputEvent m_onSpawned; + COutputEvent m_onInjured; + COutputEvent m_onKilled; + COutputEvent m_onAttackingEnemy; + COutputEvent m_onKilledEnemy; + + char m_botName[64]; + char m_className[64]; + char m_teamName[64]; + + string_t m_spawnOnStart; + string_t m_actionPointName; + float m_respawnInterval; + + CHandle< CTFBot > m_bot; + CHandle< CTFBotActionPoint > m_moveGoal; +}; + + +#endif // TF_BOT_PROXY_H diff --git a/game/server/tf/bot/map_entities/tf_bot_roster.cpp b/game/server/tf/bot/map_entities/tf_bot_roster.cpp new file mode 100644 index 0000000..d07fc23 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_roster.cpp @@ -0,0 +1,107 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_roster.cpp +// entity that dictates what classes a bot can choose when spawning +// Tom Bui, April 2010 + +#include "cbase.h" + +#include "tf_shareddefs.h" +#include "bot/map_entities/tf_bot_roster.h" + +//------------------------------------------------------------------------------ + +BEGIN_DATADESC( CTFBotRoster ) + DEFINE_KEYFIELD( m_teamName, FIELD_STRING, "team" ), + DEFINE_KEYFIELD( m_bAllowClassChanges, FIELD_BOOLEAN, "allowClassChanges" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_SCOUT], FIELD_BOOLEAN, "allowScout" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_SNIPER], FIELD_BOOLEAN, "allowSniper" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_SOLDIER], FIELD_BOOLEAN, "allowSoldier" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_DEMOMAN], FIELD_BOOLEAN, "allowDemoman" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_MEDIC], FIELD_BOOLEAN, "allowMedic" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_HEAVYWEAPONS], FIELD_BOOLEAN, "allowHeavy" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_PYRO], FIELD_BOOLEAN, "allowPyro" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_SPY], FIELD_BOOLEAN, "allowSpy" ), + DEFINE_KEYFIELD( m_bAllowedClasses[TF_CLASS_ENGINEER], FIELD_BOOLEAN, "allowEngineer" ), + + DEFINE_INPUTFUNC( FIELD_STRING, "SetTeam", InputSetTeam ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowScout", InputSetAllowScout ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowSniper", InputSetAllowSniper ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowSoldier", InputSetAllowSoldier ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowDemoman", InputSetAllowDemoman ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowMedic", InputSetAllowMedic ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowHeavy", InputSetAllowHeavy ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowPyro", InputSetAllowPyro ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowSpy", InputSetAllowSpy ), + DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetAllowEngineer", InputSetAllowEngineer ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( bot_roster, CTFBotRoster ); + +//------------------------------------------------------------------------------ + +CTFBotRoster::CTFBotRoster() +{ + memset( m_bAllowedClasses, 0, sizeof( m_bAllowedClasses ) ); +} + +//------------------------------------------------------------------------------ + +void CTFBotRoster::InputSetAllowScout( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_SCOUT] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowSniper( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_SNIPER] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowSoldier( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_SOLDIER] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowDemoman( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_DEMOMAN] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowMedic( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_MEDIC] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowHeavy( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_HEAVYWEAPONS] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowPyro( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_PYRO] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowSpy( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_SPY] = inputdata.value.Bool(); +} + +void CTFBotRoster::InputSetAllowEngineer( inputdata_t &inputdata ) +{ + m_bAllowedClasses[TF_CLASS_ENGINEER] = inputdata.value.Bool(); +} + +//------------------------------------------------------------------------------ + +bool CTFBotRoster::IsClassAllowed( int iBotClass ) const +{ + return iBotClass > TF_CLASS_UNDEFINED && iBotClass < TF_LAST_NORMAL_CLASS && m_bAllowedClasses[iBotClass]; +} + +//------------------------------------------------------------------------------ + +bool CTFBotRoster::IsClassChangeAllowed() const +{ + return m_bAllowClassChanges; +} diff --git a/game/server/tf/bot/map_entities/tf_bot_roster.h b/game/server/tf/bot/map_entities/tf_bot_roster.h new file mode 100644 index 0000000..e3931ba --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_bot_roster.h @@ -0,0 +1,39 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_bot_roster.h +// entity that dictates what classes a bot can choose when spawning +// Tom Bui, April 2010 + +#ifndef TF_BOT_ROSTER_H +#define TF_BOT_ROSTER_H + +class CTFBotRoster : public CPointEntity +{ + DECLARE_CLASS( CTFBotRoster, CPointEntity ); +public: + DECLARE_DATADESC(); + + CTFBotRoster( void ); + virtual ~CTFBotRoster() {} + + // input + void InputSetAllowScout( inputdata_t &inputdata ); + void InputSetAllowSniper( inputdata_t &inputdata ); + void InputSetAllowSoldier( inputdata_t &inputdata ); + void InputSetAllowDemoman( inputdata_t &inputdata ); + void InputSetAllowMedic( inputdata_t &inputdata ); + void InputSetAllowHeavy( inputdata_t &inputdata ); + void InputSetAllowPyro( inputdata_t &inputdata ); + void InputSetAllowSpy( inputdata_t &inputdata ); + void InputSetAllowEngineer( inputdata_t &inputdata ); + + // misc. + bool IsClassAllowed( int iBotClass ) const; + bool IsClassChangeAllowed() const; + +public: + string_t m_teamName; + bool m_bAllowClassChanges; + bool m_bAllowedClasses[TF_LAST_NORMAL_CLASS]; +}; + +#endif // TF_BOT_ROSTER_H diff --git a/game/server/tf/bot/map_entities/tf_spawner.cpp b/game/server/tf/bot/map_entities/tf_spawner.cpp new file mode 100644 index 0000000..fa4f7c7 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_spawner.cpp @@ -0,0 +1,163 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_spawner.cpp +// Entity to spawn one or more templatized entities +// Michael Booth, April 2011 + +#include "cbase.h" + +#include "tf_gamerules.h" +#include "bot/map_entities/tf_spawner.h" + + +//------------------------------------------------------------------------------ +BEGIN_DATADESC( CTFSpawner ) + DEFINE_KEYFIELD( m_spawnCount, FIELD_INTEGER, "count" ), + DEFINE_KEYFIELD( m_maxActiveCount, FIELD_INTEGER, "maxActive" ), + DEFINE_KEYFIELD( m_spawnInterval, FIELD_FLOAT, "interval" ), + DEFINE_KEYFIELD( m_templateName, FIELD_STRING, "template" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Reset", InputReset ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_OUTPUT( m_onExpended, "OnExpended" ), + DEFINE_OUTPUT( m_onSpawned, "OnSpawned" ), + DEFINE_OUTPUT( m_onKilled, "OnKilled" ), + + DEFINE_THINKFUNC( SpawnerThink ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( tf_spawner, CTFSpawner ); + + +//------------------------------------------------------------------------------ +CTFSpawner::CTFSpawner( void ) +{ + Reset(); +} + + +//------------------------------------------------------------------------------ +void CTFSpawner::Reset( void ) +{ + m_bExpended = false; + m_spawnCountRemaining = 0; + m_spawnedVector.RemoveAll(); + SetThink( NULL ); +} + + +//------------------------------------------------------------------------------ +void CTFSpawner::InputReset( inputdata_t &inputdata ) +{ + Reset(); +} + + +//------------------------------------------------------------------------------ +void CTFSpawner::InputEnable( inputdata_t &inputdata ) +{ + if ( m_bExpended ) + { + return; + } + + SetThink( &CTFSpawner::SpawnerThink ); + + if ( m_spawnCountRemaining ) + { + // already generating - don't restart count + return; + } + + SetNextThink( gpGlobals->curtime ); + m_spawnCountRemaining = m_spawnCount; + + m_template = dynamic_cast< CTFSpawnTemplate * >( gEntList.FindEntityByName( NULL, m_templateName ) ); + if ( m_template == NULL ) + { + Warning( "%s failed to find template named '%s'\n", GetClassname(), STRING( m_templateName ) ); + } +} + + +//------------------------------------------------------------------------------ +void CTFSpawner::InputDisable( inputdata_t &inputdata ) +{ + // just stop thinking + SetThink( NULL ); +} + + +//------------------------------------------------------------------------------ +void CTFSpawner::OnKilled( CBaseEntity *dead ) +{ + m_onKilled.FireOutput( dead, this ); +} + + +//------------------------------------------------------------------------------ +void CTFSpawner::SpawnerThink( 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; + } + + // clean up destroyed children + for ( int i = 0; i < m_spawnedVector.Count(); ) + { + CHandle< CBaseEntity > child = m_spawnedVector[i]; + + if ( child == NULL ) + { + m_spawnedVector.FastRemove(i); + m_onKilled.FireOutput( this, this ); + continue; + } + + ++i; + } + + if ( m_spawnedVector.Count() >= m_maxActiveCount ) + { + // reached max simultanous active count + SetNextThink( gpGlobals->curtime + 0.1f ); + return; + } + + if ( m_template == NULL ) + { + // nothing to spawn! + return; + } + + // spawn the entity + CBaseEntity *child = m_template->Instantiate(); + if ( child ) + { + m_spawnedVector.AddToTail( child ); + + child->SetAbsOrigin( GetAbsOrigin() ); + child->SetAbsAngles( GetAbsAngles() ); + child->SetOwnerEntity( this ); + + DispatchSpawn( child ); + m_onSpawned.FireOutput( child, this ); + + --m_spawnCountRemaining; + if ( m_spawnCountRemaining ) + { + SetNextThink( gpGlobals->curtime + m_spawnInterval ); + } + else + { + SetThink( NULL ); + m_onExpended.FireOutput( this, this ); + m_bExpended = true; + } + } +} diff --git a/game/server/tf/bot/map_entities/tf_spawner.h b/game/server/tf/bot/map_entities/tf_spawner.h new file mode 100644 index 0000000..f57db89 --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_spawner.h @@ -0,0 +1,66 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_spawner.h +// Entity to spawn one or more templatized entities +// Michael Booth, April 2011 + +#ifndef TF_SPAWNER_H +#define TF_SPAWNER_H + +//-------------------------------------------------------- +/** + * Each particular type of entity the tf_spawner can create + * has an associated template (derived from this class) + * which defines its spawning location and initial properties. + */ +class CTFSpawnTemplate : public CPointEntity +{ +public: + DECLARE_CLASS( CTFSpawnTemplate, CPointEntity ); + + virtual ~CTFSpawnTemplate() { } + + virtual CBaseEntity *Instantiate( void ) const = 0; // spawn an instance of this template +}; + + +//-------------------------------------------------------- +class CTFSpawner : public CPointEntity +{ +public: + DECLARE_CLASS( CTFSpawner, CPointEntity ); + DECLARE_DATADESC(); + + CTFSpawner( void ); + virtual ~CTFSpawner() { } + + void SpawnerThink( void ); + + // Input. + void InputReset( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + // Output + void OnKilled( CBaseEntity *dead ); + +private: + void Reset( void ); + + bool m_bExpended; + int m_spawnCount; + int m_spawnCountRemaining; + int m_maxActiveCount; + float m_spawnInterval; + + string_t m_templateName; + CHandle< CTFSpawnTemplate > m_template; + + COutputEvent m_onSpawned; + COutputEvent m_onExpended; + COutputEvent m_onKilled; + + CUtlVector< CHandle< CBaseEntity > > m_spawnedVector; +}; + + +#endif // TF_SPAWNER_H diff --git a/game/server/tf/bot/map_entities/tf_spawner_boss.cpp b/game/server/tf/bot/map_entities/tf_spawner_boss.cpp new file mode 100644 index 0000000..4381dac --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_spawner_boss.cpp @@ -0,0 +1,167 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_spawner_boss.cpp +// Entity to spawn a Boss +// Michael Booth, February 2011 + +#include "cbase.h" + +#ifdef OBSOLETE_USE_BOSS_ALPHA + +#ifdef TF_RAID_MODE + +#include "tf_gamerules.h" +#include "tf_spawner_boss.h" +#include "bot_npc/bot_npc.h" + + +//------------------------------------------------------------------------------ + +BEGIN_DATADESC( CTFSpawnerBoss ) + DEFINE_KEYFIELD( m_spawnCount, FIELD_INTEGER, "count" ), + DEFINE_KEYFIELD( m_maxActiveCount, FIELD_INTEGER, "maxActive" ), + DEFINE_KEYFIELD( m_spawnInterval, FIELD_FLOAT, "interval" ), + DEFINE_KEYFIELD( m_teamName, FIELD_STRING, "team" ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + DEFINE_OUTPUT( m_onSpawned, "OnSpawned" ), + DEFINE_OUTPUT( m_onExpended, "OnExpended" ), + DEFINE_OUTPUT( m_onBotKilled, "OnBotKilled" ), + DEFINE_OUTPUT( m_onBotStunned, "OnBotStunned" ), + + DEFINE_THINKFUNC( SpawnerThink ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( tf_spawner_boss, CTFSpawnerBoss ); + + +//------------------------------------------------------------------------------ +CTFSpawnerBoss::CTFSpawnerBoss( void ) +{ + m_isExpended = false; + m_spawnCountRemaining = 0; + + SetThink( NULL ); +} + +//------------------------------------------------------------------------------ +void CTFSpawnerBoss::InputEnable( inputdata_t &inputdata ) +{ + if ( m_isExpended ) + { + return; + } + + SetThink( &CTFSpawnerBoss::SpawnerThink ); + + if ( m_spawnCountRemaining ) + { + // already generating - don't restart count + return; + } + SetNextThink( gpGlobals->curtime ); + m_spawnCountRemaining = m_spawnCount; +} + + +//------------------------------------------------------------------------------ +void CTFSpawnerBoss::InputDisable( inputdata_t &inputdata ) +{ + // just stop thinking + SetThink( NULL ); +} + + +//------------------------------------------------------------------------------ +void CTFSpawnerBoss::OnBotKilled( CBotNPC *pBot ) +{ + m_onBotKilled.FireOutput( pBot, this ); +} + + +//------------------------------------------------------------------------------ +void CTFSpawnerBoss::OnBotStunned( CBotNPC *pBot ) +{ + m_onBotStunned.FireOutput( pBot, this ); +} + + +//------------------------------------------------------------------------------ +void CTFSpawnerBoss::SpawnerThink( 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; + } + + // remove invalid handles from our collection + int i = 0; + while( i < m_spawnedBotVector.Count() ) + { + CHandle< CBotNPC > hBot = m_spawnedBotVector[i]; + if ( hBot == NULL ) + { + m_spawnedBotVector.FastRemove(i); + continue; + } + + ++i; + } + + if ( m_spawnedBotVector.Count() >= m_maxActiveCount ) + { + // maximum count reached - can't spawn any more + SetNextThink( gpGlobals->curtime + 0.1f ); + return; + } + + // spawn a bot + CBotNPC *bot = (CBotNPC *)CreateEntityByName( "bot_boss" ); + if ( bot ) + { + m_spawnedBotVector.AddToTail( bot ); + + int iTeam = TEAM_UNASSIGNED; + if ( FStrEq( m_teamName.ToCStr(), "red" ) ) + { + iTeam = TF_TEAM_RED; + } + else if ( FStrEq( m_teamName.ToCStr(), "blue" ) ) + { + iTeam = TF_TEAM_BLUE; + } + bot->ChangeTeam( iTeam ); + + // match bot facing to that of spawner + bot->SetAbsAngles( GetAbsAngles() ); + + bot->SetAbsOrigin( GetAbsOrigin() ); + + bot->SetSpawner( this ); + + DispatchSpawn( bot ); + + m_onSpawned.FireOutput( bot, this ); + + --m_spawnCountRemaining; + if ( m_spawnCountRemaining ) + { + SetNextThink( gpGlobals->curtime + m_spawnInterval ); + } + else + { + SetThink( NULL ); + m_onExpended.FireOutput( this, this ); + m_isExpended = true; + } + } +} + +#endif // TF_RAID_MODE + +#endif // #ifdef OBSOLETE_USE_BOSS_ALPHA + diff --git a/game/server/tf/bot/map_entities/tf_spawner_boss.h b/game/server/tf/bot/map_entities/tf_spawner_boss.h new file mode 100644 index 0000000..13f247a --- /dev/null +++ b/game/server/tf/bot/map_entities/tf_spawner_boss.h @@ -0,0 +1,54 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// tf_spawner_boss.h +// Entity to spawn a Boss +// Michael Booth, February 2011 + +#ifndef TF_SPAWNER_BOSS_H +#define TF_SPAWNER_BOSS_H + +#ifdef OBSOLETE_USE_BOSS_ALPHA + +#ifdef TF_RAID_MODE + +class CBotNPC; + +class CTFSpawnerBoss : public CPointEntity +{ +public: + DECLARE_CLASS( CTFSpawnerBoss, CPointEntity ); + DECLARE_DATADESC(); + + CTFSpawnerBoss( void ); + virtual ~CTFSpawnerBoss() { } + + void SpawnerThink( void ); + + // Input. + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + // Output + void OnBotKilled( CBotNPC *pBot ); + void OnBotStunned( CBotNPC *pBot ); + +private: + bool m_isExpended; + int m_spawnCount; + int m_spawnCountRemaining; + int m_maxActiveCount; + float m_spawnInterval; + string_t m_teamName; + + COutputEvent m_onSpawned; + COutputEvent m_onExpended; + COutputEvent m_onBotKilled; + COutputEvent m_onBotStunned; + + CUtlVector< CHandle< CBotNPC > > m_spawnedBotVector; +}; + +#endif // TF_RAID_MODE + +#endif // OBSOLETE_USE_BOSS_ALPHA + +#endif // TF_SPAWNER_BOSS_H |