aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/episodic/npc_advisor.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/episodic/npc_advisor.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/episodic/npc_advisor.cpp')
-rw-r--r--mp/src/game/server/episodic/npc_advisor.cpp2051
1 files changed, 2051 insertions, 0 deletions
diff --git a/mp/src/game/server/episodic/npc_advisor.cpp b/mp/src/game/server/episodic/npc_advisor.cpp
new file mode 100644
index 00000000..b70ab7ea
--- /dev/null
+++ b/mp/src/game/server/episodic/npc_advisor.cpp
@@ -0,0 +1,2051 @@
+//========= 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