source-engine/game/server/tf2/npc_bug_builder.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

578 lines
15 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The builder bug
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "AI_Task.h"
#include "AI_Default.h"
#include "AI_Schedule.h"
#include "AI_Hull.h"
#include "AI_Hint.h"
#include "AI_Navigator.h"
#include "activitylist.h"
#include "soundent.h"
#include "game.h"
#include "NPCEvent.h"
#include "tf_player.h"
#include "EntityList.h"
#include "ndebugoverlay.h"
#include "shake.h"
#include "monstermaker.h"
#include "decals.h"
#include "vstdlib/random.h"
#include "tf_obj.h"
#include "engine/IEngineSound.h"
#include "IEffects.h"
#include "npc_bug_builder.h"
#include "npc_bug_hole.h"
ConVar npc_bug_builder_health( "npc_bug_builder_health", "100" );
BEGIN_DATADESC( CNPC_Bug_Builder )
DEFINE_FIELD( m_flIdleDelay, FIELD_FLOAT ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( npc_bug_builder, CNPC_Bug_Builder );
IMPLEMENT_CUSTOM_AI( npc_bug_builder, CNPC_Bug_Builder );
// Dawdling details
// Max & Min distances for dawdle forward movement
#define DAWDLE_MIN_DIST 64
#define DAWDLE_MAX_DIST 1024
//==================================================
// Bug Conditions
//==================================================
enum BugConditions
{
COND_BBUG_RETURN_TO_BUGHOLE = LAST_SHARED_CONDITION,
};
//==================================================
// Bug Schedules
//==================================================
enum BugSchedules
{
SCHED_BBUG_FLEE_ENEMY = LAST_SHARED_SCHEDULE,
SCHED_BBUG_RETURN_TO_BUGHOLE,
SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
SCHED_BBUG_DAWDLE,
};
//==================================================
// Bug Tasks
//==================================================
enum BugTasks
{
TASK_BBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK,
TASK_BBUG_GET_PATH_TO_BUGHOLE,
TASK_BBUG_HOLE_REMOVE,
TASK_BBUG_GET_PATH_TO_DAWDLE,
TASK_BBUG_FACE_DAWDLE,
};
//==================================================
// Bug Activities
//==================================================
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNPC_Bug_Builder::CNPC_Bug_Builder( void )
{
m_flFieldOfView = 0.5f;
m_flIdleDelay = 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose: Setup our schedules and tasks, etc.
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::InitCustomSchedules( void )
{
INIT_CUSTOM_AI( CNPC_Bug_Builder );
// Schedules
ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY );
ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE );
ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE );
// Conditions
ADD_CUSTOM_CONDITION( CNPC_Bug_Builder, COND_BBUG_RETURN_TO_BUGHOLE );
// Tasks
ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_FLEE );
ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_BUGHOLE );
ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_HOLE_REMOVE );
ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_GET_PATH_TO_DAWDLE );
ADD_CUSTOM_TASK( CNPC_Bug_Builder, TASK_BBUG_FACE_DAWDLE );
// Activities
//ADD_CUSTOM_ACTIVITY( CNPC_Bug_Builder, ACT_BUG_WARRIOR_DISTRACT );
AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY );
AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE );
AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::Spawn( void )
{
Precache();
SetModel( BUG_BUILDER_MODEL );
SetHullType(HULL_TINY);
SetHullSizeNormal();
SetDefaultEyeOffset();
SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 ); // See from my center
SetDistLook( 1024.0 );
m_flNextDawdle = 0;
SetNavType(NAV_GROUND);
m_NPCState = NPC_STATE_NONE;
SetBloodColor( BLOOD_COLOR_YELLOW );
m_iHealth = npc_bug_builder_health.GetFloat();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
NPCInit();
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::Precache( void )
{
PrecacheModel( BUG_BUILDER_MODEL );
PrecacheScriptSound( "NPC_Bug_Builder.Idle" );
PrecacheScriptSound( "NPC_Bug_Builder.Pain" );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
int CNPC_Bug_Builder::SelectSchedule( void )
{
// If I'm not in idle anymore, don't idle
if ( m_NPCState != NPC_STATE_IDLE )
{
m_flNextDawdle = 0;
}
switch ( m_NPCState )
{
case NPC_STATE_IDLE:
{
// BugHole might be requesting help
if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) )
return SCHED_BBUG_RETURN_TO_BUGHOLE;
// Setup to dawdle a bit from now
if ( !m_flNextDawdle )
{
m_flNextDawdle = gpGlobals->curtime + random->RandomFloat( 3.0, 5.0 );
}
else if ( m_flNextDawdle < gpGlobals->curtime )
{
m_flNextDawdle = 0;
return SCHED_BBUG_DAWDLE;
}
// When I take damage, I flee
if ( HasCondition( COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE ) )
return SCHED_BBUG_FLEE_ENEMY;
// Return to my bughole
//return SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE;
break;
}
case NPC_STATE_ALERT:
{
// BugHole might be requesting help
if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) )
return SCHED_BBUG_RETURN_TO_BUGHOLE;
// When I take damage, I flee
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
return SCHED_BBUG_FLEE_ENEMY;
break;
}
case NPC_STATE_COMBAT:
{
// Did I lose my enemy?
if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) )
{
SetEnemy( NULL );
SetState(NPC_STATE_IDLE);
return BaseClass::SelectSchedule();
}
// When I take damage, I flee
if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
return SCHED_BBUG_FLEE_ENEMY;
}
break;
}
return BaseClass::SelectSchedule();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTask -
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_BBUG_GET_PATH_TO_FLEE:
{
// Always tell our bughole that we're under attack
if ( m_hMyBugHole )
{
m_hMyBugHole->IncomingFleeingBug( this );
}
// If we have no squad, or we couldn't get a path to our squadmate, move to our bughole
if ( m_hMyBugHole )
{
SetTarget( m_hMyBugHole );
AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
if ( GetNavigator()->SetGoal( goal ) )
{
TaskComplete();
return;
}
}
TaskComplete();
}
break;
case TASK_BBUG_GET_PATH_TO_BUGHOLE:
{
// Get a path back to my bughole
// If we have no squad, or we couldn't get a path to our squadmate, look for a bughole
if ( m_hMyBugHole )
{
SetTarget( m_hMyBugHole );
AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
if ( GetNavigator()->SetGoal( goal ) )
{
TaskComplete();
return;
}
}
TaskFail( "Couldn't get to bughole." );
}
break;
case TASK_BBUG_HOLE_REMOVE:
{
TaskComplete();
// Crawl inside the bughole and remove myself
AddEffects( EF_NODRAW );
AddSolidFlags( FSOLID_NOT_SOLID );
Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) );
// Tell the bughole
if ( m_hMyBugHole )
{
m_hMyBugHole->BugReturned();
}
}
break;
case TASK_BBUG_GET_PATH_TO_DAWDLE:
{
// Get a dawdle point ahead of us
Vector vecForward, vecTarget;
AngleVectors( GetAbsAngles(), &vecForward );
VectorMA( GetAbsOrigin(), random->RandomFloat( DAWDLE_MIN_DIST, DAWDLE_MAX_DIST ), vecForward, vecTarget );
// See how far we could move ahead
trace_t tr;
UTIL_TraceEntity( this, GetAbsOrigin(), vecTarget, MASK_SOLID, &tr);
float flDistance = tr.fraction * (vecTarget - GetAbsOrigin()).Length();
if ( flDistance >= DAWDLE_MIN_DIST )
{
AI_NavGoal_t goal( tr.endpos );
GetNavigator()->SetGoal( goal );
}
TaskComplete();
}
break;
case TASK_BBUG_FACE_DAWDLE:
{
// Turn a random amount to the right
float flYaw = GetMotor()->GetIdealYaw();
flYaw = flYaw + random->RandomFloat( 45, 135 );
GetMotor()->SetIdealYaw( UTIL_AngleMod(flYaw) );
SetTurnActivity();
break;
}
break;
default:
BaseClass::StartTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTask -
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_BBUG_FACE_DAWDLE:
{
GetMotor()->UpdateYaw();
if ( FacingIdeal() )
{
TaskComplete();
}
break;
}
default:
BaseClass::RunTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_Bug_Builder::FValidateHintType(CAI_Hint *pHint)
{
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pVictim -
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::Event_Killed( const CTakeDamageInfo &info )
{
BaseClass::Event_Killed( info );
// Remove myself in a minute
if ( !ShouldFadeOnDeath() )
{
SetThink( SUB_Remove );
SetNextThink( gpGlobals->curtime + 20 );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pEvent -
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::HandleAnimEvent( animevent_t *pEvent )
{
BaseClass::HandleAnimEvent( pEvent );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CNPC_Bug_Builder::MaxYawSpeed( void )
{
return 2.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::IdleSound( void )
{
EmitSound( "NPC_Bug_Builder.Idle" );
m_flIdleDelay = gpGlobals->curtime + 4.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::PainSound( const CTakeDamageInfo &info )
{
EmitSound( "NPC_Bug_Builder.Pain" );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Bug_Builder::ShouldPlayIdleSound( void )
{
//Only do idles in the right states
if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) )
return false;
//Gagged monsters don't talk
if ( HasSpawnFlags( SF_NPC_GAG ) )
return false;
//Don't cut off another sound or play again too soon
if ( m_flIdleDelay > gpGlobals->curtime )
return false;
//Randomize it a bit
if ( random->RandomInt( 0, 20 ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::SetBugHole( CMaker_BugHole *pBugHole )
{
m_hMyBugHole = pBugHole;
}
//-----------------------------------------------------------------------------
// Purpose: BugHole is calling me home to defend it
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::ReturnToBugHole( void )
{
SetCondition( COND_BBUG_RETURN_TO_BUGHOLE );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::AlertSound( void )
{
if ( GetEnemy() )
{
//FIXME: We need a better solution for inner-squad alerts!!
//SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type.
CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this );
}
}
//-----------------------------------------------------------------------------
// Purpose: Overridden for team handling
//-----------------------------------------------------------------------------
Disposition_t CNPC_Bug_Builder::IRelationType( CBaseEntity *pTarget )
{
// Builders ignore everything
return D_NU;
}
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
//=========================================================
// Dawdle around
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_BBUG_DAWDLE,
" Tasks"
" TASK_SET_TOLERANCE_DISTANCE 32"
" TASK_BBUG_GET_PATH_TO_DAWDLE 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_BBUG_FACE_DAWDLE 0"
" "
" Interrupts"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
);
//=========================================================
// Flee from our enemy
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_BBUG_FLEE_ENEMY,
" Tasks"
" TASK_SET_TOLERANCE_DISTANCE 128"
" TASK_BBUG_GET_PATH_TO_FLEE 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_TURN_RIGHT 180"
" "
" Interrupts"
" COND_ENEMY_DEAD"
" COND_LOST_ENEMY"
);
//=========================================================
// Retreat to a bughole
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_BBUG_RETURN_TO_BUGHOLE,
" Tasks"
" TASK_SET_TOLERANCE_DISTANCE 128"
" TASK_BBUG_GET_PATH_TO_BUGHOLE 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" "
" Interrupts"
" COND_NEW_ENEMY"
" COND_HEAR_COMBAT"
" COND_HEAR_DANGER"
);
//=========================================================
// Return to a bughole and remove myself
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
" Tasks"
" TASK_WAIT 5" // Wait for 5-10 seconds to see if anything happens
" TASK_WAIT_RANDOM 5"
" TASK_SET_TOLERANCE_DISTANCE 128"
" TASK_BBUG_GET_PATH_TO_BUGHOLE 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_BBUG_HOLE_REMOVE 0"
" "
" Interrupts"
" COND_NEW_ENEMY"
" COND_HEAR_COMBAT"
" COND_HEAR_DANGER"
);