2013-06-26 15:22:04 -07:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Advisors. Large sluglike aliens with creepy psychic powers!
//
//=============================================================================
# include "cbase.h"
# include "game.h"
# include "ai_basenpc.h"
# include "ai_schedule.h"
# include "ai_hull.h"
# include "ai_hint.h"
# include "ai_motor.h"
# include "ai_navigator.h"
# include "beam_shared.h"
# include "hl2_shareddefs.h"
# include "ai_route.h"
# include "npcevent.h"
# include "gib.h"
# include "ai_interactions.h"
# include "ndebugoverlay.h"
# include "physics_saverestore.h"
# include "saverestore_utlvector.h"
# include "soundent.h"
# include "vstdlib/random.h"
# include "engine/IEngineSound.h"
# include "movevars_shared.h"
# include "particle_parse.h"
# include "weapon_physcannon.h"
// #include "mathlib/noise.h"
// this file contains the definitions for the message ID constants (eg ADVISOR_MSG_START_BEAM etc)
# include "npc_advisor_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
//
// Custom activities.
//
//
// Skill settings.
//
ConVar sk_advisor_health ( " sk_advisor_health " , " 0 " ) ;
ConVar advisor_use_impact_table ( " advisor_use_impact_table " , " 1 " , FCVAR_NONE , " If true, advisor will use her custom impact damage table. " ) ;
# if NPC_ADVISOR_HAS_BEHAVIOR
ConVar advisor_throw_velocity ( " advisor_throw_velocity " , " 1100 " ) ;
ConVar advisor_throw_rate ( " advisor_throw_rate " , " 4 " ) ; // Throw an object every 4 seconds.
ConVar advisor_throw_warn_time ( " advisor_throw_warn_time " , " 1.0 " ) ; // Warn players one second before throwing an object.
ConVar advisor_throw_lead_prefetch_time ( " advisor_throw_lead_prefetch_time " , " 0.66 " , FCVAR_NONE , " Save off the player's velocity this many seconds before throwing. " ) ;
ConVar advisor_throw_stage_distance ( " advisor_throw_stage_distance " , " 180.0 " , FCVAR_NONE , " Advisor will try to hold an object this far in front of him just before throwing it at you. Small values will clobber the shield and be very bad. " ) ;
// ConVar advisor_staging_num("advisor_staging_num","1",FCVAR_NONE,"Advisor will queue up this many objects to throw at Gordon.");
ConVar advisor_throw_clearout_vel ( " advisor_throw_clearout_vel " , " 200 " , FCVAR_NONE , " TEMP: velocity with which advisor clears things out of a throwable's way " ) ;
// ConVar advisor_staging_duration("
// how long it will take an object to get hauled to the staging point
# define STAGING_OBJECT_FALLOFF_TIME 0.15f
# endif
//
// Spawnflags.
//
//
// Animation events.
//
# if NPC_ADVISOR_HAS_BEHAVIOR
//
// Custom schedules.
//
enum
{
SCHED_ADVISOR_COMBAT = LAST_SHARED_SCHEDULE ,
SCHED_ADVISOR_IDLE_STAND ,
SCHED_ADVISOR_TOSS_PLAYER
} ;
//
// Custom tasks.
//
enum
{
TASK_ADVISOR_FIND_OBJECTS = LAST_SHARED_TASK ,
TASK_ADVISOR_LEVITATE_OBJECTS ,
TASK_ADVISOR_STAGE_OBJECTS ,
TASK_ADVISOR_BARRAGE_OBJECTS ,
TASK_ADVISOR_PIN_PLAYER ,
} ;
//
// Custom conditions.
//
enum
{
COND_ADVISOR_PHASE_INTERRUPT = LAST_SHARED_CONDITION ,
} ;
# endif
class CNPC_Advisor ;
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
class CAdvisorLevitate : public IMotionEvent
{
DECLARE_SIMPLE_DATADESC ( ) ;
public :
// in the absence of goal entities, we float up before throwing and down after
inline bool OldStyle ( void )
{
return ! ( m_vecGoalPos1 . IsValid ( ) & & m_vecGoalPos2 . IsValid ( ) ) ;
}
virtual simresult_e Simulate ( IPhysicsMotionController * pController , IPhysicsObject * pObject , float deltaTime , Vector & linear , AngularImpulse & angular ) ;
EHANDLE m_Advisor ; ///< handle to the advisor.
Vector m_vecGoalPos1 ;
Vector m_vecGoalPos2 ;
float m_flFloat ;
} ;
BEGIN_SIMPLE_DATADESC ( CAdvisorLevitate )
DEFINE_FIELD ( m_flFloat , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_vecGoalPos1 , FIELD_POSITION_VECTOR ) ,
DEFINE_FIELD ( m_vecGoalPos2 , FIELD_POSITION_VECTOR ) ,
DEFINE_FIELD ( m_Advisor , FIELD_EHANDLE ) ,
END_DATADESC ( )
//-----------------------------------------------------------------------------
// The advisor class.
//-----------------------------------------------------------------------------
class CNPC_Advisor : public CAI_BaseNPC
{
DECLARE_CLASS ( CNPC_Advisor , CAI_BaseNPC ) ;
# if NPC_ADVISOR_HAS_BEHAVIOR
DECLARE_SERVERCLASS ( ) ;
# endif
public :
//
// CBaseEntity:
//
virtual void Activate ( ) ;
virtual void Spawn ( ) ;
virtual void Precache ( ) ;
virtual void OnRestore ( ) ;
virtual void UpdateOnRemove ( ) ;
virtual int DrawDebugTextOverlays ( ) ;
//
// CAI_BaseNPC:
//
virtual float MaxYawSpeed ( ) { return 120.0f ; }
virtual Class_T Classify ( ) ;
# if NPC_ADVISOR_HAS_BEHAVIOR
virtual int GetSoundInterests ( ) ;
virtual int SelectSchedule ( ) ;
virtual void StartTask ( const Task_t * pTask ) ;
virtual void RunTask ( const Task_t * pTask ) ;
virtual void OnScheduleChange ( void ) ;
# endif
virtual void PainSound ( const CTakeDamageInfo & info ) ;
virtual void DeathSound ( const CTakeDamageInfo & info ) ;
virtual void IdleSound ( ) ;
virtual void AlertSound ( ) ;
# if NPC_ADVISOR_HAS_BEHAVIOR
virtual bool QueryHearSound ( CSound * pSound ) ;
virtual void GatherConditions ( void ) ;
/// true iff I recently threw the given object (not so fast)
bool DidThrow ( const CBaseEntity * pEnt ) ;
# else
inline bool DidThrow ( const CBaseEntity * pEnt ) { return false ; }
# endif
virtual bool IsHeavyDamage ( const CTakeDamageInfo & info ) ;
virtual int OnTakeDamage ( const CTakeDamageInfo & info ) ;
virtual const impactdamagetable_t & GetPhysicsImpactDamageTable ( void ) ;
COutputInt m_OnHealthIsNow ;
# if NPC_ADVISOR_HAS_BEHAVIOR
DEFINE_CUSTOM_AI ;
void InputSetThrowRate ( inputdata_t & inputdata ) ;
void InputWrenchImmediate ( inputdata_t & inputdata ) ; ///< immediately wrench an object into the air
void InputSetStagingNum ( inputdata_t & inputdata ) ;
void InputPinPlayer ( inputdata_t & inputdata ) ;
void InputTurnBeamOn ( inputdata_t & inputdata ) ;
void InputTurnBeamOff ( inputdata_t & inputdata ) ;
void InputElightOn ( inputdata_t & inputdata ) ;
void InputElightOff ( inputdata_t & inputdata ) ;
COutputEvent m_OnPickingThrowable , m_OnThrowWarn , m_OnThrow ;
enum { kMaxThrownObjectsTracked = 4 } ;
# endif
DECLARE_DATADESC ( ) ;
protected :
# if NPC_ADVISOR_HAS_BEHAVIOR
Vector GetThrowFromPos ( CBaseEntity * pEnt ) ; ///< Get the position in which we shall hold an object prior to throwing it
# endif
bool CanLevitateEntity ( CBaseEntity * pEntity , int minMass , int maxMass ) ;
void StartLevitatingObjects ( void ) ;
# if NPC_ADVISOR_HAS_BEHAVIOR
// void PurgeThrownObjects(); ///< clean out the recently thrown objects array
void AddToThrownObjects ( CBaseEntity * pEnt ) ; ///< add to the recently thrown objects array
void HurlObjectAtPlayer ( CBaseEntity * pEnt , const Vector & leadVel ) ;
void PullObjectToStaging ( CBaseEntity * pEnt , const Vector & stagingPos ) ;
CBaseEntity * ThrowObjectPrepare ( void ) ;
CBaseEntity * PickThrowable ( bool bRequireInView ) ; ///< choose an object to throw at the player (so it can get stuffed in the handle array)
/// push everything out of the way between an object I'm about to throw and the player.
void PreHurlClearTheWay ( CBaseEntity * pThrowable , const Vector & toPos ) ;
# endif
CUtlVector < EHANDLE > m_physicsObjects ;
IPhysicsMotionController * m_pLevitateController ;
CAdvisorLevitate m_levitateCallback ;
EHANDLE m_hLevitateGoal1 ;
EHANDLE m_hLevitateGoal2 ;
EHANDLE m_hLevitationArea ;
# if NPC_ADVISOR_HAS_BEHAVIOR
// EHANDLE m_hThrowEnt;
CUtlVector < EHANDLE > m_hvStagedEnts ;
CUtlVector < EHANDLE > m_hvStagingPositions ;
// todo: write accessor functions for m_hvStagedEnts so that it doesn't have members added and removed willy nilly throughout
// code (will make the networking below more reliable)
void Write_BeamOn ( CBaseEntity * pEnt ) ; ///< write a message turning a beam on
void Write_BeamOff ( CBaseEntity * pEnt ) ; ///< write a message turning a beam off
void Write_AllBeamsOff ( void ) ; ///< tell client to kill all beams
// for the pin-the-player-to-something behavior
EHANDLE m_hPlayerPinPos ;
float m_playerPinFailsafeTime ;
// keep track of up to four objects after we have thrown them, to prevent oscillation or levitation of recently thrown ammo.
EHANDLE m_haRecentlyThrownObjects [ kMaxThrownObjectsTracked ] ;
float m_flaRecentlyThrownObjectTimes [ kMaxThrownObjectsTracked ] ;
# endif
string_t m_iszLevitateGoal1 ;
string_t m_iszLevitateGoal2 ;
string_t m_iszLevitationArea ;
# if NPC_ADVISOR_HAS_BEHAVIOR
string_t m_iszStagingEntities ;
string_t m_iszPriorityEntityGroupName ;
float m_flStagingEnd ;
float m_flThrowPhysicsTime ;
float m_flLastThrowTime ;
float m_flLastPlayerAttackTime ; ///< last time the player attacked something.
int m_iStagingNum ; ///< number of objects advisor stages at once
bool m_bWasScripting ;
// unsigned char m_pickFailures; // the number of times we have tried to pick a throwable and failed
Vector m_vSavedLeadVel ; ///< save off player velocity for leading a bit before actually pelting them.
# endif
} ;
LINK_ENTITY_TO_CLASS ( npc_advisor , CNPC_Advisor ) ;
BEGIN_DATADESC ( CNPC_Advisor )
DEFINE_KEYFIELD ( m_iszLevitateGoal1 , FIELD_STRING , " levitategoal_bottom " ) ,
DEFINE_KEYFIELD ( m_iszLevitateGoal2 , FIELD_STRING , " levitategoal_top " ) ,
DEFINE_KEYFIELD ( m_iszLevitationArea , FIELD_STRING , " levitationarea " ) , ///< we will float all the objects in this volume
DEFINE_PHYSPTR ( m_pLevitateController ) ,
DEFINE_EMBEDDED ( m_levitateCallback ) ,
DEFINE_UTLVECTOR ( m_physicsObjects , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hLevitateGoal1 , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hLevitateGoal2 , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hLevitationArea , FIELD_EHANDLE ) ,
# if NPC_ADVISOR_HAS_BEHAVIOR
DEFINE_KEYFIELD ( m_iszStagingEntities , FIELD_STRING , " staging_ent_names " ) , ///< entities named this constitute the positions to which we stage objects to be thrown
DEFINE_KEYFIELD ( m_iszPriorityEntityGroupName , FIELD_STRING , " priority_grab_name " ) ,
DEFINE_UTLVECTOR ( m_hvStagedEnts , FIELD_EHANDLE ) ,
DEFINE_UTLVECTOR ( m_hvStagingPositions , FIELD_EHANDLE ) ,
DEFINE_ARRAY ( m_haRecentlyThrownObjects , FIELD_EHANDLE , CNPC_Advisor : : kMaxThrownObjectsTracked ) ,
DEFINE_ARRAY ( m_flaRecentlyThrownObjectTimes , FIELD_TIME , CNPC_Advisor : : kMaxThrownObjectsTracked ) ,
DEFINE_FIELD ( m_hPlayerPinPos , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_playerPinFailsafeTime , FIELD_TIME ) ,
// DEFINE_FIELD( m_hThrowEnt, FIELD_EHANDLE ),
DEFINE_FIELD ( m_flThrowPhysicsTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_flLastPlayerAttackTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_flStagingEnd , FIELD_TIME ) ,
DEFINE_FIELD ( m_iStagingNum , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_bWasScripting , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_flLastThrowTime , FIELD_TIME ) ,
DEFINE_FIELD ( m_vSavedLeadVel , FIELD_VECTOR ) ,
DEFINE_OUTPUT ( m_OnPickingThrowable , " OnPickingThrowable " ) ,
DEFINE_OUTPUT ( m_OnThrowWarn , " OnThrowWarn " ) ,
DEFINE_OUTPUT ( m_OnThrow , " OnThrow " ) ,
DEFINE_OUTPUT ( m_OnHealthIsNow , " OnHealthIsNow " ) ,
DEFINE_INPUTFUNC ( FIELD_FLOAT , " SetThrowRate " , InputSetThrowRate ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " WrenchImmediate " , InputWrenchImmediate ) ,
DEFINE_INPUTFUNC ( FIELD_INTEGER , " SetStagingNum " , InputSetStagingNum ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " PinPlayer " , InputPinPlayer ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " BeamOn " , InputTurnBeamOn ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " BeamOff " , InputTurnBeamOff ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " ElightOn " , InputElightOn ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " ElightOff " , InputElightOff ) ,
# endif
END_DATADESC ( )
# if NPC_ADVISOR_HAS_BEHAVIOR
IMPLEMENT_SERVERCLASS_ST ( CNPC_Advisor , DT_NPC_Advisor )
END_SEND_TABLE ( )
# endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : Spawn ( )
{
BaseClass : : Spawn ( ) ;
# ifdef _XBOX
// Always fade the corpse
AddSpawnFlags ( SF_NPC_FADE_CORPSE ) ;
# endif // _XBOX
Precache ( ) ;
SetModel ( STRING ( GetModelName ( ) ) ) ;
m_iHealth = sk_advisor_health . GetFloat ( ) ;
m_takedamage = DAMAGE_NO ;
SetHullType ( HULL_LARGE_CENTERED ) ;
SetHullSizeNormal ( ) ;
SetSolid ( SOLID_BBOX ) ;
// AddSolidFlags( FSOLID_NOT_SOLID );
SetMoveType ( MOVETYPE_FLY ) ;
m_flFieldOfView = VIEW_FIELD_FULL ;
SetViewOffset ( Vector ( 0 , 0 , 80 ) ) ; // Position of the eyes relative to NPC's origin.
SetBloodColor ( BLOOD_COLOR_GREEN ) ;
m_NPCState = NPC_STATE_NONE ;
CapabilitiesClear ( ) ;
NPCInit ( ) ;
SetGoalEnt ( NULL ) ;
AddEFlags ( EFL_NO_DISSOLVE ) ;
}
# if NPC_ADVISOR_HAS_BEHAVIOR
//-----------------------------------------------------------------------------
// comparison function for qsort used below. Compares "StagingPriority" keyfield
//-----------------------------------------------------------------------------
int __cdecl AdvisorStagingComparator ( const EHANDLE * pe1 , const EHANDLE * pe2 )
{
// bool ReadKeyField( const char *varName, variant_t *var );
variant_t var ;
int val1 = 10 , val2 = 10 ; // default priority is ten
// read field one
if ( pe1 - > Get ( ) - > ReadKeyField ( " StagingPriority " , & var ) )
{
val1 = var . Int ( ) ;
}
// read field two
if ( pe2 - > Get ( ) - > ReadKeyField ( " StagingPriority " , & var ) )
{
val2 = var . Int ( ) ;
}
// return comparison (< 0 if pe1<pe2)
return ( val1 - val2 ) ;
}
# endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
2013-10-06 13:46:15 -04:00
# ifdef _WIN32
2013-06-26 15:22:04 -07:00
# pragma warning(push)
# pragma warning(disable : 4706)
2013-10-06 13:46:15 -04:00
# endif
2013-06-26 15:22:04 -07:00
void CNPC_Advisor : : Activate ( )
{
BaseClass : : Activate ( ) ;
m_hLevitateGoal1 = gEntList . FindEntityGeneric ( NULL , STRING ( m_iszLevitateGoal1 ) , this ) ;
m_hLevitateGoal2 = gEntList . FindEntityGeneric ( NULL , STRING ( m_iszLevitateGoal2 ) , this ) ;
m_hLevitationArea = gEntList . FindEntityGeneric ( NULL , STRING ( m_iszLevitationArea ) , this ) ;
m_levitateCallback . m_Advisor = this ;
# if NPC_ADVISOR_HAS_BEHAVIOR
// load the staging positions
CBaseEntity * pEnt = NULL ;
m_hvStagingPositions . EnsureCapacity ( 6 ) ; // reserve six
// conditional assignment: find an entity by name and save it into pEnt. Bail out when none are left.
while ( pEnt = gEntList . FindEntityByName ( pEnt , m_iszStagingEntities ) )
{
m_hvStagingPositions . AddToTail ( pEnt ) ;
}
// sort the staging positions by their staging number.
m_hvStagingPositions . Sort ( AdvisorStagingComparator ) ;
// positions loaded, null out the m_hvStagedEnts array with exactly as many null spaces
m_hvStagedEnts . SetCount ( m_hvStagingPositions . Count ( ) ) ;
m_iStagingNum = 1 ;
AssertMsg ( m_hvStagingPositions . Count ( ) > 0 , " You did not specify any staging positions in the advisor's staging_ent_names ! " ) ;
# endif
}
2013-10-06 13:46:15 -04:00
# ifdef _WIN32
2013-06-26 15:22:04 -07:00
# pragma warning(pop)
2013-10-06 13:46:15 -04:00
# endif
2013-06-26 15:22:04 -07:00
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : UpdateOnRemove ( )
{
if ( m_pLevitateController )
{
physenv - > DestroyMotionController ( m_pLevitateController ) ;
}
BaseClass : : UpdateOnRemove ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : OnRestore ( )
{
BaseClass : : OnRestore ( ) ;
StartLevitatingObjects ( ) ;
}
//-----------------------------------------------------------------------------
// Returns this monster's classification in the relationship table.
//-----------------------------------------------------------------------------
Class_T CNPC_Advisor : : Classify ( )
{
return CLASS_COMBINE ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_Advisor : : IsHeavyDamage ( const CTakeDamageInfo & info )
{
return ( info . GetDamage ( ) > 0 ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : StartLevitatingObjects ( )
{
if ( ! m_pLevitateController )
{
m_pLevitateController = physenv - > CreateMotionController ( & m_levitateCallback ) ;
}
m_pLevitateController - > ClearObjects ( ) ;
int nCount = m_physicsObjects . Count ( ) ;
for ( int i = 0 ; i < nCount ; i + + )
{
CBaseEntity * pEnt = m_physicsObjects . Element ( i ) ;
if ( ! pEnt )
continue ;
//NDebugOverlay::Box( pEnt->GetAbsOrigin(), pEnt->CollisionProp()->OBBMins(), pEnt->CollisionProp()->OBBMaxs(), 0, 255, 0, 1, 0.1 );
IPhysicsObject * pPhys = pEnt - > VPhysicsGetObject ( ) ;
if ( pPhys & & pPhys - > IsMoveable ( ) )
{
m_pLevitateController - > AttachObject ( pPhys , false ) ;
pPhys - > Wake ( ) ;
}
}
}
// This function is used by both version of the entity finder below
bool CNPC_Advisor : : CanLevitateEntity ( CBaseEntity * pEntity , int minMass , int maxMass )
{
if ( ! pEntity | | pEntity - > IsNPC ( ) )
return false ;
IPhysicsObject * pPhys = pEntity - > VPhysicsGetObject ( ) ;
if ( ! pPhys )
return false ;
float mass = pPhys - > GetMass ( ) ;
return ( mass > = minMass & &
mass < = maxMass & &
//pEntity->VPhysicsGetObject()->IsAsleep() &&
pPhys - > IsMoveable ( ) /* &&
! DidThrow ( pEntity ) */ ) ;
}
# if NPC_ADVISOR_HAS_BEHAVIOR
// find an object to throw at the player and start the warning on it. Return object's
// pointer if we got something. Otherwise, return NULL if nothing left to throw. Will
// always leave the prepared object at the head of m_hvStagedEnts
CBaseEntity * CNPC_Advisor : : ThrowObjectPrepare ( )
{
CBaseEntity * pThrowable = NULL ;
while ( m_hvStagedEnts . Count ( ) > 0 )
{
pThrowable = m_hvStagedEnts [ 0 ] ;
if ( pThrowable )
{
IPhysicsObject * pPhys = pThrowable - > VPhysicsGetObject ( ) ;
if ( ! pPhys )
{
// reject!
Write_BeamOff ( m_hvStagedEnts [ 0 ] ) ;
pThrowable = NULL ;
}
}
// if we still have pThrowable...
if ( pThrowable )
{
// we're good
break ;
}
else
{
m_hvStagedEnts . Remove ( 0 ) ;
}
}
if ( pThrowable )
{
Assert ( pThrowable - > VPhysicsGetObject ( ) ) ;
// play the sound, attach the light, fire the trigger
EmitSound ( " NPC_Advisor.ObjectChargeUp " ) ;
m_OnThrowWarn . FireOutput ( pThrowable , this ) ;
m_flThrowPhysicsTime = gpGlobals - > curtime + advisor_throw_warn_time . GetFloat ( ) ;
if ( GetEnemy ( ) )
{
PreHurlClearTheWay ( pThrowable , GetEnemy ( ) - > EyePosition ( ) ) ;
}
return pThrowable ;
}
else // we had nothing to throw
{
return NULL ;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : StartTask ( const Task_t * pTask )
{
switch ( pTask - > iTask )
{
// DVS: TODO: if this gets expensive we can start caching the results and doing it less often.
case TASK_ADVISOR_FIND_OBJECTS :
{
// if we have a trigger volume, use the contents of that. If not, use a hardcoded box (for debugging purposes)
// in both cases we validate the objects using the same helper funclet just above. When we can count on the
// trigger vol being there, we can elide the else{} clause here.
CBaseEntity * pVolume = m_hLevitationArea ;
AssertMsg ( pVolume , " Combine advisor needs 'levitationarea' key pointing to a trigger volume. " ) ;
if ( ! pVolume )
{
TaskFail ( " No levitation area found! " ) ;
break ;
}
touchlink_t * touchroot = ( touchlink_t * ) pVolume - > GetDataObject ( TOUCHLINK ) ;
if ( touchroot )
{
m_physicsObjects . RemoveAll ( ) ;
for ( touchlink_t * link = touchroot - > nextLink ; link ! = touchroot ; link = link - > nextLink )
{
CBaseEntity * pTouch = link - > entityTouched ;
if ( CanLevitateEntity ( pTouch , 10 , 220 ) )
{
if ( pTouch - > GetMoveType ( ) = = MOVETYPE_VPHYSICS )
{
//Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
m_physicsObjects . AddToTail ( pTouch ) ;
}
}
}
}
/*
// this is the old mechanism, using a hardcoded box and an entity enumerator.
// since deprecated.
else
{
CBaseEntity * list [ 128 ] ;
m_physicsObjects . RemoveAll ( ) ;
//NDebugOverlay::Box( GetAbsOrigin(), Vector( -408, -368, -188 ), Vector( 92, 208, 168 ), 255, 255, 0, 1, 5 );
// one-off class used to determine which entities we want from the UTIL_EntitiesInBox
class CAdvisorLevitateEntitiesEnum : public CFlaggedEntitiesEnum
{
public :
CAdvisorLevitateEntitiesEnum ( CBaseEntity * * pList , int listMax , int nMinMass , int nMaxMass )
: CFlaggedEntitiesEnum ( pList , listMax , 0 ) ,
m_nMinMass ( nMinMass ) ,
m_nMaxMass ( nMaxMass )
{
}
virtual IterationRetval_t EnumElement ( IHandleEntity * pHandleEntity )
{
CBaseEntity * pEntity = gEntList . GetBaseEntity ( pHandleEntity - > GetRefEHandle ( ) ) ;
if ( AdvisorCanLevitateEntity ( pEntity , m_nMinMass , m_nMaxMass ) )
{
return CFlaggedEntitiesEnum : : EnumElement ( pHandleEntity ) ;
}
return ITERATION_CONTINUE ;
}
int m_nMinMass ;
int m_nMaxMass ;
} ;
CAdvisorLevitateEntitiesEnum levitateEnum ( list , ARRAYSIZE ( list ) , 10 , 220 ) ;
int nCount = UTIL_EntitiesInBox ( GetAbsOrigin ( ) - Vector ( 554 , 368 , 188 ) , GetAbsOrigin ( ) + Vector ( 92 , 208 , 168 ) , & levitateEnum ) ;
for ( int i = 0 ; i < nCount ; i + + )
{
//Msg( "%d found %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
if ( list [ i ] - > GetMoveType ( ) = = MOVETYPE_VPHYSICS )
{
//Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
m_physicsObjects . AddToTail ( list [ i ] ) ;
}
}
}
*/
if ( m_physicsObjects . Count ( ) > 0 )
{
TaskComplete ( ) ;
}
else
{
TaskFail ( " No physics objects found! " ) ;
}
break ;
}
case TASK_ADVISOR_LEVITATE_OBJECTS :
{
StartLevitatingObjects ( ) ;
m_flThrowPhysicsTime = gpGlobals - > curtime + advisor_throw_rate . GetFloat ( ) ;
break ;
}
case TASK_ADVISOR_STAGE_OBJECTS :
{
// m_pickFailures = 0;
// clear out previously staged throwables
/*
for ( int ii = m_hvStagedEnts . Count ( ) - 1 ; ii > = 0 ; - - ii )
{
m_hvStagedEnts [ ii ] = NULL ;
}
*/
Write_AllBeamsOff ( ) ;
m_hvStagedEnts . RemoveAll ( ) ;
m_OnPickingThrowable . FireOutput ( NULL , this ) ;
m_flStagingEnd = gpGlobals - > curtime + pTask - > flTaskData ;
break ;
}
// we're about to pelt the player with everything. Start the warning effect on the first object.
case TASK_ADVISOR_BARRAGE_OBJECTS :
{
CBaseEntity * pThrowable = ThrowObjectPrepare ( ) ;
if ( ! pThrowable | | m_hvStagedEnts . Count ( ) < 1 )
{
TaskFail ( " Nothing to throw! " ) ;
return ;
}
m_vSavedLeadVel . Invalidate ( ) ;
break ;
}
case TASK_ADVISOR_PIN_PLAYER :
{
// should never be here
/*
Assert ( m_hPlayerPinPos . IsValid ( ) ) ;
m_playerPinFailsafeTime = gpGlobals - > curtime + 10.0f ;
break ;
*/
}
default :
{
BaseClass : : StartTask ( pTask ) ;
}
}
}
//-----------------------------------------------------------------------------
// todo: find a way to guarantee that objects are made pickupable again when bailing out of a task
//-----------------------------------------------------------------------------
void CNPC_Advisor : : RunTask ( const Task_t * pTask )
{
switch ( pTask - > iTask )
{
// Raise up the objects that we found and then hold them.
case TASK_ADVISOR_LEVITATE_OBJECTS :
{
float flTimeToThrow = m_flThrowPhysicsTime - gpGlobals - > curtime ;
if ( flTimeToThrow < 0 )
{
TaskComplete ( ) ;
return ;
}
// set the top and bottom on the levitation volume from the entities. If we don't have
// both, zero it out so that we can use the old-style simpler mechanism.
if ( m_hLevitateGoal1 & & m_hLevitateGoal2 )
{
m_levitateCallback . m_vecGoalPos1 = m_hLevitateGoal1 - > GetAbsOrigin ( ) ;
m_levitateCallback . m_vecGoalPos2 = m_hLevitateGoal2 - > GetAbsOrigin ( ) ;
// swap them if necessary (1 must be the bottom)
if ( m_levitateCallback . m_vecGoalPos1 . z > m_levitateCallback . m_vecGoalPos2 . z )
{
swap ( m_levitateCallback . m_vecGoalPos1 , m_levitateCallback . m_vecGoalPos2 ) ;
}
m_levitateCallback . m_flFloat = 0.06f ; // this is an absolute accumulation upon gravity
}
else
{
m_levitateCallback . m_vecGoalPos1 . Invalidate ( ) ;
m_levitateCallback . m_vecGoalPos2 . Invalidate ( ) ;
// the below two stanzas are used for old-style floating, which is linked
// to float up before thrown and down after
if ( flTimeToThrow > 2.0f )
{
m_levitateCallback . m_flFloat = 1.06f ;
}
else
{
m_levitateCallback . m_flFloat = 0.94f ;
}
}
/*
// Draw boxes around the objects we're levitating.
for ( int i = 0 ; i < m_physicsObjects . Count ( ) ; i + + )
{
CBaseEntity * pEnt = m_physicsObjects . Element ( i ) ;
if ( ! pEnt )
continue ; // The prop has been broken!
IPhysicsObject * pPhys = pEnt - > VPhysicsGetObject ( ) ;
if ( pPhys & & pPhys - > IsMoveable ( ) )
{
NDebugOverlay : : Box ( pEnt - > GetAbsOrigin ( ) , pEnt - > CollisionProp ( ) - > OBBMins ( ) , pEnt - > CollisionProp ( ) - > OBBMaxs ( ) , 0 , 255 , 0 , 1 , 0.1 ) ;
}
} */
break ;
}
// Pick a random object that we are levitating. If we have a clear LOS from that object
// to our enemy's eyes, choose that one to throw. Otherwise, keep looking.
case TASK_ADVISOR_STAGE_OBJECTS :
{
if ( m_iStagingNum > m_hvStagingPositions . Count ( ) )
{
Warning ( " Advisor tries to stage %d objects but only has %d positions named %s! Overriding. \n " , m_iStagingNum , m_hvStagingPositions . Count ( ) , m_iszStagingEntities ) ;
m_iStagingNum = m_hvStagingPositions . Count ( ) ;
}
// advisor_staging_num
// in the future i'll distribute the staging chronologically. For now, yank all the objects at once.
if ( m_hvStagedEnts . Count ( ) < m_iStagingNum )
{
// pull another object
bool bDesperate = m_flStagingEnd - gpGlobals - > curtime < 0.50f ; // less than one half second left
CBaseEntity * pThrowable = PickThrowable ( ! bDesperate ) ;
if ( pThrowable )
{
// don't let the player take it from me
IPhysicsObject * pPhys = pThrowable - > VPhysicsGetObject ( ) ;
if ( pPhys )
{
// no pickup!
pPhys - > SetGameFlags ( pPhys - > GetGameFlags ( ) | FVPHYSICS_NO_PLAYER_PICKUP ) ; ;
}
m_hvStagedEnts . AddToTail ( pThrowable ) ;
Write_BeamOn ( pThrowable ) ;
DispatchParticleEffect ( " advisor_object_charge " , PATTACH_ABSORIGIN_FOLLOW ,
pThrowable , 0 ,
false ) ;
}
}
Assert ( m_hvStagedEnts . Count ( ) < = m_hvStagingPositions . Count ( ) ) ;
// yank all objects into place
for ( int ii = m_hvStagedEnts . Count ( ) - 1 ; ii > = 0 ; - - ii )
{
// just ignore lost objects (if the player destroys one, that's fine, leave a hole)
CBaseEntity * pThrowable = m_hvStagedEnts [ ii ] ;
if ( pThrowable )
{
PullObjectToStaging ( pThrowable , m_hvStagingPositions [ ii ] - > GetAbsOrigin ( ) ) ;
}
}
// are we done yet?
if ( gpGlobals - > curtime > m_flStagingEnd )
{
TaskComplete ( ) ;
break ;
}
break ;
}
// Fling the object that we picked at our enemy's eyes!
case TASK_ADVISOR_BARRAGE_OBJECTS :
{
Assert ( m_hvStagedEnts . Count ( ) > 0 ) ;
// do I still have an enemy?
if ( ! GetEnemy ( ) )
{
// no? bail all the objects.
for ( int ii = m_hvStagedEnts . Count ( ) - 1 ; ii > = 0 ; - - ii )
{
IPhysicsObject * pPhys = m_hvStagedEnts [ ii ] - > VPhysicsGetObject ( ) ;
if ( pPhys )
{
pPhys - > SetGameFlags ( pPhys - > GetGameFlags ( ) & ( ~ FVPHYSICS_NO_PLAYER_PICKUP ) ) ;
}
}
Write_AllBeamsOff ( ) ;
m_hvStagedEnts . RemoveAll ( ) ;
TaskFail ( " Lost enemy " ) ;
return ;
}
// do I still have something to throw at the player?
CBaseEntity * pThrowable = m_hvStagedEnts [ 0 ] ;
while ( ! pThrowable )
{ // player has destroyed whatever I planned to hit him with, get something else
if ( m_hvStagedEnts . Count ( ) > 0 )
{
pThrowable = ThrowObjectPrepare ( ) ;
}
else
{
TaskComplete ( ) ;
break ;
}
}
// If we've gone NULL, then opt out
if ( pThrowable = = NULL )
{
TaskComplete ( ) ;
break ;
}
if ( ( gpGlobals - > curtime > m_flThrowPhysicsTime - advisor_throw_lead_prefetch_time . GetFloat ( ) ) & &
! m_vSavedLeadVel . IsValid ( ) )
{
// save off the velocity we will use to lead the player a little early, so that if he jukes
// at the last moment he'll have a better shot of dodging the object.
m_vSavedLeadVel = GetEnemy ( ) - > GetAbsVelocity ( ) ;
}
// if it's time to throw something, throw it and go on to the next one.
if ( gpGlobals - > curtime > m_flThrowPhysicsTime )
{
IPhysicsObject * pPhys = pThrowable - > VPhysicsGetObject ( ) ;
Assert ( pPhys ) ;
pPhys - > SetGameFlags ( pPhys - > GetGameFlags ( ) & ( ~ FVPHYSICS_NO_PLAYER_PICKUP ) ) ;
HurlObjectAtPlayer ( pThrowable , Vector ( 0 , 0 , 0 ) /*m_vSavedLeadVel*/ ) ;
m_flLastThrowTime = gpGlobals - > curtime ;
m_flThrowPhysicsTime = gpGlobals - > curtime + 0.75f ;
// invalidate saved lead for next time
m_vSavedLeadVel . Invalidate ( ) ;
EmitSound ( " NPC_Advisor.Blast " ) ;
Write_BeamOff ( m_hvStagedEnts [ 0 ] ) ;
m_hvStagedEnts . Remove ( 0 ) ;
if ( ! ThrowObjectPrepare ( ) )
{
TaskComplete ( ) ;
break ;
}
}
else
{
// wait, bide time
// PullObjectToStaging(pThrowable, m_hvStagingPositions[ii]->GetAbsOrigin());
}
break ;
}
case TASK_ADVISOR_PIN_PLAYER :
{
/*
// bail out if the pin entity went away.
CBaseEntity * pPinEnt = m_hPlayerPinPos ;
if ( ! pPinEnt )
{
GetEnemy ( ) - > SetGravity ( 1.0f ) ;
GetEnemy ( ) - > SetMoveType ( MOVETYPE_WALK ) ;
TaskComplete ( ) ;
break ;
}
// failsafe: don't do this for more than ten seconds.
if ( gpGlobals - > curtime > m_playerPinFailsafeTime )
{
GetEnemy ( ) - > SetGravity ( 1.0f ) ;
GetEnemy ( ) - > SetMoveType ( MOVETYPE_WALK ) ;
Warning ( " Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe! \n " ) ;
TaskFail ( " Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe! \n " ) ;
break ;
}
// if the player isn't the enemy, bail out.
if ( ! GetEnemy ( ) - > IsPlayer ( ) )
{
GetEnemy ( ) - > SetGravity ( 1.0f ) ;
GetEnemy ( ) - > SetMoveType ( MOVETYPE_WALK ) ;
TaskFail ( " Player is not the enemy?! " ) ;
break ;
}
GetEnemy ( ) - > SetMoveType ( MOVETYPE_FLY ) ;
GetEnemy ( ) - > SetGravity ( 0 ) ;
// use exponential falloff to peg the player to the pin point
const Vector & desiredPos = pPinEnt - > GetAbsOrigin ( ) ;
const Vector & playerPos = GetEnemy ( ) - > GetAbsOrigin ( ) ;
Vector displacement = desiredPos - playerPos ;
float desiredDisplacementLen = ExponentialDecay ( 0.250f , gpGlobals - > frametime ) ; // * sqrt(displacementLen);
Vector nuPos = playerPos + ( displacement * ( 1.0f - desiredDisplacementLen ) ) ;
GetEnemy ( ) - > SetAbsOrigin ( nuPos ) ;
break ;
*/
}
default :
{
BaseClass : : RunTask ( pTask ) ;
}
}
}
# endif
// helper function for testing whether or not an avisor is allowed to grab an object
static bool AdvisorCanPickObject ( CBasePlayer * pPlayer , CBaseEntity * pEnt )
{
Assert ( pPlayer ! = NULL ) ;
// Is the player carrying something?
CBaseEntity * pHeldObject = GetPlayerHeldEntity ( pPlayer ) ;
if ( ! pHeldObject )
{
pHeldObject = PhysCannonGetHeldEntity ( pPlayer - > GetActiveWeapon ( ) ) ;
}
if ( pHeldObject = = pEnt )
{
return false ;
}
if ( pEnt - > GetCollisionGroup ( ) = = COLLISION_GROUP_DEBRIS )
{
return false ;
}
return true ;
}
# if NPC_ADVISOR_HAS_BEHAVIOR
//-----------------------------------------------------------------------------
// Choose an object to throw.
// param bRequireInView : if true, only accept objects that are in the player's fov.
//
// Can always return NULL.
// todo priority_grab_name
//-----------------------------------------------------------------------------
CBaseEntity * CNPC_Advisor : : PickThrowable ( bool bRequireInView )
{
CBasePlayer * pPlayer = ToBasePlayer ( GetEnemy ( ) ) ;
Assert ( pPlayer ) ;
if ( ! pPlayer )
return NULL ;
const int numObjs = m_physicsObjects . Count ( ) ; ///< total number of physics objects in my system
if ( numObjs < 1 )
return NULL ; // bail out if nothing available
// used for require-in-view
Vector eyeForward , eyeOrigin ;
if ( pPlayer )
{
eyeOrigin = pPlayer - > EyePosition ( ) ;
pPlayer - > EyeVectors ( & eyeForward ) ;
}
else
{
bRequireInView = false ;
}
// filter-and-choose algorithm:
// build a list of candidates
Assert ( numObjs < 128 ) ; /// I'll come back and utlvector this shortly -- wanted easier debugging
unsigned int candidates [ 128 ] ;
unsigned int numCandidates = 0 ;
if ( ! ! m_iszPriorityEntityGroupName ) // if the string isn't null
{
// first look to see if we have any priority objects.
for ( int ii = 0 ; ii < numObjs ; + + ii )
{
CBaseEntity * pThrowEnt = m_physicsObjects [ ii ] ;
// Assert(pThrowEnt);
if ( ! pThrowEnt )
continue ;
if ( ! pThrowEnt - > NameMatches ( m_iszPriorityEntityGroupName ) ) // if this is not a priority object
continue ;
bool bCanPick = AdvisorCanPickObject ( pPlayer , pThrowEnt ) & & ! m_hvStagedEnts . HasElement ( m_physicsObjects [ ii ] ) ;
if ( ! bCanPick )
continue ;
// bCanPick guaranteed true here
if ( bRequireInView )
{
bCanPick = ( pThrowEnt - > GetAbsOrigin ( ) - eyeOrigin ) . Dot ( eyeForward ) > 0 ;
}
if ( bCanPick )
{
candidates [ numCandidates + + ] = ii ;
}
}
}
// if we found no priority objects (or don't have a priority), just grab whatever
if ( numCandidates = = 0 )
{
for ( int ii = 0 ; ii < numObjs ; + + ii )
{
CBaseEntity * pThrowEnt = m_physicsObjects [ ii ] ;
// Assert(pThrowEnt);
if ( ! pThrowEnt )
continue ;
bool bCanPick = AdvisorCanPickObject ( pPlayer , pThrowEnt ) & & ! m_hvStagedEnts . HasElement ( m_physicsObjects [ ii ] ) ;
if ( ! bCanPick )
continue ;
// bCanPick guaranteed true here
if ( bRequireInView )
{
bCanPick = ( pThrowEnt - > GetAbsOrigin ( ) - eyeOrigin ) . Dot ( eyeForward ) > 0 ;
}
if ( bCanPick )
{
candidates [ numCandidates + + ] = ii ;
}
}
}
if ( numCandidates = = 0 )
return NULL ; // must have at least one candidate
// pick a random candidate.
int nRandomIndex = random - > RandomInt ( 0 , numCandidates - 1 ) ;
return m_physicsObjects [ candidates [ nRandomIndex ] ] ;
}
/*! \TODO
Correct bug where Advisor seemed to be throwing stuff at people ' s feet .
This is because the object was falling slightly in between the staging
and when he threw it , and that downward velocity was getting accumulated
into the throw speed . This is temporarily fixed here by using SetVelocity
instead of AddVelocity , but the proper fix is to pin the object to its
staging point during the warn period . That will require maintaining a map
of throwables to their staging points during the throw task .
*/
//-----------------------------------------------------------------------------
// Impart necessary force on any entity to make it clobber Gordon.
// Also detaches from levitate controller.
// The optional lead velocity parameter is for cases when we pre-save off the
// player's speed, to make last-moment juking more effective
//-----------------------------------------------------------------------------
void CNPC_Advisor : : HurlObjectAtPlayer ( CBaseEntity * pEnt , const Vector & leadVel )
{
IPhysicsObject * pPhys = pEnt - > VPhysicsGetObject ( ) ;
//
// Lead the target accurately. This encourages hiding behind cover
// and/or catching the thrown physics object!
//
Vector vecObjOrigin = pEnt - > CollisionProp ( ) - > WorldSpaceCenter ( ) ;
Vector vecEnemyPos = GetEnemy ( ) - > EyePosition ( ) ;
// disabled -- no longer compensate for gravity: // vecEnemyPos.y += 12.0f;
// const Vector &leadVel = pLeadVelocity ? *pLeadVelocity : GetEnemy()->GetAbsVelocity();
Vector vecDelta = vecEnemyPos - vecObjOrigin ;
float flDist = vecDelta . Length ( ) ;
float flVelocity = advisor_throw_velocity . GetFloat ( ) ;
if ( flVelocity = = 0 )
{
flVelocity = 1000 ;
}
float flFlightTime = flDist / flVelocity ;
Vector vecThrowAt = vecEnemyPos + flFlightTime * leadVel ;
Vector vecThrowDir = vecThrowAt - vecObjOrigin ;
VectorNormalize ( vecThrowDir ) ;
Vector vecVelocity = flVelocity * vecThrowDir ;
pPhys - > SetVelocity ( & vecVelocity , NULL ) ;
AddToThrownObjects ( pEnt ) ;
m_OnThrow . FireOutput ( pEnt , this ) ;
}
//-----------------------------------------------------------------------------
// do a sweep from an object I'm about to throw, to the target, pushing aside
// anything floating in the way.
// TODO: this is probably a good profiling candidate.
//-----------------------------------------------------------------------------
void CNPC_Advisor : : PreHurlClearTheWay ( CBaseEntity * pThrowable , const Vector & toPos )
{
// look for objects in the way of chucking.
CBaseEntity * list [ 128 ] ;
Ray_t ray ;
float boundingRadius = pThrowable - > BoundingRadius ( ) ;
ray . Init ( pThrowable - > GetAbsOrigin ( ) , toPos ,
Vector ( - boundingRadius , - boundingRadius , - boundingRadius ) ,
Vector ( boundingRadius , boundingRadius , boundingRadius ) ) ;
int nFoundCt = UTIL_EntitiesAlongRay ( list , 128 , ray , 0 ) ;
AssertMsg ( nFoundCt < 128 , " Found more than 128 obstructions between advisor and Gordon while throwing. (safe to continue) \n " ) ;
// for each thing in the way that I levitate, but is not something I'm staging
// or throwing, push it aside.
for ( int i = 0 ; i < nFoundCt ; + + i )
{
CBaseEntity * obstruction = list [ i ] ;
if ( obstruction ! = pThrowable & &
m_physicsObjects . HasElement ( obstruction ) & & // if it's floating
! m_hvStagedEnts . HasElement ( obstruction ) & & // and I'm not staging it
! DidThrow ( obstruction ) ) // and I didn't just throw it
{
IPhysicsObject * pPhys = obstruction - > VPhysicsGetObject ( ) ;
Assert ( pPhys ) ;
// this is an object we want to push out of the way. Compute a vector perpendicular
// to the path of the throwables's travel, and thrust the object along that vector.
Vector thrust ;
CalcClosestPointOnLine ( obstruction - > GetAbsOrigin ( ) ,
pThrowable - > GetAbsOrigin ( ) ,
toPos ,
thrust ) ;
// "thrust" is now the closest point on the line to the obstruction.
// compute the difference to get the direction of impulse
thrust = obstruction - > GetAbsOrigin ( ) - thrust ;
// and renormalize it to equal a giant kick out of the way
// (which I'll say is about ten feet per second -- if we want to be
// more precise we could do some kind of interpolation based on how
// far away the object is)
float thrustLen = thrust . Length ( ) ;
if ( thrustLen > 0.0001f )
{
thrust * = advisor_throw_clearout_vel . GetFloat ( ) / thrustLen ;
}
// heave!
pPhys - > AddVelocity ( & thrust , NULL ) ;
}
}
/*
// Otherwise only help out a little
Vector extents = Vector ( 256 , 256 , 256 ) ;
Ray_t ray ;
ray . Init ( vecStartPoint , vecStartPoint + 2048 * vecVelDir , - extents , extents ) ;
int nCount = UTIL_EntitiesAlongRay ( list , 1024 , ray , FL_NPC | FL_CLIENT ) ;
for ( int i = 0 ; i < nCount ; i + + )
{
if ( ! IsAttractiveTarget ( list [ i ] ) )
continue ;
VectorSubtract ( list [ i ] - > WorldSpaceCenter ( ) , vecStartPoint , vecDelta ) ;
distance = VectorNormalize ( vecDelta ) ;
flDot = DotProduct ( vecDelta , vecVelDir ) ;
if ( flDot > flMaxDot )
{
if ( distance < flBestDist )
{
pBestTarget = list [ i ] ;
flBestDist = distance ;
}
}
}
*/
}
/*
// commented out because unnecessary: we will do this during the DidThrow check
//-----------------------------------------------------------------------------
// clean out the recently thrown objects array
//-----------------------------------------------------------------------------
void CNPC_Advisor : : PurgeThrownObjects ( )
{
float threeSecondsAgo = gpGlobals - > curtime - 3.0f ; // two seconds ago
for ( int ii = 0 ; ii < kMaxThrownObjectsTracked ; + + ii )
{
if ( m_haRecentlyThrownObjects [ ii ] . IsValid ( ) & &
m_flaRecentlyThrownObjectTimes [ ii ] < threeSecondsAgo )
{
m_haRecentlyThrownObjects [ ii ] . Set ( NULL ) ;
}
}
}
*/
//-----------------------------------------------------------------------------
// true iff an advisor threw the object in the last three seconds
//-----------------------------------------------------------------------------
bool CNPC_Advisor : : DidThrow ( const CBaseEntity * pEnt )
{
// look through all my objects and see if they match this entity. Incidentally if
// they're more than three seconds old, purge them.
float threeSecondsAgo = gpGlobals - > curtime - 3.0f ;
for ( int ii = 0 ; ii < kMaxThrownObjectsTracked ; + + ii )
{
// if object is old, skip it.
CBaseEntity * pTestEnt = m_haRecentlyThrownObjects [ ii ] ;
if ( pTestEnt )
{
if ( m_flaRecentlyThrownObjectTimes [ ii ] < threeSecondsAgo )
{
m_haRecentlyThrownObjects [ ii ] . Set ( NULL ) ;
continue ;
}
else if ( pTestEnt = = pEnt )
{
return true ;
}
}
}
return false ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : AddToThrownObjects ( CBaseEntity * pEnt )
{
Assert ( pEnt ) ;
// try to find an empty slot, or if none exists, the oldest object
int oldestThrownObject = 0 ;
for ( int ii = 0 ; ii < kMaxThrownObjectsTracked ; + + ii )
{
if ( m_haRecentlyThrownObjects [ ii ] . IsValid ( ) )
{
if ( m_flaRecentlyThrownObjectTimes [ ii ] < m_flaRecentlyThrownObjectTimes [ oldestThrownObject ] )
{
oldestThrownObject = ii ;
}
}
else
{ // just use this one
oldestThrownObject = ii ;
break ;
}
}
m_haRecentlyThrownObjects [ oldestThrownObject ] = pEnt ;
m_flaRecentlyThrownObjectTimes [ oldestThrownObject ] = gpGlobals - > curtime ;
}
//-----------------------------------------------------------------------------
// Drag a particular object towards its staging location.
//-----------------------------------------------------------------------------
void CNPC_Advisor : : PullObjectToStaging ( CBaseEntity * pEnt , const Vector & stagingPos )
{
IPhysicsObject * pPhys = pEnt - > VPhysicsGetObject ( ) ;
Assert ( pPhys ) ;
Vector curPos = pEnt - > CollisionProp ( ) - > WorldSpaceCenter ( ) ;
Vector displacement = stagingPos - curPos ;
// quick and dirty -- use exponential decay to haul the object into place
// ( a better looking solution would be to use a spring system )
float desiredDisplacementLen = ExponentialDecay ( STAGING_OBJECT_FALLOFF_TIME , gpGlobals - > frametime ) ; // * sqrt(displacementLen);
Vector vel ; AngularImpulse angimp ;
pPhys - > GetVelocity ( & vel , & angimp ) ;
vel = ( 1.0f / gpGlobals - > frametime ) * ( displacement * ( 1.0f - desiredDisplacementLen ) ) ;
pPhys - > SetVelocity ( & vel , & angimp ) ;
}
# endif
int CNPC_Advisor : : OnTakeDamage ( const CTakeDamageInfo & info )
{
// Clip our max
CTakeDamageInfo newInfo = info ;
if ( newInfo . GetDamage ( ) > 20.0f )
{
newInfo . SetDamage ( 20.0f ) ;
}
// Hack to make him constantly flinch
m_flNextFlinchTime = gpGlobals - > curtime ;
const float oldLastDamageTime = m_flLastDamageTime ;
int retval = BaseClass : : OnTakeDamage ( newInfo ) ;
// we have a special reporting output
if ( oldLastDamageTime ! = gpGlobals - > curtime )
{
// only fire once per frame
m_OnHealthIsNow . Set ( GetHealth ( ) , newInfo . GetAttacker ( ) , this ) ;
}
return retval ;
}
# if NPC_ADVISOR_HAS_BEHAVIOR
//-----------------------------------------------------------------------------
// Returns the best new schedule for this NPC based on current conditions.
//-----------------------------------------------------------------------------
int CNPC_Advisor : : SelectSchedule ( )
{
if ( IsInAScript ( ) )
return SCHED_ADVISOR_IDLE_STAND ;
switch ( m_NPCState )
{
case NPC_STATE_IDLE :
case NPC_STATE_ALERT :
{
return SCHED_ADVISOR_IDLE_STAND ;
}
case NPC_STATE_COMBAT :
{
if ( GetEnemy ( ) & & GetEnemy ( ) - > IsAlive ( ) )
{
if ( false /* m_hPlayerPinPos.IsValid() */ )
return SCHED_ADVISOR_TOSS_PLAYER ;
else
return SCHED_ADVISOR_COMBAT ;
}
return SCHED_ADVISOR_IDLE_STAND ;
}
}
return BaseClass : : SelectSchedule ( ) ;
}
//-----------------------------------------------------------------------------
// return the position where an object should be staged before throwing
//-----------------------------------------------------------------------------
Vector CNPC_Advisor : : GetThrowFromPos ( CBaseEntity * pEnt )
{
Assert ( pEnt ) ;
Assert ( pEnt - > VPhysicsGetObject ( ) ) ;
const CCollisionProperty * cProp = pEnt - > CollisionProp ( ) ;
Assert ( cProp ) ;
float effecRadius = cProp - > BoundingRadius ( ) ; // radius of object (important for kickout)
float howFarInFront = advisor_throw_stage_distance . GetFloat ( ) + effecRadius * 1.43f ; // clamp(lenToPlayer - posDist + effecRadius,effecRadius*2,90.f + effecRadius);
Vector fwd ;
GetVectors ( & fwd , NULL , NULL ) ;
return GetAbsOrigin ( ) + fwd * howFarInFront ;
}
# endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : Precache ( )
{
BaseClass : : Precache ( ) ;
PrecacheModel ( STRING ( GetModelName ( ) ) ) ;
# if NPC_ADVISOR_HAS_BEHAVIOR
PrecacheModel ( " sprites/lgtning.vmt " ) ;
# endif
PrecacheScriptSound ( " NPC_Advisor.Blast " ) ;
PrecacheScriptSound ( " NPC_Advisor.Gib " ) ;
PrecacheScriptSound ( " NPC_Advisor.Idle " ) ;
PrecacheScriptSound ( " NPC_Advisor.Alert " ) ;
PrecacheScriptSound ( " NPC_Advisor.Die " ) ;
PrecacheScriptSound ( " NPC_Advisor.Pain " ) ;
PrecacheScriptSound ( " NPC_Advisor.ObjectChargeUp " ) ;
PrecacheParticleSystem ( " Advisor_Psychic_Beam " ) ;
PrecacheParticleSystem ( " advisor_object_charge " ) ;
PrecacheModel ( " sprites/greenglow1.vmt " ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_Advisor : : IdleSound ( )
{
EmitSound ( " NPC_Advisor.Idle " ) ;
}
void CNPC_Advisor : : AlertSound ( )
{
EmitSound ( " NPC_Advisor.Alert " ) ;
}
void CNPC_Advisor : : PainSound ( const CTakeDamageInfo & info )
{
EmitSound ( " NPC_Advisor.Pain " ) ;
}
void CNPC_Advisor : : DeathSound ( const CTakeDamageInfo & info )
{
EmitSound ( " NPC_Advisor.Die " ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_Advisor : : DrawDebugTextOverlays ( )
{
int nOffset = BaseClass : : DrawDebugTextOverlays ( ) ;
return nOffset ;
}
# if NPC_ADVISOR_HAS_BEHAVIOR
//-----------------------------------------------------------------------------
// Determines which sounds the advisor cares about.
//-----------------------------------------------------------------------------
int CNPC_Advisor : : GetSoundInterests ( )
{
return SOUND_WORLD | SOUND_COMBAT | SOUND_PLAYER | SOUND_DANGER ;
}
//-----------------------------------------------------------------------------
// record the last time we heard a combat sound
//-----------------------------------------------------------------------------
bool CNPC_Advisor : : QueryHearSound ( CSound * pSound )
{
// Disregard footsteps from our own class type
CBaseEntity * pOwner = pSound - > m_hOwner ;
if ( pOwner & & pSound - > IsSoundType ( SOUND_COMBAT ) & & pSound - > SoundChannel ( ) ! = SOUNDENT_CHANNEL_NPC_FOOTSTEP & & pSound - > m_hOwner . IsValid ( ) & & pOwner - > IsPlayer ( ) )
{
// Msg("Heard player combat.\n");
m_flLastPlayerAttackTime = gpGlobals - > curtime ;
}
return BaseClass : : QueryHearSound ( pSound ) ;
}
//-----------------------------------------------------------------------------
// designer hook for setting throw rate
//-----------------------------------------------------------------------------
void CNPC_Advisor : : InputSetThrowRate ( inputdata_t & inputdata )
{
advisor_throw_rate . SetValue ( inputdata . value . Float ( ) ) ;
}
void CNPC_Advisor : : InputSetStagingNum ( inputdata_t & inputdata )
{
m_iStagingNum = inputdata . value . Int ( ) ;
}
//
// cause the player to be pinned to a point in space
//
void CNPC_Advisor : : InputPinPlayer ( inputdata_t & inputdata )
{
string_t targetname = inputdata . value . StringID ( ) ;
// null string means designer is trying to unpin the player
if ( ! targetname )
{
m_hPlayerPinPos = NULL ;
}
// otherwise try to look up the entity and make it a target.
CBaseEntity * pEnt = gEntList . FindEntityByName ( NULL , targetname ) ;
if ( pEnt )
{
m_hPlayerPinPos = pEnt ;
}
else
{
// if we couldn't find the target, just bail on the behavior.
Warning ( " Advisor tried to pin player to %s but that does not exist. \n " , targetname . ToCStr ( ) ) ;
m_hPlayerPinPos = NULL ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Advisor : : OnScheduleChange ( void )
{
Write_AllBeamsOff ( ) ;
m_hvStagedEnts . RemoveAll ( ) ;
BaseClass : : OnScheduleChange ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Advisor : : GatherConditions ( void )
{
BaseClass : : GatherConditions ( ) ;
// Handle script state changes
bool bInScript = IsInAScript ( ) ;
if ( ( m_bWasScripting & & bInScript = = false ) | | ( m_bWasScripting = = false & & bInScript ) )
{
SetCondition ( COND_ADVISOR_PHASE_INTERRUPT ) ;
}
// Retain this
m_bWasScripting = bInScript ;
}
//-----------------------------------------------------------------------------
// designer hook for yanking an object into the air right now
//-----------------------------------------------------------------------------
void CNPC_Advisor : : InputWrenchImmediate ( inputdata_t & inputdata )
{
string_t groupname = inputdata . value . StringID ( ) ;
Assert ( ! ! groupname ) ;
// for all entities with that name that aren't floating, punt them at me and add them to the levitation
CBaseEntity * pEnt = NULL ;
const Vector & myPos = GetAbsOrigin ( ) + Vector ( 0 , 36.0f , 0 ) ;
// conditional assignment: find an entity by name and save it into pEnt. Bail out when none are left.
while ( ( pEnt = gEntList . FindEntityByName ( pEnt , groupname ) ) ! = NULL )
{
// if I'm not already levitating it, and if I didn't just throw it
if ( ! m_physicsObjects . HasElement ( pEnt ) )
{
// add to levitation
IPhysicsObject * pPhys = pEnt - > VPhysicsGetObject ( ) ;
if ( pPhys )
{
// if the object isn't moveable, make it so.
if ( ! pPhys - > IsMoveable ( ) )
{
pPhys - > EnableMotion ( true ) ;
}
// first, kick it at me
Vector objectToMe ;
pPhys - > GetPosition ( & objectToMe , NULL ) ;
objectToMe = myPos - objectToMe ;
// compute a velocity that will get it here in about a second
objectToMe / = ( 1.5f * gpGlobals - > frametime ) ;
objectToMe * = random - > RandomFloat ( 0.25f , 1.0f ) ;
pPhys - > SetVelocity ( & objectToMe , NULL ) ;
// add it to tracked physics objects
m_physicsObjects . AddToTail ( pEnt ) ;
m_pLevitateController - > AttachObject ( pPhys , false ) ;
pPhys - > Wake ( ) ;
}
else
{
Warning ( " Advisor tried to wrench %s, but it is not moveable! " , pEnt - > GetEntityName ( ) . ToCStr ( ) ) ;
}
}
}
}
//-----------------------------------------------------------------------------
// write a message turning a beam on
//-----------------------------------------------------------------------------
void CNPC_Advisor : : Write_BeamOn ( CBaseEntity * pEnt )
{
Assert ( pEnt ) ;
EntityMessageBegin ( this , true ) ;
WRITE_BYTE ( ADVISOR_MSG_START_BEAM ) ;
WRITE_LONG ( pEnt - > entindex ( ) ) ;
MessageEnd ( ) ;
}
//-----------------------------------------------------------------------------
// write a message turning a beam off
//-----------------------------------------------------------------------------
void CNPC_Advisor : : Write_BeamOff ( CBaseEntity * pEnt )
{
Assert ( pEnt ) ;
EntityMessageBegin ( this , true ) ;
WRITE_BYTE ( ADVISOR_MSG_STOP_BEAM ) ;
WRITE_LONG ( pEnt - > entindex ( ) ) ;
MessageEnd ( ) ;
}
//-----------------------------------------------------------------------------
// tell client to kill all beams
//-----------------------------------------------------------------------------
void CNPC_Advisor : : Write_AllBeamsOff ( void )
{
EntityMessageBegin ( this , true ) ;
WRITE_BYTE ( ADVISOR_MSG_STOP_ALL_BEAMS ) ;
MessageEnd ( ) ;
}
//-----------------------------------------------------------------------------
// input wrapper around Write_BeamOn
//-----------------------------------------------------------------------------
void CNPC_Advisor : : InputTurnBeamOn ( inputdata_t & inputdata )
{
// inputdata should specify a target
CBaseEntity * pTarget = gEntList . FindEntityByName ( NULL , inputdata . value . StringID ( ) ) ;
if ( pTarget )
{
Write_BeamOn ( pTarget ) ;
}
else
{
Warning ( " InputTurnBeamOn could not find object %s " , inputdata . value . String ( ) ) ;
}
}
//-----------------------------------------------------------------------------
// input wrapper around Write_BeamOff
//-----------------------------------------------------------------------------
void CNPC_Advisor : : InputTurnBeamOff ( inputdata_t & inputdata )
{
// inputdata should specify a target
CBaseEntity * pTarget = gEntList . FindEntityByName ( NULL , inputdata . value . StringID ( ) ) ;
if ( pTarget )
{
Write_BeamOff ( pTarget ) ;
}
else
{
Warning ( " InputTurnBeamOn could not find object %s " , inputdata . value . String ( ) ) ;
}
}
void CNPC_Advisor : : InputElightOn ( inputdata_t & inputdata )
{
EntityMessageBegin ( this , true ) ;
WRITE_BYTE ( ADVISOR_MSG_START_ELIGHT ) ;
MessageEnd ( ) ;
}
void CNPC_Advisor : : InputElightOff ( inputdata_t & inputdata )
{
EntityMessageBegin ( this , true ) ;
WRITE_BYTE ( ADVISOR_MSG_STOP_ELIGHT ) ;
MessageEnd ( ) ;
}
# endif
//==============================================================================================
// MOTION CALLBACK
//==============================================================================================
CAdvisorLevitate : : simresult_e CAdvisorLevitate : : Simulate ( IPhysicsMotionController * pController , IPhysicsObject * pObject , float deltaTime , Vector & linear , AngularImpulse & angular )
{
// this function can be optimized to minimize branching if necessary (PPE branch prediction)
CNPC_Advisor * pAdvisor = static_cast < CNPC_Advisor * > ( m_Advisor . Get ( ) ) ;
Assert ( pAdvisor ) ;
if ( ! OldStyle ( ) )
{ // independent movement of all objects
// if an object was recently thrown, just zero out its gravity.
if ( pAdvisor - > DidThrow ( static_cast < CBaseEntity * > ( pObject - > GetGameData ( ) ) ) )
{
linear = Vector ( 0 , 0 , GetCurrentGravity ( ) ) ;
return SIM_GLOBAL_ACCELERATION ;
}
else
{
Vector vel ; AngularImpulse angvel ;
pObject - > GetVelocity ( & vel , & angvel ) ;
Vector pos ;
pObject - > GetPosition ( & pos , NULL ) ;
bool bMovingUp = vel . z > 0 ;
// if above top limit and moving up, move down. if below bottom limit and moving down, move up.
if ( bMovingUp )
{
if ( pos . z > m_vecGoalPos2 . z )
{
// turn around move down
linear = Vector ( 0 , 0 , Square ( ( 1.0f - m_flFloat ) ) * GetCurrentGravity ( ) ) ;
angular = Vector ( 0 , - 5 , 0 ) ;
}
else
{ // keep moving up
linear = Vector ( 0 , 0 , ( 1.0f + m_flFloat ) * GetCurrentGravity ( ) ) ;
angular = Vector ( 0 , 0 , 10 ) ;
}
}
else
{
if ( pos . z < m_vecGoalPos1 . z )
{
// turn around move up
linear = Vector ( 0 , 0 , Square ( ( 1.0f + m_flFloat ) ) * GetCurrentGravity ( ) ) ;
angular = Vector ( 0 , 5 , 0 ) ;
}
else
{ // keep moving down
linear = Vector ( 0 , 0 , ( 1.0f - m_flFloat ) * GetCurrentGravity ( ) ) ;
angular = Vector ( 0 , 0 , 10 ) ;
}
}
return SIM_GLOBAL_ACCELERATION ;
}
//NDebugOverlay::Cross3D(pos,24.0f,255,255,0,true,0.04f);
}
else // old stateless technique
{
Warning ( " Advisor using old-style object movement! \n " ) ;
/* // obsolete
CBaseEntity * pEnt = ( CBaseEntity * ) pObject - > GetGameData ( ) ;
Vector vecDir1 = m_vecGoalPos1 - pEnt - > GetAbsOrigin ( ) ;
VectorNormalize ( vecDir1 ) ;
Vector vecDir2 = m_vecGoalPos2 - pEnt - > GetAbsOrigin ( ) ;
VectorNormalize ( vecDir2 ) ;
*/
linear = Vector ( 0 , 0 , m_flFloat * GetCurrentGravity ( ) ) ; // + m_flFloat * 0.5 * ( vecDir1 + vecDir2 );
angular = Vector ( 0 , 0 , 10 ) ;
return SIM_GLOBAL_ACCELERATION ;
}
}
//==============================================================================================
// ADVISOR PHYSICS DAMAGE TABLE
//==============================================================================================
static impactentry_t advisorLinearTable [ ] =
{
{ 100 * 100 , 10 } ,
{ 250 * 250 , 25 } ,
{ 350 * 350 , 50 } ,
{ 500 * 500 , 75 } ,
{ 1000 * 1000 , 100 } ,
} ;
static impactentry_t advisorAngularTable [ ] =
{
{ 50 * 50 , 10 } ,
{ 100 * 100 , 25 } ,
{ 150 * 150 , 50 } ,
{ 200 * 200 , 75 } ,
} ;
static impactdamagetable_t gAdvisorImpactDamageTable =
{
advisorLinearTable ,
advisorAngularTable ,
ARRAYSIZE ( advisorLinearTable ) ,
ARRAYSIZE ( advisorAngularTable ) ,
200 * 200 , // minimum linear speed squared
180 * 180 , // minimum angular speed squared (360 deg/s to cause spin/slice damage)
15 , // can't take damage from anything under 15kg
10 , // anything less than 10kg is "small"
5 , // never take more than 1 pt of damage from anything under 15kg
128 * 128 , // <15kg objects must go faster than 36 in/s to do damage
45 , // large mass in kg
2 , // large mass scale (anything over 500kg does 4X as much energy to read from damage table)
1 , // large mass falling scale
0 , // my min velocity
} ;
//-----------------------------------------------------------------------------
// Purpose:
// Output : const impactdamagetable_t
//-----------------------------------------------------------------------------
const impactdamagetable_t & CNPC_Advisor : : GetPhysicsImpactDamageTable ( void )
{
return advisor_use_impact_table . GetBool ( ) ? gAdvisorImpactDamageTable : BaseClass : : GetPhysicsImpactDamageTable ( ) ;
}
# if NPC_ADVISOR_HAS_BEHAVIOR
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC ( npc_advisor , CNPC_Advisor )
DECLARE_TASK ( TASK_ADVISOR_FIND_OBJECTS )
DECLARE_TASK ( TASK_ADVISOR_LEVITATE_OBJECTS )
/*
DECLARE_TASK ( TASK_ADVISOR_PICK_THROW_OBJECT )
DECLARE_TASK ( TASK_ADVISOR_THROW_OBJECT )
*/
DECLARE_CONDITION ( COND_ADVISOR_PHASE_INTERRUPT ) // A stage has interrupted us
DECLARE_TASK ( TASK_ADVISOR_STAGE_OBJECTS ) // haul all the objects into the throw-from slots
DECLARE_TASK ( TASK_ADVISOR_BARRAGE_OBJECTS ) // hurl all the objects in sequence
DECLARE_TASK ( TASK_ADVISOR_PIN_PLAYER ) // pinion the player to a point in space
//=========================================================
DEFINE_SCHEDULE
(
SCHED_ADVISOR_COMBAT ,
" Tasks "
" TASK_ADVISOR_FIND_OBJECTS 0 "
" TASK_ADVISOR_LEVITATE_OBJECTS 0 "
" TASK_ADVISOR_STAGE_OBJECTS 1 "
" TASK_ADVISOR_BARRAGE_OBJECTS 0 "
" "
" Interrupts "
" COND_ADVISOR_PHASE_INTERRUPT "
" COND_ENEMY_DEAD "
)
//=========================================================
DEFINE_SCHEDULE
(
SCHED_ADVISOR_IDLE_STAND ,
" Tasks "
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE "
" TASK_WAIT 3 "
" "
" Interrupts "
" COND_NEW_ENEMY "
" COND_SEE_FEAR "
" COND_ADVISOR_PHASE_INTERRUPT "
)
DEFINE_SCHEDULE
(
SCHED_ADVISOR_TOSS_PLAYER ,
" Tasks "
" TASK_ADVISOR_FIND_OBJECTS 0 "
" TASK_ADVISOR_LEVITATE_OBJECTS 0 "
" TASK_ADVISOR_PIN_PLAYER 0 "
" "
" Interrupts "
)
AI_END_CUSTOM_NPC ( )
# endif