summaryrefslogtreecommitdiff
path: root/game/shared/tf/tf_robot_destruction_robot.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/shared/tf/tf_robot_destruction_robot.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/shared/tf/tf_robot_destruction_robot.cpp')
-rw-r--r--game/shared/tf/tf_robot_destruction_robot.cpp1313
1 files changed, 1313 insertions, 0 deletions
diff --git a/game/shared/tf/tf_robot_destruction_robot.cpp b/game/shared/tf/tf_robot_destruction_robot.cpp
new file mode 100644
index 0000000..5fe2a66
--- /dev/null
+++ b/game/shared/tf/tf_robot_destruction_robot.cpp
@@ -0,0 +1,1313 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: The robots for use in the Robot Destruction TF2 game mode.
+//
+//=========================================================================//
+
+#include "cbase.h"
+#include "tf_logic_robot_destruction.h"
+#include "tf_shareddefs.h"
+#include "particle_parse.h"
+#include "tf_gamerules.h"
+#include "debugoverlay_shared.h"
+#ifdef GAME_DLL
+ #include "tf_ammo_pack.h"
+ #include "entity_bonuspack.h"
+ #include "entity_capture_flag.h"
+ #include "effect_dispatch_data.h"
+ #include "te_effect_dispatch.h"
+ #include "tf_gamestats.h"
+ #include "eventlist.h"
+#else
+ #include "eventlist.h"
+#endif
+
+#ifdef GAME_DLL
+ extern ConVar tf_obj_gib_velocity_min;
+ extern ConVar tf_obj_gib_velocity_max;
+ extern ConVar tf_obj_gib_maxspeed;
+#endif
+
+#define ADD_POINTS_CONTEXT "add_points_context"
+#define SPEW_BARS_CONTEXT "spew_bars_context"
+#define SELF_DESTRUCT_THINK "self_destruct_think"
+#define ANIMS_THINK "anims_think"
+
+ConVar tf_rd_robot_repair_rate( "tf_rd_robot_repair_rate", "60", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
+
+RobotData_t* g_RobotData[ NUM_ROBOT_TYPES ] =
+{
+ // Model // Busted model // Pain // Death // Collide // Idle // Bar offset
+ new RobotData_t( "models/bots/bot_worker/bot_worker_A.mdl", "models/bots/bot_worker/bot_worker_A.mdl", "Robot.Pain", "Robot.Death", "Robot.Collide", "Robot.Greeting", -35.f ),
+#ifdef STAGING_ONLY
+ new RobotData_t( "models/bots/bot_worker/bot_worker_b.mdl", "models/bots/bot_worker/bot_worker_b.mdl", "Robot.Pain", "Robot.Death", "Robot.Collide", "Robot.Greeting", -30.f ),
+#else
+ new RobotData_t( "models/bots/bot_worker/bot_worker2.mdl", "models/bots/bot_worker/bot_worker2.mdl", "Robot.Pain", "Robot.Death", "Robot.Collide", "Robot.Greeting", -30.f ),
+#endif
+ new RobotData_t( "models/bots/bot_worker/bot_worker3.mdl", "models/bots/bot_worker/bot_worker3_nohead.mdl", "Robot.Pain", "Robot.Death", "Robot.Collide", "Robot.Greeting", -10.f ),
+};
+
+#define ROBOT_DEATH_EXPLOSION "RD.BotDeathExplosion"
+#define SCORING_POINTS_PARTICLE_EFFECT "bot_radio_waves"
+#define DAMAGED_ROBOT_PARTICLE_EFFECT "sentrydamage_4"
+#define DEATH_PARTICLE_EFFECT "rd_robot_explosion"
+
+
+void RobotData_t::Precache()
+{
+ if ( GetStringData( MODEL_KEY ) )
+ {
+ CBaseEntity::PrecacheModel( GetStringData( MODEL_KEY ) );
+ PrecacheGibsForModel( modelinfo->GetModelIndex( GetStringData( MODEL_KEY ) ) );
+ PrecachePropsForModel( modelinfo->GetModelIndex( GetStringData( MODEL_KEY ) ), "spawn" );
+ }
+ if ( GetStringData( DAMAGED_MODEL_KEY ) ) CBaseEntity::PrecacheModel( GetStringData( DAMAGED_MODEL_KEY ) );
+ if ( GetStringData( HURT_SOUND_KEY ) ) CBaseEntity::PrecacheScriptSound( GetStringData( HURT_SOUND_KEY ) );
+ if ( GetStringData( DEATH_SOUND_KEY ) ) CBaseEntity::PrecacheScriptSound( GetStringData( DEATH_SOUND_KEY ) );
+ if ( GetStringData( COLLIDE_SOUND_KEY ) ) CBaseEntity::PrecacheScriptSound( GetStringData( COLLIDE_SOUND_KEY ) );
+ if ( GetStringData( IDLE_SOUND_KEY ) ) CBaseEntity::PrecacheScriptSound( GetStringData( IDLE_SOUND_KEY ) );
+}
+
+
+CTFRobotDestruction_RobotAnimController::CTFRobotDestruction_RobotAnimController( CTFRobotDestruction_Robot *pOuter )
+ : m_vecOldOrigin( vec3_origin )
+ , m_vecLean( vec3_origin )
+ , m_pOuter( pOuter )
+ , m_vecImpulse( vec3_origin )
+{}
+
+void CTFRobotDestruction_RobotAnimController::Update()
+{
+ if( !m_pOuter )
+ return;
+
+ CStudioHdr *pStudioHdr = m_pOuter->GetModelPtr();
+ if ( !pStudioHdr )
+ return;
+
+ const Vector vecNewOrigin = m_pOuter->GetAbsOrigin();
+ const Vector vecVelocity = m_vecOldOrigin - vecNewOrigin;
+ m_vecOldOrigin = vecNewOrigin;
+
+ Approach( m_vecLean, vecVelocity + m_vecImpulse, 2.f );
+ GetPoseParams();
+ Approach( m_vecImpulse, vec3_origin, 200.f );
+
+ Vector vecForward, vecRight;
+ AngleVectors( m_pOuter->GetAbsAngles(), &vecForward, &vecRight, NULL );
+
+ float flRightLean = vecRight.Dot( m_vecLean );
+ float flForwardLean = vecForward.Dot( m_vecLean );
+
+ m_pOuter->SetPoseParameter( m_poseParams.m_nMoveX, flForwardLean );
+ m_pOuter->SetPoseParameter( m_poseParams.m_nMoveY, flRightLean );
+}
+
+void CTFRobotDestruction_RobotAnimController::Impulse( const Vector& vecImpulse )
+{
+ m_vecImpulse += vecImpulse * 5;
+}
+
+void CTFRobotDestruction_RobotAnimController::Approach( Vector& vecIn, const Vector& vecTarget, float flRate )
+{
+ Vector vecApproach = ( vecTarget - vecIn ) * flRate * gpGlobals->frametime;
+ if ( vecApproach.LengthSqr() > ( vecIn - vecTarget ).LengthSqr() )
+ vecIn = vecTarget;
+ else
+ vecIn += vecApproach;
+}
+
+void CTFRobotDestruction_RobotAnimController::GetPoseParams()
+{
+ m_poseParams.m_nMoveX = m_pOuter->LookupPoseParameter( "move_x" );
+ m_poseParams.m_nMoveY = m_pOuter->LookupPoseParameter( "move_y" );
+}
+
+IMPLEMENT_NETWORKCLASS_ALIASED( TFRobotDestruction_Robot, DT_TFRobotDestruction_Robot )
+
+BEGIN_NETWORK_TABLE( CTFRobotDestruction_Robot, DT_TFRobotDestruction_Robot )
+#ifdef CLIENT_DLL
+ RecvPropInt( RECVINFO(m_iHealth) ),
+ RecvPropInt( RECVINFO(m_iMaxHealth) ),
+ RecvPropInt( RECVINFO(m_eType) ),
+#else
+ SendPropInt(SENDINFO(m_iHealth), -1, SPROP_VARINT ),
+ SendPropInt(SENDINFO(m_iMaxHealth), -1, SPROP_VARINT ),
+ SendPropInt(SENDINFO(m_eType), -1, SPROP_VARINT ),
+#endif
+END_NETWORK_TABLE()
+
+BEGIN_DATADESC( CTFRobotDestruction_Robot )
+#ifdef GAME_DLL
+ DEFINE_INPUTFUNC( FIELD_VOID, "StopAndUseComputer", InputStopAndUseComputer ),
+#endif
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( tf_robot_destruction_robot, CTFRobotDestruction_Robot );
+
+CTFRobotDestruction_Robot::CTFRobotDestruction_Robot()
+ : m_animController( this )
+{
+#ifdef GAME_DLL
+ m_nPointsSpewed = 0;
+
+ m_intention = new CRobotIntention( this );
+ m_locomotor = new CRobotLocomotion( this );
+ m_body = new CHeadlessHatmanBody( this );
+ m_bIsPanicked = false;
+#else
+ ListenForGameEvent( "rd_robot_impact" );
+#endif
+}
+
+CTFRobotDestruction_Robot::~CTFRobotDestruction_Robot()
+{
+#ifdef CLIENT_DLL
+ m_hDamagedParticleEffect = NULL;
+#else
+ if ( m_hSpawn )
+ m_hSpawn->ClearRobot();
+ if ( m_intention )
+ delete m_intention;
+ if ( m_locomotor )
+ delete m_locomotor;
+ if ( m_body )
+ delete m_body;
+#endif
+}
+
+void CTFRobotDestruction_Robot::StaticPrecache()
+{
+ PrecacheParticleSystem( DEATH_PARTICLE_EFFECT );
+ PrecacheParticleSystem( SCORING_POINTS_PARTICLE_EFFECT );
+ PrecacheParticleSystem( DAMAGED_ROBOT_PARTICLE_EFFECT );
+ PrecacheScriptSound( ROBOT_DEATH_EXPLOSION );
+
+ for( int i=0; i < ARRAYSIZE( g_RobotData ); ++i )
+ {
+ g_RobotData[i]->Precache();
+ }
+}
+
+void CTFRobotDestruction_Robot::Precache()
+{
+ BaseClass::Precache();
+ StaticPrecache();
+}
+
+void CTFRobotDestruction_Robot::Spawn()
+{
+ // Clear out the gib list and create a new one.
+ m_aGibs.Purge();
+ BuildGibList( m_aGibs, GetModelIndex(), 1.0f, COLLISION_GROUP_NONE );
+ BuildPropList( "spawn", m_aSpawnProps, GetModelIndex(), 1.f, COLLISION_GROUP_NONE );
+
+ BaseClass::Spawn();
+
+ SetSolid( SOLID_BBOX );
+
+ m_takedamage = DAMAGE_YES;
+ m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
+#ifdef GAME_DLL
+ SetContextThink( &CTFRobotDestruction_Robot::UpdateAnimsThink, gpGlobals->curtime, ANIMS_THINK );
+
+ if ( m_hGroup )
+ {
+ m_hGroup->UpdateState();
+ }
+
+ m_hNextPath.Set( dynamic_cast<CPathTrack*>( gEntList.FindEntityByName( NULL, m_spawnData.m_pszPathName ) ) );
+ // The path needs to exist
+ if ( !m_hNextPath )
+ {
+ UTIL_Remove( this );
+ }
+
+ if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
+ CTFRobotDestructionLogic::GetRobotDestructionLogic()->RobotCreated( this );
+
+ // Create our dispenser
+ m_pDispenser = dynamic_cast<CRobotDispenser*>( CreateEntityByName( "rd_robot_dispenser" ) );
+ Assert( m_pDispenser );
+ m_pDispenser->SetParent( this );
+ m_pDispenser->Spawn();
+ m_pDispenser->ChangeTeam( GetTeamNumber() );
+ m_pDispenser->OnGoActive();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Dont collide with players
+//-----------------------------------------------------------------------------
+bool CTFRobotDestruction_Robot::ShouldCollide( int collisionGroup, int contentsMask ) const
+{
+ if ( collisionGroup == COLLISION_GROUP_PLAYER_MOVEMENT )
+ {
+ return false;
+ }
+
+ return BaseClass::ShouldCollide( collisionGroup, contentsMask );
+}
+
+
+#ifdef CLIENT_DLL
+
+//-----------------------------------------------------------------------------
+// Purpose: Specify where our healthbars should go over our heads
+//-----------------------------------------------------------------------------
+float CTFRobotDestruction_Robot::GetHealthBarHeightOffset() const
+{
+ return g_RobotData[ m_eType ]->GetFloatData( RobotData_t::HEALTH_BAR_OFFSET_KEY );
+}
+
+
+void CTFRobotDestruction_Robot::OnDataChanged( DataUpdateType_t type )
+{
+ BaseClass::OnDataChanged( type );
+
+ if ( type == DATA_UPDATE_CREATED )
+ {
+ SetNextClientThink( CLIENT_THINK_ALWAYS );
+ }
+
+ UpdateDamagedEffects();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play damaged effects, similar to sentries
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::UpdateDamagedEffects()
+{
+ // Start playing our damaged particle if we're damaged
+ bool bLowHealth = GetHealth() <= (GetMaxHealth() * 0.5);
+ if ( bLowHealth && !m_hDamagedParticleEffect )
+ {
+ m_hDamagedParticleEffect = ParticleProp()->Create( DAMAGED_ROBOT_PARTICLE_EFFECT,
+ PATTACH_ABSORIGIN_FOLLOW,
+ INVALID_PARTICLE_ATTACHMENT,
+ Vector(0,0,50) );
+
+ }
+ else if ( !bLowHealth && m_hDamagedParticleEffect )
+ {
+ ParticleProp()->StopEmission( m_hDamagedParticleEffect );
+ m_hDamagedParticleEffect = NULL;
+ }
+}
+
+void CTFRobotDestruction_Robot::UpdateClientSideAnimation( void )
+{
+ m_animController.Update();
+
+ BaseClass::UpdateClientSideAnimation();
+}
+
+void CTFRobotDestruction_Robot::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options )
+{
+ if ( event == AE_RD_ROBOT_POP_PANELS_OFF )
+ {
+ CUtlVector<breakmodel_t> vecProp;
+ FOR_EACH_VEC( m_aSpawnProps, i )
+ {
+ char pstrLowerName[ MAX_PATH ];
+ memset( pstrLowerName, 0, sizeof(pstrLowerName) );
+ Q_snprintf( pstrLowerName, sizeof(pstrLowerName), "%s", options );
+ Q_strlower( pstrLowerName );
+ if ( Q_strstr( m_aSpawnProps[i].modelName, pstrLowerName ) )
+ {
+ vecProp.AddToTail( m_aSpawnProps[i] );
+ }
+ }
+
+ if ( vecProp.Count() )
+ {
+ Vector vForward, vRight, vUp;
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ Vector vecBreakVelocity = Vector(0,0,200);
+ AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
+ Vector vecOrigin = GetAbsOrigin() + vForward*70 + vUp*10;
+ QAngle vecAngles = GetAbsAngles();
+ breakablepropparams_t breakParams( vecOrigin, vecAngles, vecBreakVelocity, angularImpulse );
+ breakParams.impactEnergyScale = 1.0f;
+ breakParams.defBurstScale = 3.f;
+ int nModelIndex = GetModelIndex();
+
+ CreateGibsFromList( vecProp, nModelIndex, NULL, breakParams, this, -1 , false, true );
+ }
+ }
+
+ BaseClass::FireEvent( origin, angles, event, options );
+}
+
+void CTFRobotDestruction_Robot::FireGameEvent( IGameEvent *pEvent )
+{
+ const char *pszName = pEvent->GetName();
+ if ( FStrEq( pszName, "rd_robot_impact" ) )
+ {
+ const int index_ = pEvent->GetInt( "entindex" );
+ if ( index_ == entindex() )
+ {
+ Vector vecImpulse( pEvent->GetFloat( "impulse_x" ), pEvent->GetFloat( "impulse_y" ), pEvent->GetFloat( "impulse_z" ) );
+ m_animController.Impulse( vecImpulse.Normalized() * 20 );
+ }
+ }
+}
+
+ CStudioHdr* CTFRobotDestruction_Robot::OnNewModel()
+ {
+ CStudioHdr *hdr = BaseClass::OnNewModel();
+ BuildPropList( "spawn", m_aSpawnProps, GetModelIndex(), 1.f, COLLISION_GROUP_NONE );
+
+ return hdr;
+ }
+
+#endif
+
+
+#ifdef GAME_DLL
+void CTFRobotDestruction_Robot::HandleAnimEvent( animevent_t *pEvent )
+{
+ if ((pEvent->type & AE_TYPE_NEWEVENTSYSTEM) && (pEvent->type & AE_TYPE_SERVER))
+ {
+ /* if ( pEvent->event == AE_RD_ROBOT_POP_PANELS_OFF )
+ {
+ CUtlVector<breakmodel_t> vecProp;
+ FOR_EACH_VEC( m_aSpawnProps, i )
+ {
+ char pstrLowerName[ MAX_PATH ];
+ memset( pstrLowerName, 0, sizeof(pstrLowerName) );
+ Q_snprintf( pstrLowerName, sizeof(pstrLowerName), "%s", pEvent->options );
+ Q_strlower( pstrLowerName );
+ if ( Q_strstr( m_aSpawnProps[i].modelName, pstrLowerName ) )
+ {
+ vecProp.AddToTail( m_aSpawnProps[i] );
+ }
+ }
+
+ if ( vecProp.Count() )
+ {
+ Vector vForward, vRight, vUp;
+ AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp );
+
+ Vector vecBreakVelocity = Vector(0,0,200);
+ AngularImpulse angularImpulse( RandomFloat( 0.0f, 120.0f ), RandomFloat( 0.0f, 120.0f ), 0.0 );
+ Vector vecOrigin = GetAbsOrigin() + vForward*70 + vUp*10;
+ QAngle vecAngles = GetAbsAngles();
+ breakablepropparams_t breakParams( vecOrigin, vecAngles, vecBreakVelocity, angularImpulse );
+ breakParams.impactEnergyScale = 1.0f;
+
+ int nModelIndex = modelinfo->GetModelIndex( STRING(GetModelName()) );
+ CreateGibsFromList( vecProp, nModelIndex, NULL, breakParams, NULL, -1 , false, true );
+ }
+ }*/
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Tell the game logic we're gone
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::UpdateOnRemove( void )
+{
+ BaseClass::UpdateOnRemove();
+
+ if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
+ {
+ CTFRobotDestructionLogic::GetRobotDestructionLogic()->RobotRemoved( this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Play our death visual and audio effects
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::PlayDeathEffects()
+{
+ EmitSound( g_RobotData[ GetRobotSpawnData().m_eType ]->GetStringData( RobotData_t::DEATH_SOUND_KEY ) );
+ EmitSound( ROBOT_DEATH_EXPLOSION );
+ DispatchParticleEffect( DEATH_PARTICLE_EFFECT, GetAbsOrigin(), QAngle( 0,0,0 ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle getting killed
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Let the game logic know that we died
+ if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
+ {
+ CTFRobotDestructionLogic::GetRobotDestructionLogic()->RobotRemoved( this );
+ }
+
+ PlayDeathEffects();
+
+ // Find the killer & the scorer
+ CBaseEntity *pInflictor = info.GetInflictor();
+ CBaseEntity *pKiller = info.GetAttacker();
+ CTFPlayer *pScorer = ToTFPlayer( TFGameRules()->GetDeathScorer( pKiller, pInflictor, this ) );
+ CTFPlayer *pAssister = NULL;
+
+ if ( pScorer )
+ {
+ // If a player is healing the scorer, give that player credit for the assist
+ CTFPlayer *pHealer = ToTFPlayer( static_cast<CBaseEntity *>( pScorer->m_Shared.GetFirstHealer() ) );
+ // Must be a medic to receive a healing assist, otherwise engineers get credit for assists from dispensers doing healing.
+ // Also don't give an assist for healing if the inflictor was a sentry gun, otherwise medics healing engineers get assists for the engineer's sentry kills.
+ if ( pHealer && ( pHealer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC ) )
+ {
+ pAssister = pHealer;
+ }
+
+ // Work out what killed the player, and send a message to all clients about it
+ int iWeaponID;
+ const char *killer_weapon_name = TFGameRules()->GetKillingWeaponName( info, NULL, &iWeaponID );
+ const char *killer_weapon_log_name = killer_weapon_name;
+
+ CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( pScorer->Weapon_OwnsThisID( iWeaponID ) );
+ if ( pWeapon )
+ {
+ CEconItemView *pItem = pWeapon->GetAttributeContainer()->GetItem();
+
+ if ( pItem )
+ {
+ if ( pItem->GetStaticData()->GetIconClassname() )
+ {
+ killer_weapon_name = pItem->GetStaticData()->GetIconClassname();
+ }
+
+ if ( pItem->GetStaticData()->GetLogClassname() )
+ {
+ killer_weapon_log_name = pItem->GetStaticData()->GetLogClassname();
+ }
+ }
+ }
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "rd_robot_killed" );
+ if ( event )
+ {
+ if ( pAssister && ( pAssister != pScorer ) )
+ {
+ event->SetInt( "assister", pAssister->GetUserID() );
+ }
+
+ event->SetInt( "attacker", pScorer->GetUserID() ); // attacker
+ event->SetString( "weapon", killer_weapon_name );
+ event->SetString( "weapon_logclassname", killer_weapon_log_name );
+ event->SetInt( "weaponid", iWeaponID );
+ event->SetInt( "priority", 6 ); // HLTV event priority, not transmitted
+
+ gameeventmanager->FireEvent( event );
+ }
+ }
+
+ if ( m_hSpawn )
+ {
+ m_hSpawn->OnRobotKilled();
+ }
+
+ if ( m_hGroup )
+ {
+ m_hGroup->OnRobotKilled();
+ }
+
+ // Kings dont die right away. Their head cracks open and they spew points out over time, then self-destruct
+ if ( m_spawnData.m_eType == ROBOT_TYPE_KING )
+ {
+ // Change our model to be the damage king model
+ SetModel( g_RobotData[ m_spawnData.m_eType ]->GetStringData( RobotData_t::DAMAGED_MODEL_KEY ) );
+ ResetSequence( LookupSequence("idle") );
+
+ m_takedamage = DAMAGE_NO;
+ SetContextThink( &CTFRobotDestruction_Robot::SpewBarsThink, gpGlobals->curtime, SPEW_BARS_CONTEXT );
+ SetContextThink( &CTFRobotDestruction_Robot::SelfDestructThink, gpGlobals->curtime + 5.f, SELF_DESTRUCT_THINK );
+
+ return;
+ }
+
+ // Spew our points
+ SpewBars( m_spawnData.m_nNumGibs );
+
+ // Spew ammo gibs out
+ SpewGibs();
+
+ CBaseAnimating::Event_Killed( info );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Spew out robot gibs that give ammo
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::SpewGibs()
+{
+ for ( int i=0; i<m_aGibs.Count(); i++ )
+ {
+ CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( GetAbsOrigin() + m_aGibs[i].offset, GetAbsAngles(), this, m_aGibs[i].modelName );
+ Assert( pAmmoPack );
+ if ( pAmmoPack )
+ {
+ pAmmoPack->ActivateWhenAtRest();
+
+ // Calculate the initial impulse on the weapon.
+ Vector vecImpulse( random->RandomFloat( -0.5f, 0.5f ), random->RandomFloat( -0.5f, 0.5f ), random->RandomFloat( 0.75f, 1.25f ) );
+ VectorNormalize( vecImpulse );
+ // Detect the head model
+ bool bIsTheHead = FStrEq( "models/bots/bot_worker/bot_worker_head_gib.mdl", m_aGibs[i].modelName );
+ if ( bIsTheHead )
+ {
+ // Pop more up than anything
+ vecImpulse[2] = 3.f;
+ vecImpulse *= random->RandomFloat( tf_obj_gib_velocity_max.GetFloat() * 0.75, tf_obj_gib_velocity_max.GetFloat() );
+ }
+ else
+ {
+ vecImpulse *= random->RandomFloat( tf_obj_gib_velocity_min.GetFloat(), tf_obj_gib_velocity_max.GetFloat() );
+ }
+
+
+ // Cap the impulse.
+ float flSpeed = vecImpulse.Length();
+ if ( flSpeed > tf_obj_gib_maxspeed.GetFloat() )
+ {
+ VectorScale( vecImpulse, tf_obj_gib_maxspeed.GetFloat() / flSpeed, vecImpulse );
+ }
+
+ if ( pAmmoPack->VPhysicsGetObject() )
+ {
+ AngularImpulse angImpulse( 0.f, random->RandomFloat( 0.f, 100.f ), 0.f );
+ if ( bIsTheHead )
+ {
+ // Make the head spin around like a top
+ angImpulse = AngularImpulse( RandomFloat(-60.f,60.f), RandomFloat(-60.f,60.f), 100000.f );
+ }
+ pAmmoPack->VPhysicsGetObject()->SetVelocityInstantaneous( &vecImpulse, &angImpulse );
+ }
+
+ pAmmoPack->SetInitialVelocity( vecImpulse );
+
+ pAmmoPack->m_nSkin = ( GetTeamNumber() == TF_TEAM_RED ) ? 0 : 1;
+
+ // Give the ammo pack some health, so that trains can destroy it.
+ pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ pAmmoPack->m_takedamage = DAMAGE_YES;
+ pAmmoPack->SetHealth( 900 );
+ pAmmoPack->m_bObjGib = true;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Do some special effects when we take damage
+//-----------------------------------------------------------------------------
+int CTFRobotDestruction_Robot::OnTakeDamage( const CTakeDamageInfo &info )
+{
+ // Check teams
+ if ( info.GetAttacker() )
+ {
+ if ( InSameTeam(info.GetAttacker()) )
+ return 0;
+
+ CBasePlayer *pAttacker = ToBasePlayer( info.GetAttacker() );
+ if ( pAttacker )
+ {
+ pAttacker->SetLastObjectiveTime( gpGlobals->curtime );
+ }
+ }
+
+ SetContextThink( &CTFRobotDestruction_Robot::RepairSelfThink, gpGlobals->curtime + 5.f, "RepairSelfThink" );
+
+ if ( m_bShielded )
+ {
+ return 0;
+ }
+
+ CTakeDamageInfo newInfo;
+ newInfo = info;
+ ModifyDamage( &newInfo );
+
+ // Get our attack vectors
+ Vector vecDamagePos = newInfo.GetDamagePosition();
+ QAngle vecDamageAngles;
+ VectorAngles( -newInfo.GetDamageForce(), vecDamageAngles );
+ // Use worldspace center if no damage position (happens with flamethrowers)
+ if ( vecDamagePos == vec3_origin )
+ {
+ vecDamagePos = WorldSpaceCenter();
+ }
+
+ // Play a spark effect
+ DispatchParticleEffect( "rd_bot_impact_sparks", vecDamagePos, vecDamageAngles );
+
+ // Send an impulse event to the client for this bot
+ Vector vecImpulse( newInfo.GetDamageForce() );
+ m_animController.Impulse( vecImpulse.Normalized() * 20.f );
+
+ IGameEvent *event = gameeventmanager->CreateEvent( "rd_robot_impact" );
+ if ( event )
+ {
+ event->SetInt( "entindex", entindex() );
+ event->SetInt( "impulse_x", vecImpulse.x );
+ event->SetInt( "impulse_y", vecImpulse.y );
+ event->SetInt( "impulse_z", vecImpulse.z );
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ if( m_hGroup )
+ {
+ m_hGroup->OnRobotAttacked();
+ }
+
+ int nBaseResult = BaseClass::OnTakeDamage( newInfo );
+
+ // Let the game logic know that we got hurt
+ if ( CTFRobotDestructionLogic::GetRobotDestructionLogic() )
+ CTFRobotDestructionLogic::GetRobotDestructionLogic()->RobotAttacked( this );
+
+ return nBaseResult;
+}
+
+
+void CTFRobotDestruction_Robot::ModifyDamage( CTakeDamageInfo *info ) const
+{
+ CTFPlayer *pAttacker = ToTFPlayer( info->GetAttacker() );
+ if ( pAttacker )
+ {
+ float flScale = 1.f;
+
+ if ( pAttacker->IsPlayerClass( TF_CLASS_SCOUT ) )
+ flScale = 1.5f;
+ else if( pAttacker->IsPlayerClass( TF_CLASS_SNIPER ) )
+ flScale = 2.25f;
+ else if ( pAttacker->IsPlayerClass( TF_CLASS_SPY ) )
+ flScale = 2.f;
+ else if ( pAttacker->IsPlayerClass( TF_CLASS_PYRO ) )
+ flScale = 0.75;
+ else if ( pAttacker->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) )
+ flScale = 0.75;
+ else if ( pAttacker->IsPlayerClass( TF_CLASS_MEDIC ) )
+ flScale = 2.f;
+
+ info->SetDamage( info->GetDamage() * flScale );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Override base traceattack to prevent visible effects from team members shooting me
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
+{
+ // Prevent team damage here so blood doesn't appear
+ if ( inputInfo.GetAttacker() && InSameTeam(inputInfo.GetAttacker()) )
+ return;
+
+ AddMultiDamage( inputInfo, this );
+}
+
+void CTFRobotDestruction_Robot::UpdateAnimsThink( void )
+{
+ m_animController.Update();
+
+ SetContextThink( &CTFRobotDestruction_Robot::UpdateAnimsThink, gpGlobals->curtime, ANIMS_THINK );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Change our AI state to use a computer
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::InputStopAndUseComputer( inputdata_t &inputdata )
+{
+ // TODO: Fire off into the AI
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Shoot bars out as we die
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::SpewBars( int nNumToSpew )
+{
+ for( int i=0; i < nNumToSpew; ++i )
+ {
+ CBonusPack *pBonusPack = assert_cast< CBonusPack* >( CreateEntityByName( "item_bonuspack" ) );
+ if ( pBonusPack )
+ {
+ pBonusPack->ChangeTeam( GetEnemyTeam( GetTeamNumber() ) );
+ pBonusPack->SetDisabled( false );
+ pBonusPack->SetAbsOrigin( GetAbsOrigin() + Vector(0,0,20) );
+ pBonusPack->SetAbsAngles( QAngle( 0.f, RandomFloat( 0, 360.f ), 0.f ) );
+ // Calculate the initial impulse on the cores
+ Vector vecImpulse( random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( -0.5, 0.5 ), random->RandomFloat( 1.0, 1.25 ) );
+ VectorNormalize( vecImpulse );
+ vecImpulse *= random->RandomFloat( 125.f, 150.f );
+
+ // Cap the impulse.
+ float flSpeed = vecImpulse.Length();
+ if ( flSpeed > tf_obj_gib_maxspeed.GetFloat() )
+ {
+ VectorScale( vecImpulse, tf_obj_gib_maxspeed.GetFloat() / flSpeed, vecImpulse );
+ }
+
+ pBonusPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
+ pBonusPack->AddSpawnFlags( SF_NORESPAWN );
+ pBonusPack->m_nSkin = GetTeamNumber() == TF_TEAM_RED ? 0 : 1;
+
+ DispatchSpawn( pBonusPack );
+ pBonusPack->DropSingleInstance( vecImpulse, NULL, 0, 0 );
+ pBonusPack->SetCycle( RandomFloat( 0.f, 1.f ) );
+ pBonusPack->SetGravity( 0.2f );
+ }
+ }
+}
+
+void CTFRobotDestruction_Robot::SpewBarsThink()
+{
+ int nNumToSpew = 1;
+ m_nPointsSpewed += nNumToSpew;
+ SpewBars( nNumToSpew );
+
+ if ( m_nPointsSpewed >= m_spawnData.m_nNumGibs )
+ {
+ SelfDestructThink();
+ }
+ else
+ {
+ SetContextThink( &CTFRobotDestruction_Robot::SpewBarsThink, gpGlobals->curtime + 0.1f, SPEW_BARS_CONTEXT );
+ }
+}
+
+void CTFRobotDestruction_Robot::SelfDestructThink()
+{
+ SpewGibs();
+ SpewBars( m_spawnData.m_nNumGibs - m_nPointsSpewed );
+ PlayDeathEffects();
+ UTIL_Remove( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Repair ourselves!
+//-----------------------------------------------------------------------------
+void CTFRobotDestruction_Robot::RepairSelfThink()
+{
+ // Heal!
+ int nHealth = GetHealth();
+ if ( tf_rd_robot_repair_rate.GetFloat() != 0.f )
+ {
+ nHealth += GetMaxHealth() / tf_rd_robot_repair_rate.GetFloat();
+ }
+ nHealth = Min( nHealth, GetMaxHealth() );
+ SetHealth( nHealth );
+
+ // Continue to heal if we're still hurt
+ if ( GetHealth() != GetMaxHealth() )
+ {
+ SetContextThink( &CTFRobotDestruction_Robot::RepairSelfThink, gpGlobals->curtime + 1.f, "RepairSelfThink" );
+ }
+}
+
+void CTFRobotDestruction_Robot::ArriveAtPath()
+{
+ m_hNextPath->AcceptInput( "InPass", this, this, variant_t(), 0 );
+ m_hNextPath = m_hNextPath->GetNext();
+}
+
+void CTFRobotDestruction_Robot::EnableUber()
+{
+ m_bShielded = true;
+ m_nSkin = GetTeamNumber() == TF_TEAM_RED ? 2 : 3;
+
+ if ( m_hGroup )
+ {
+ m_hGroup->UpdateState();
+ }
+}
+
+void CTFRobotDestruction_Robot::DisableUber()
+{
+ m_bShielded = false;
+ m_nSkin = GetTeamNumber() == TF_TEAM_RED ? 0 : 1;
+
+ if ( m_hGroup )
+ {
+ m_hGroup->UpdateState();
+ }
+}
+
+void CTFRobotDestruction_Robot::SetNewActivity( Activity activity )
+{
+ int nSequence = SelectWeightedSequence( activity );
+ if ( nSequence )
+ {
+ SetSequence( nSequence );
+ SetPlaybackRate( 1.0f );
+ SetCycle( 0 );
+ ResetSequenceInfo();
+ }
+}
+
+#define CLOSE_ENOUGH_TO_PATH 50.f
+
+//---------------------------------------------------------------------------------------------
+class CRobotPatrol : public Action< CTFRobotDestruction_Robot >
+{
+public:
+ void PlayIdleActivity( CTFRobotDestruction_Robot *pMe )
+ {
+ pMe->SetNewActivity( ACT_BOT_PRIMARY_MOVEMENT );
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+ {
+ PlayIdleActivity( pMe );
+
+ return Continue();
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > OnResume( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *interruptingAction )
+ {
+ PlayIdleActivity( pMe );
+
+ return Continue();
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > Update( CTFRobotDestruction_Robot *pMe, float interval )
+ {
+ CPathTrack* pNextPath = pMe->GetNextPath();
+
+ if ( pMe->IsRangeGreaterThan( pNextPath, CLOSE_ENOUGH_TO_PATH ) )
+ {
+ if ( m_path.GetAge() > 0.5f )
+ {
+ CRobotPathCost cost( pMe );
+ m_path.Compute( pMe, pNextPath->GetAbsOrigin(), cost );
+ }
+
+ m_path.Update( pMe );
+ }
+ else
+ {
+ pMe->ArriveAtPath();
+ }
+
+ return Continue();
+ }
+
+ virtual const char *GetName( void ) const { return "Patrol"; } // return name of this action
+ PathFollower m_path;
+};
+
+class CRobotSpawn : public Action< CTFRobotDestruction_Robot >
+{
+ virtual ActionResult< CTFRobotDestruction_Robot > OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+ {
+ pMe->SetNewActivity( ACT_BOT_SPAWN );
+ return Continue();
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > Update( CTFRobotDestruction_Robot *pMe, float interval )
+ {
+ if ( pMe->IsActivityFinished() )
+ {
+ return ChangeTo( new CRobotPatrol, "I've finished my spawn sequence" );
+ }
+
+ return Continue();
+ }
+
+ EventDesiredResult< CTFRobotDestruction_Robot > OnInjured( CTFRobotDestruction_Robot *pMe, const CTakeDamageInfo &info )
+ {
+ return TryToSustain( RESULT_CRITICAL, "I'm spawning and being attacked" );
+ }
+
+ virtual void OnEnd( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *nextAction )
+ {
+ pMe->SetBodygroup( pMe->FindBodygroupByName( "head_shell" ), 1 );
+ pMe->SetBodygroup( pMe->FindBodygroupByName( "body_shell" ), 1 );
+ }
+
+ virtual const char *GetName( void ) const { return "Spawn"; } // return name of this action
+};
+
+class CRobotMaterialize : public Action< CTFRobotDestruction_Robot >
+{
+public:
+ virtual ActionResult< CTFRobotDestruction_Robot > OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+ {
+ // TODO: Play the materialize anim and effects
+ /*int nSequence = pMe->SelectWeightedSequence( ACT_BOT_MATERIALIZE );
+ pMe->ResetSequence( nSequence );*/
+ return Continue();
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > Update( CTFRobotDestruction_Robot *pMe, float interval )
+ {
+ // TODO: Check if materialize activity is finished
+ //if ( pMe->IsActivityFinished() )
+ {
+ return ChangeTo( new CRobotSpawn, "I've fully materialized" );
+ }
+
+ // TODO: Control the materialize anim
+
+ return Continue();
+ }
+
+ virtual const char *GetName( void ) const { return "Materialize"; } // return name of this action
+ PathFollower m_path;
+};
+
+
+class CRobotPanic : public Action< CTFRobotDestruction_Robot >
+{
+public:
+ virtual ActionResult< CTFRobotDestruction_Robot > OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction );
+ virtual ActionResult< CTFRobotDestruction_Robot > Update( CTFRobotDestruction_Robot *pMe, float interval );
+ virtual EventDesiredResult< CTFRobotDestruction_Robot > OnInjured( CTFRobotDestruction_Robot *pMe, const CTakeDamageInfo &info );
+ virtual void OnEnd( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *nextAction );
+
+ virtual const char *GetName( void ) const { return "Panic"; }
+
+private:
+
+ CountdownTimer m_SpeakTimer;
+ CountdownTimer m_attackedTimer;
+ CountdownTimer m_spinTimer;
+ bool m_bSpinRight;
+};
+
+class CRobotEnterPanic : public Action< CTFRobotDestruction_Robot >
+{
+ virtual ActionResult< CTFRobotDestruction_Robot > OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+ {
+ pMe->SetNewActivity( ACT_BOT_PANIC_START );
+ return Continue();
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > Update( CTFRobotDestruction_Robot *pMe, float interval )
+ {
+ if ( pMe->IsActivityFinished() )
+ {
+ return ChangeTo( new CRobotPanic, "I've finished my enter panic sequence" );
+ }
+
+ return Continue();
+ }
+
+ EventDesiredResult< CTFRobotDestruction_Robot > OnInjured( CTFRobotDestruction_Robot *pMe, const CTakeDamageInfo &info )
+ {
+ return TryToSustain( RESULT_CRITICAL, "I'm entering panic and being attacked" );
+ }
+
+ virtual const char *GetName( void ) const { return "Enter Panic"; } // return name of this action
+};
+
+class CRobotLeavePanic : public Action< CTFRobotDestruction_Robot >
+{
+ virtual ActionResult< CTFRobotDestruction_Robot > OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+ {
+ pMe->SetNewActivity( ACT_BOT_PANIC_END );
+ return Continue();
+ }
+
+ virtual ActionResult< CTFRobotDestruction_Robot > Update( CTFRobotDestruction_Robot *pMe, float interval )
+ {
+ if ( pMe->IsActivityFinished() )
+ {
+ return Done( "I've finished my leave panic sequence" );
+ }
+
+ return Continue();
+ }
+
+ EventDesiredResult< CTFRobotDestruction_Robot > OnInjured( CTFRobotDestruction_Robot *pMe, const CTakeDamageInfo &info )
+ {
+ return TryToSustain( RESULT_CRITICAL, "I'm leaving panic and being attacked" );
+ }
+
+ virtual const char *GetName( void ) const { return "Leave Panic"; } // return name of this action
+};
+
+//---------------------------------------------------------------------------------------------
+ActionResult< CTFRobotDestruction_Robot > CRobotPanic::OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+{
+ m_bSpinRight = RandomInt(0,1) == 1; // Randomly pick which way to spin
+ pMe->SetIsPanicked( true ); // Let our bot know he's panicked
+ m_attackedTimer.Start( 5.f ); // We panic for 5 seconds
+ m_spinTimer.Start( RandomFloat( 0.75f, 1.25f ) ); // Spin for a little bit
+ pMe->GetLocomotionInterface()->SetDesiredSpeed( 150.f ); // We go fast when panicked
+ DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pMe, "wheel" ); // Smoke trails on our tire when panicked
+ pMe->SetNewActivity( ACT_BOT_PANIC ); // Play panicked activity
+
+ m_SpeakTimer.Start( 3.f );
+ const RobotSpawnData_t & data = pMe->GetRobotSpawnData();
+ pMe->EmitSound( g_RobotData[ data.m_eType ]->GetStringData( RobotData_t::HURT_SOUND_KEY ) );
+
+ return Continue();
+}
+
+ActionResult< CTFRobotDestruction_Robot > CRobotPanic::Update( CTFRobotDestruction_Robot *pMe, float interval )
+{
+ // If we haven't been attacked in awhile, then we're done panicking
+ if ( m_attackedTimer.IsElapsed() )
+ {
+ return ChangeTo( new CRobotLeavePanic, "I'm done panicking" );
+ }
+
+ QAngle angles = pMe->GetLocalAngles();
+
+ // If our spin timer is still going, then spin!
+ if ( m_spinTimer.GetRemainingTime() < ( m_spinTimer.GetCountdownDuration() * 0.5f ) )
+ {
+ float flSpinAmt = 2500.f * gpGlobals->frametime;
+ flSpinAmt *= m_bSpinRight ? 1.f : -1.f;
+ angles.y += flSpinAmt;
+ pMe->SetLocalAngles( angles );
+ }
+
+ // We just drive forard
+ Vector vForward;
+ AngleVectors( angles, &vForward );
+ Vector vArrivePosition = pMe->GetAbsOrigin() + vForward * 30;
+
+ trace_t tr;
+ // See if we hit anything solid a little bit below the robot. We dont want to jump off cliffs
+ UTIL_TraceLine( vArrivePosition, vArrivePosition + Vector(0,0,-30), MASK_PLAYERSOLID, pMe, COLLISION_GROUP_PLAYER_MOVEMENT, &tr );
+ if ( tr.fraction < 1.0f )
+ {
+ pMe->GetLocomotionInterface()->Approach( vArrivePosition );
+ }
+
+ // It's time change our spin direction, choose when to spin next, and reapply our smoke particle
+ if ( m_spinTimer.IsElapsed() )
+ {
+ m_bSpinRight = RandomInt(0,1) == 1;
+ m_spinTimer.Start( RandomFloat( 0.75f, 1.25f ) );
+ DispatchParticleEffect( "rocketjump_smoke", PATTACH_POINT_FOLLOW, pMe, "wheel" );
+ }
+
+ return Continue();
+}
+
+EventDesiredResult< CTFRobotDestruction_Robot > CRobotPanic::OnInjured( CTFRobotDestruction_Robot *pMe, const CTakeDamageInfo &info )
+{
+ if ( m_SpeakTimer.IsElapsed() )
+ {
+ m_SpeakTimer.Start( RandomFloat( 1.5f, 2.f ) );
+ const RobotSpawnData_t & data = pMe->GetRobotSpawnData();
+ pMe->EmitSound( g_RobotData[ data.m_eType ]->GetStringData( RobotData_t::HURT_SOUND_KEY ) );
+ }
+
+ m_attackedTimer.Start( m_attackedTimer.GetCountdownDuration() );
+ return TryToSustain( RESULT_IMPORTANT, "I'm panicking and getting attacked" );
+}
+
+void CRobotPanic::OnEnd( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *nextAction )
+{
+ pMe->SetIsPanicked( false );
+ pMe->GetLocomotionInterface()->SetDesiredSpeed( 80.f );
+}
+
+
+//---------------------------------------------------------------------------------------------
+Action< CTFRobotDestruction_Robot > *CRobotBehavior::InitialContainedAction( CTFRobotDestruction_Robot *pMe )
+{
+ return new CRobotMaterialize;
+}
+
+ActionResult< CTFRobotDestruction_Robot > CRobotBehavior::OnStart( CTFRobotDestruction_Robot *pMe, Action< CTFRobotDestruction_Robot > *priorAction )
+{
+ return Continue();
+}
+
+#ifdef STAGING_ONLY
+ConVar sv_rd_bots_STFU( "sv_rd_bots_STFU", "0", FCVAR_ARCHIVE );
+#endif
+ActionResult< CTFRobotDestruction_Robot > CRobotBehavior::Update( CTFRobotDestruction_Robot *pMe, float interval )
+{
+ //const CKnownEntity *pThreat = pMe->GetVisionInterface()->GetPrimaryKnownThreat();
+ //if ( pThreat )
+ //{
+ // return SuspendFor( new CRobotAttackEnemy, "I see an enemy!" );
+ //}
+#ifdef STAGING_ONLY
+ if ( !sv_rd_bots_STFU.GetBool() )
+#endif
+ {
+ // We've been wandering for a bit. Speak!
+ if ( m_IdleSpeakTimer.IsElapsed() && m_SpeakTimer.IsElapsed() )
+ {
+ m_SpeakTimer.Start( 1.f );
+ m_IdleSpeakTimer.Start( RandomFloat( 6.f, 10.f ) );
+ const RobotSpawnData_t & data = pMe->GetRobotSpawnData();
+ pMe->EmitSound( g_RobotData[ data.m_eType ]->GetStringData( RobotData_t::IDLE_SOUND_KEY ) );
+ }
+ }
+
+ // Do stuff!
+ return Continue();
+}
+
+EventDesiredResult< CTFRobotDestruction_Robot > CRobotBehavior::OnInjured( CTFRobotDestruction_Robot *pMe, const CTakeDamageInfo &info )
+{
+ return TrySuspendFor( new CRobotEnterPanic, RESULT_TRY, "I've been attacked" );
+}
+
+
+EventDesiredResult< CTFRobotDestruction_Robot > CRobotBehavior::OnContact( CTFRobotDestruction_Robot *pMe, CBaseEntity *pOther, CGameTrace *result )
+{
+ if ( m_SpeakTimer.IsElapsed() && ( pOther->IsPlayer() || dynamic_cast< CTFRobotDestruction_Robot * >( pOther ) ) )
+ {
+ m_SpeakTimer.Start( 3.f );
+
+#ifdef STAGING_ONLY
+ if ( !sv_rd_bots_STFU.GetBool() )
+#endif
+ {
+ const RobotSpawnData_t & data = pMe->GetRobotSpawnData();
+ pMe->EmitSound( g_RobotData[ data.m_eType ]->GetStringData( RobotData_t::COLLIDE_SOUND_KEY ) );
+ }
+ }
+
+ return TryContinue( RESULT_TRY );
+}
+
+
+//---------------------------------------------------------------------------------------------
+CRobotIntention::CRobotIntention( CTFRobotDestruction_Robot *pMe ) : IIntention( pMe )
+{
+ m_behavior = new Behavior< CTFRobotDestruction_Robot >( new CRobotBehavior );
+}
+
+CRobotIntention::~CRobotIntention()
+{
+ delete m_behavior;
+}
+
+void CRobotIntention::Reset( void )
+{
+ delete m_behavior;
+ m_behavior = new Behavior< CTFRobotDestruction_Robot >( new CRobotBehavior );
+}
+
+void CRobotIntention::Update( void )
+{
+ m_behavior->Update( static_cast< CTFRobotDestruction_Robot * >( GetBot() ), GetUpdateInterval() );
+}
+
+// is this a place we can be?
+QueryResultType CRobotIntention::IsPositionAllowed( const INextBot *pMeBot, const Vector &pos ) const
+{
+ return ANSWER_YES;
+}
+
+
+
+//---------------------------------------------------------------------------------------------
+float CRobotLocomotion::GetRunSpeed( void ) const
+{
+ CTFRobotDestruction_Robot *pRobotMe = static_cast< CTFRobotDestruction_Robot *>( GetBot()->GetEntity() );
+ return pRobotMe->GetIsPanicked() ? 150.f : 80.f;
+}
+
+float CRobotLocomotion::GetGroundSpeed() const
+{
+ CTFRobotDestruction_Robot *pRobotMe = static_cast< CTFRobotDestruction_Robot *>( GetBot()->GetEntity() );
+ return pRobotMe->GetIsPanicked() ? 150.f : 80.f;
+}
+
+//---------------------------------------------------------------------------------------------
+// if delta Z is greater than this, we have to jump to get up
+float CRobotLocomotion::GetStepHeight( void ) const
+{
+ return 24.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// return maximum height of a jump
+float CRobotLocomotion::GetMaxJumpHeight( void ) const
+{
+ return 40.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+// Return max rate of yaw rotation
+float CRobotLocomotion::GetMaxYawRate( void ) const
+{
+ return 200.0f;
+}
+
+
+//---------------------------------------------------------------------------------------------
+bool CRobotLocomotion::ShouldCollideWith( const CBaseEntity *object ) const
+{
+ return false;
+}
+
+#endif
+
+//-----------------------------------------------------------------------------
+// Robot Dispenser
+//-----------------------------------------------------------------------------
+
+BEGIN_DATADESC( CRobotDispenser )
+END_DATADESC()
+
+IMPLEMENT_NETWORKCLASS_ALIASED( RobotDispenser, DT_RobotDispenser )
+LINK_ENTITY_TO_CLASS( rd_robot_dispenser, CRobotDispenser );
+
+BEGIN_NETWORK_TABLE( CRobotDispenser, DT_RobotDispenser )
+END_NETWORK_TABLE()
+
+#ifdef GAME_DLL
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CRobotDispenser::CRobotDispenser()
+{
+ m_bUseGenerateMetalSound = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRobotDispenser::Spawn( void )
+{
+ // This cast is for the benefit of GCC
+ m_fObjectFlags |= (int)OF_DOESNT_HAVE_A_MODEL;
+ m_takedamage = DAMAGE_NO;
+ m_iUpgradeLevel = 1;
+
+ TFGameRules()->OnDispenserBuilt( this );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Finished building
+//-----------------------------------------------------------------------------
+void CRobotDispenser::OnGoActive( void )
+{
+ BaseClass::OnGoActive();
+
+ if ( m_hTouchTrigger )
+ {
+ m_hTouchTrigger->SetParent( GetParent() );
+ }
+
+ SetModel( "" );
+}
+
+//-----------------------------------------------------------------------------
+// Spawn the vgui control screens on the object
+//-----------------------------------------------------------------------------
+void CRobotDispenser::GetControlPanelInfo( int nPanelIndex, const char *&pPanelName )
+{
+ // no panels
+ return;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CRobotDispenser::SetModel( const char *pModel )
+{
+ CBaseObject::SetModel( pModel );
+}
+
+
+#endif