You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2051 lines
58 KiB
2051 lines
58 KiB
//========= 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 |
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
|
|
#pragma warning(push) |
|
#pragma warning(disable : 4706) |
|
|
|
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 |
|
} |
|
#pragma warning(pop) |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
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
|
|
|