mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-07-19 15:51:22 +00:00
4696 lines
132 KiB
C++
4696 lines
132 KiB
C++
//========= 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 "ai_baseactor.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"
|
|
|
|
#include "ai_default.h"
|
|
#include "ai_task.h"
|
|
#include "npcevent.h"
|
|
#include "activitylist.h"
|
|
|
|
#ifdef EZ2
|
|
#include "grenade_hopwire.h"
|
|
#include "npc_scanner.h"
|
|
#include "bone_setup.h"
|
|
#include "npc_turret_floor.h"
|
|
#include "IEffects.h"
|
|
#include "collisionutils.h"
|
|
#include "scripted.h"
|
|
#endif
|
|
|
|
// 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.");
|
|
|
|
ConVar sk_advisor_melee_dmg("sk_advisor_melee_dmg", "0");
|
|
ConVar sk_advisor_pin_speed( "sk_advisor_pin_speed", "32" );
|
|
|
|
#ifdef EZ2
|
|
ConVar sk_advisor_pin_throw_cooldown( "sk_advisor_pin_throw_cooldown", "3" );
|
|
|
|
// Think contexts
|
|
static const char * ADVISOR_FLY_THINK = "FlyThink";
|
|
static const char * ADVISOR_PARTICLE_THINK = "AdvisorParticle";
|
|
#endif
|
|
|
|
#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("
|
|
ConVar advisor_throw_min_mass( "advisor_throw_min_mass", "20" );
|
|
ConVar advisor_throw_max_mass( "advisor_throw_max_mass", "220" );
|
|
|
|
#ifdef EZ2
|
|
ConVar advisor_throw_stage_min_mass( "advisor_throw_stage_min_mass", "20" );
|
|
ConVar advisor_throw_stage_max_mass( "advisor_throw_stage_max_mass", "220" );
|
|
|
|
ConVar advisor_head_debounce( "advisor_head_debounce", "0.7" );
|
|
ConVar advisor_camera_influence( "advisor_camera_influence", "1.0" );
|
|
ConVar advisor_camera_debounce( "advisor_camera_debounce", "0.2" );
|
|
|
|
ConVar advisor_use_facing_override( "advisor_use_facing_override", "1" );
|
|
ConVar advisor_use_flyer_poses( "advisor_use_flyer_poses", "1" );
|
|
ConVar advisor_update_yaw( "advisor_update_yaw", "1" );
|
|
|
|
ConVar advisor_hurt_pose( "advisor_hurt_pose", "0" );
|
|
ConVar advisor_camera_flash_health_ratio( "advisor_camera_flash_health_ratio", "0.35" );
|
|
//ConVar advisor_bleed_health_ratio( "advisor_bleed_health_ratio", "0.25" );
|
|
|
|
ConVar advisor_sparks_health_ratio_max( "advisor_sparks_health_ratio_max", "0.5" );
|
|
ConVar advisor_sparks_health_ratio_min( "advisor_sparks_health_ratio_min", "0.1" );
|
|
|
|
//ConVar advisor_push_max_radius( "advisor_push_max_radius", "224.0" );
|
|
//ConVar advisor_push_delta( "advisor_push_delta", "4.0" );
|
|
//ConVar advisor_push_max_force_scale( "advisor_push_max_force_scale", "0.1" );
|
|
|
|
Activity ACT_ADVISOR_OBJECT_R_HOLD;
|
|
Activity ACT_ADVISOR_OBJECT_R_THROW;
|
|
Activity ACT_ADVISOR_OBJECT_L_HOLD;
|
|
Activity ACT_ADVISOR_OBJECT_L_THROW;
|
|
Activity ACT_ADVISOR_OBJECT_BOTH_HOLD;
|
|
Activity ACT_ADVISOR_OBJECT_BOTH_THROW;
|
|
#endif
|
|
|
|
// 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.
|
|
//
|
|
#define ADVISOR_MELEE_LEFT ( 3 )
|
|
#define ADVISOR_MELEE_RIGHT ( 4 )
|
|
|
|
#ifdef EZ2
|
|
int ADVISOR_AE_DETACH_R_ARM;
|
|
int ADVISOR_AE_DETACH_L_ARM;
|
|
#endif
|
|
|
|
// Skins
|
|
#ifdef EZ2
|
|
#define ADVISOR_SKIN_NOSHIELD 0
|
|
#define ADVISOR_SKIN_SHIELD 1
|
|
#endif
|
|
|
|
|
|
#if NPC_ADVISOR_HAS_BEHAVIOR
|
|
//
|
|
// Custom schedules.
|
|
//
|
|
enum
|
|
{
|
|
SCHED_ADVISOR_COMBAT = LAST_SHARED_SCHEDULE,
|
|
SCHED_ADVISOR_IDLE_STAND,
|
|
SCHED_ADVISOR_TOSS_PLAYER,
|
|
#ifdef EZ2
|
|
SCHED_ADVISOR_STAGGER,
|
|
SCHED_ADVISOR_MELEE_ATTACK1,
|
|
#endif
|
|
};
|
|
|
|
|
|
//
|
|
// 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,
|
|
|
|
#ifdef EZ2
|
|
TASK_ADVISOR_STAGGER,
|
|
|
|
TASK_ADVISOR_WAIT_FOR_ACTIVITY,
|
|
#endif
|
|
};
|
|
|
|
//
|
|
// Custom conditions.
|
|
//
|
|
enum
|
|
{
|
|
COND_ADVISOR_PHASE_INTERRUPT = LAST_SHARED_CONDITION,
|
|
#ifdef EZ2
|
|
COND_ADVISOR_FORCE_PIN,
|
|
COND_ADVISOR_STAGGER
|
|
#endif
|
|
};
|
|
#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()
|
|
|
|
#ifdef EZ2
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
class CNPC_AdvisorFlyer : public CNPC_CScanner
|
|
{
|
|
DECLARE_CLASS( CNPC_AdvisorFlyer, CNPC_CScanner );
|
|
public:
|
|
void Spawn( void );
|
|
void Precache( void );
|
|
|
|
virtual int OnTakeDamage( const CTakeDamageInfo &info ) { return 0.0f; };
|
|
|
|
Disposition_t IRelationType( CBaseEntity *pTarget );
|
|
|
|
bool UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer = NULL );
|
|
|
|
virtual void StartTask( const Task_t *pTask );
|
|
|
|
float GetGoalDistance( void );
|
|
float MinGroundDist( void );
|
|
|
|
Vector VelocityToAvoidObstacles( float flInterval );
|
|
void MoveExecute_Alive( float flInterval );
|
|
|
|
// The Advisor flyer isn't a real enemy
|
|
bool CanBeAnEnemyOf( CBaseEntity *pEnemy ) { return false; }
|
|
bool CanBeSeenBy( CAI_BaseNPC *pNPC ) { return false; }
|
|
|
|
// Never displace the flyer - the advisor should kill it if displaced
|
|
virtual bool IsDisplacementImpossible() { return false; }
|
|
|
|
// Use the base class activation function
|
|
void Activate() { BaseClass::Activate(); }
|
|
|
|
virtual char *GetEngineSound( void ) { return "NPC_AdvisorFlyer.FlyLoop"; }
|
|
virtual char *GetScannerSoundPrefix( void ) { return "NPC_AdvisorFlyer"; }
|
|
};
|
|
|
|
void CNPC_AdvisorFlyer::Spawn( void )
|
|
{
|
|
BaseClass::Spawn();
|
|
|
|
AddEffects( EF_NODRAW );
|
|
AddEffects( EF_NOSHADOW );
|
|
SetHullType( HULL_LARGE_CENTERED );
|
|
SetHullSizeNormal();
|
|
|
|
if (GetOwnerEntity())
|
|
{
|
|
SetMass( GetOwnerEntity()->GetMass() );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_AdvisorFlyer::Precache( void )
|
|
{
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.Shoot" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.Alert" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.Die" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.Combat" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.Idle" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.Pain" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.TakePhoto" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.AttackFlash" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.DiveBombFlyby" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.DiveBomb" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.DeployMine" );
|
|
PrecacheScriptSound( "NPC_AdvisorFlyer.FlyLoop" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
Disposition_t CNPC_AdvisorFlyer::IRelationType( CBaseEntity * pTarget )
|
|
{
|
|
if ( GetOwnerEntity() )
|
|
{
|
|
CAI_BaseNPC * pNPC = GetOwnerEntity()->MyNPCPointer();
|
|
if ( pNPC )
|
|
{
|
|
// Ignore targets which are not my advisor's enemy
|
|
if ( pTarget && pNPC->GetEnemy() != pTarget )
|
|
return D_NU;
|
|
|
|
return pNPC->IRelationType( pTarget );
|
|
}
|
|
}
|
|
|
|
return BaseClass::IRelationType( pTarget );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Update information on my enemy
|
|
// Input :
|
|
// Output : Returns true is new enemy, false is known enemy
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_AdvisorFlyer::UpdateEnemyMemory( CBaseEntity *pEnemy, const Vector &position, CBaseEntity *pInformer )
|
|
{
|
|
if (BaseClass::UpdateEnemyMemory( pEnemy, position, pInformer ))
|
|
{
|
|
if ( GetOwnerEntity() && GetOwnerEntity()->MyNPCPointer() )
|
|
GetOwnerEntity()->MyNPCPointer()->UpdateEnemyMemory( pEnemy, position, pInformer );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_AdvisorFlyer::StartTask( const Task_t *pTask )
|
|
{
|
|
switch ( pTask->iTask )
|
|
{
|
|
// Advisor flyers do not dive bomb
|
|
case TASK_SCANNER_SET_FLY_DIVE:
|
|
TaskComplete();
|
|
break;
|
|
|
|
default:
|
|
{
|
|
BaseClass::StartTask( pTask );
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void GetAvoidanceForcesForAdvisor( Vector &vecOutForce, CBaseEntity *pEntity, float flEntityRadius, float flAvoidTime );
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose :
|
|
// Input :
|
|
// Output :
|
|
//------------------------------------------------------------------------------
|
|
Vector CNPC_AdvisorFlyer::VelocityToAvoidObstacles(float flInterval)
|
|
{
|
|
if (!GetOwnerEntity())
|
|
return BaseClass::VelocityToAvoidObstacles( flInterval );
|
|
|
|
Vector vBounce = vec3_origin;
|
|
|
|
GetAvoidanceForcesForAdvisor( vBounce, this, GetOwnerEntity()->BoundingRadius() * 2.0f, flInterval );
|
|
|
|
// --------------------------------
|
|
// Avoid banging into stuff
|
|
// --------------------------------
|
|
trace_t tr;
|
|
Vector vTravelDir = m_vCurrentVelocity*flInterval*4.0f;
|
|
Vector endPos = GetAbsOrigin() + vTravelDir;
|
|
AI_TraceEntity( GetOwnerEntity(), GetAbsOrigin(), endPos, MASK_NPCSOLID|CONTENTS_WATER, &tr);
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
// Bounce off in normal
|
|
vBounce += tr.plane.normal * 0.5 * m_vCurrentVelocity.Length();
|
|
return (vBounce);
|
|
}
|
|
|
|
// --------------------------------
|
|
// Try to remain above the ground.
|
|
// --------------------------------
|
|
float flMinGroundDist = MinGroundDist();
|
|
AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, -flMinGroundDist),
|
|
MASK_NPCSOLID_BRUSHONLY|CONTENTS_WATER, this, COLLISION_GROUP_NONE, &tr);
|
|
if (tr.fraction < 1)
|
|
{
|
|
// Clamp veloctiy
|
|
if (tr.fraction < 0.1)
|
|
{
|
|
tr.fraction = 0.1;
|
|
}
|
|
|
|
vBounce += Vector(0, 0, 50/tr.fraction);
|
|
}
|
|
|
|
return vBounce;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_AdvisorFlyer::MoveExecute_Alive(float flInterval)
|
|
{
|
|
// Amount of noise to add to flying
|
|
float noiseScale = 3.0f;
|
|
|
|
// -------------------------------------------
|
|
// Avoid obstacles
|
|
// -------------------------------------------
|
|
SetCurrentVelocity( GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval) );
|
|
|
|
IPhysicsObject *pPhysics = VPhysicsGetObject();
|
|
|
|
if ( pPhysics && pPhysics->IsAsleep() )
|
|
{
|
|
pPhysics->Wake();
|
|
}
|
|
|
|
if (GetOwnerEntity() && GetOwnerEntity()->MyNPCPointer())
|
|
{
|
|
Vector vecDir;
|
|
AngleVectors( GetAbsAngles(), &vecDir );
|
|
float flYaw = UTIL_VecToYaw( vecDir );
|
|
|
|
if (GetOwnerEntity()->MyNPCPointer()->GetMotor()->GetIdealYaw() != flYaw)
|
|
{
|
|
// Ideal yaw is sometimes used by the advisor's base AI functions, like melee attacks
|
|
// Make sure the flyer matches this
|
|
m_fHeadYaw = flYaw;
|
|
}
|
|
}
|
|
|
|
// Add time-coherent noise to the current velocity so that it never looks bolted in place.
|
|
AddNoiseToVelocity( noiseScale );
|
|
|
|
AdjustScannerVelocity();
|
|
|
|
float maxSpeed = GetEnemy() ? ( GetMaxSpeed() * 2.0f ) : GetMaxSpeed();
|
|
if ( m_nFlyMode == SCANNER_FLY_DIVE )
|
|
{
|
|
maxSpeed = -1;
|
|
}
|
|
|
|
// Limit fall speed
|
|
LimitSpeed( maxSpeed );
|
|
|
|
// Blend out desired velocity when launched by the physcannon
|
|
BlendPhyscannonLaunchSpeed();
|
|
|
|
PlayFlySound();
|
|
}
|
|
|
|
LINK_ENTITY_TO_CLASS( npc_advisor_flyer, CNPC_AdvisorFlyer );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Purpose: A unique motor for the advisor which allows it to rotate on a full XYZ axis.
|
|
// It also sets angular velocity instead of setting angles, which is much smoother for its movetype.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
class CAI_AdvisorMotor : public CAI_BlendedMotor
|
|
{
|
|
public:
|
|
CAI_AdvisorMotor( CAI_BaseNPC *pOuter )
|
|
: CAI_BlendedMotor( pOuter )
|
|
{}
|
|
|
|
// Run the current or specified timestep worth of rotation
|
|
virtual void UpdateYaw( int speed = -1 );
|
|
|
|
void SetIdealYawAndPitchToTarget( const Vector &target, float noise = 0.0, float offset = 0.0 );
|
|
|
|
float GetIdealPitch() const { return m_IdealPitch; }
|
|
void SetIdealPitch( float idealYaw) { m_IdealPitch = idealYaw; }
|
|
|
|
float GetPitchSpeed() const;
|
|
|
|
CNPC_Advisor *GetAdvisorOuter();
|
|
const CNPC_Advisor *GetAdvisorOuter() const;
|
|
CNPC_AdvisorFlyer *GetAdvisorFlyer();
|
|
const CNPC_AdvisorFlyer *GetAdvisorFlyer() const;
|
|
|
|
private:
|
|
float m_IdealPitch; // TODO: Save this value?
|
|
};
|
|
|
|
void CAI_AdvisorMotor::UpdateYaw( int yawSpeed )
|
|
{
|
|
// Don't do this if we're locked
|
|
if ( IsYawLocked() )
|
|
return;
|
|
|
|
// Advisors are designed to face the enemy outside of regular AI facing tasks.
|
|
// However, if the yaw is updated during a facing task, this could result in the yaw being updated
|
|
// multiple times per think, which causes jittering.
|
|
// Under these circumstances, GetLastThink() usually returns as being greater than curtime.
|
|
if (gpGlobals->curtime < GetLastThink())
|
|
return;
|
|
|
|
GetOuter()->UpdateTurnGesture( );
|
|
|
|
GetOuter()->SetUpdatedYaw();
|
|
|
|
if ( yawSpeed == -1 )
|
|
yawSpeed = GetYawSpeed();
|
|
|
|
int pitchSpeed = GetPitchSpeed();
|
|
|
|
// We would normally use UTIL_AngleMod(), but this causes problems when we bring other dimensions into play.
|
|
// There's most likely a specific way of using it which would work, but for now, we can do without it.
|
|
|
|
QAngle ideal;
|
|
QAngle current;
|
|
|
|
QAngle angles = GetLocalAngles();
|
|
current[0] = angles.x; // UTIL_AngleMod
|
|
current[1] = angles.y; // UTIL_AngleMod
|
|
current[2] = angles.z; // UTIL_AngleMod
|
|
|
|
ideal[0] = GetIdealPitch(); // UTIL_AngleMod
|
|
ideal[1] = GetIdealYaw(); // UTIL_AngleMod
|
|
|
|
// Base the roll on how quickly we're changing our yaw
|
|
CNPC_AdvisorFlyer *pFlyer = GetAdvisorFlyer();
|
|
ideal[2] = -AngleDistance( ideal[1], current[1] ) * (pFlyer ? 1.0f : 0.5f);
|
|
|
|
float dt = MIN( 0.2, gpGlobals->curtime - GetLastThink() );
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
// We're setting angular velocity, so don't clamp yaw
|
|
//float newAng = AI_ClampYaw( (float)yawSpeed * 10.0, current[i], ideal[i], dt );
|
|
|
|
if (ideal[i] != current[i])
|
|
{
|
|
float speed;
|
|
switch (i)
|
|
{
|
|
// Pitch/Roll
|
|
case 0:
|
|
case 2: speed = (float)pitchSpeed * dt; break;
|
|
|
|
// Yaw
|
|
default:
|
|
case 1: speed = (float)yawSpeed * dt; break;
|
|
}
|
|
|
|
speed *= 10.0f;
|
|
|
|
float move = AngleDiff( ideal[i], current[i] );
|
|
|
|
angles[i] = move;
|
|
|
|
//if (i == 1)
|
|
// Msg( "Yaw changed: newAng %f, current %f, ideal %f, dt %f, yawSpeed %i\n", angles[i], current[i], ideal[i], dt, yawSpeed );
|
|
//else if (i == 0)
|
|
// Msg( "Pitch changed: newAng %f, current %f, ideal %f, dt %f, pitchSpeed %i\n", angles[i], current[i], ideal[i], dt, pitchSpeed );
|
|
}
|
|
}
|
|
|
|
// MOVETYPE_FLY doesn't interpolate angle changes, so we must change its velocity instead
|
|
float flAdvisorRotateSpeed = pFlyer ? 5.0f : 2.0f;
|
|
GetOuter()->SetLocalAngularVelocity( angles * flAdvisorRotateSpeed );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void CAI_AdvisorMotor::SetIdealYawAndPitchToTarget( const Vector &target, float noise, float offset )
|
|
{
|
|
float base = CalcIdealYaw( target );
|
|
float baseP = UTIL_VecToPitch( target - GetLocalOrigin() ) * 0.75f;
|
|
base += offset;
|
|
if ( noise > 0 )
|
|
{
|
|
noise *= 0.5;
|
|
base += random->RandomFloat( -noise, noise );
|
|
if ( base < 0 )
|
|
base += 360;
|
|
else if ( base >= 360 )
|
|
base -= 360;
|
|
}
|
|
SetIdealYaw( base );
|
|
SetIdealPitch( baseP );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// The advisor class.
|
|
//-----------------------------------------------------------------------------
|
|
class CNPC_Advisor : public CAI_BaseActor
|
|
{
|
|
DECLARE_CLASS( CNPC_Advisor, CAI_BaseActor );
|
|
|
|
#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();
|
|
|
|
#ifdef EZ2
|
|
virtual void SetModel( const char *szModelName );
|
|
|
|
virtual CSprite *GetGlowSpritePtr( int i );
|
|
virtual void SetGlowSpritePtr( int i, CSprite *sprite );
|
|
virtual EyeGlow_t *GetEyeGlowData( int i );
|
|
virtual int GetNumGlows() { return 2; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
enum AdvisorEyeState_t
|
|
{
|
|
ADVISOR_EYE_NORMAL, // Regular cyan
|
|
ADVISOR_EYE_CHARGE, // Turn yellow
|
|
|
|
// Eye "flags" (not real flags; can be turned into real flags if needed)
|
|
ADVISOR_EYE_DAMAGED, // Start flashing
|
|
ADVISOR_EYE_UNDAMAGED, // Stop flashing
|
|
};
|
|
|
|
void SetEyeState( AdvisorEyeState_t state );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
virtual bool GetGameTextSpeechParams( hudtextparms_t ¶ms );
|
|
virtual float GetHeadDebounce( void ); // how much of previous head turn to use
|
|
virtual void MaintainLookTargets( float flInterval );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
CBaseEntity *GetFollowTarget() { return m_hFollowTarget; }
|
|
|
|
const CNPC_AdvisorFlyer *GetAdvisorFlyer() const { return static_cast<const CNPC_AdvisorFlyer*>(m_hAdvisorFlyer.Get()); }
|
|
CNPC_AdvisorFlyer *GetAdvisorFlyer() { return static_cast<CNPC_AdvisorFlyer*>(m_hAdvisorFlyer.Get()); }
|
|
|
|
const CAI_AdvisorMotor *GetAdvisorMotor() const { return assert_cast<const CAI_AdvisorMotor *>(this->GetMotor()); }
|
|
CAI_AdvisorMotor * GetAdvisorMotor() { return assert_cast<CAI_AdvisorMotor *>(this->GetMotor()); }
|
|
|
|
CAI_Motor *CreateMotor()
|
|
{
|
|
MEM_ALLOC_CREDIT();
|
|
return new CAI_AdvisorMotor( this );
|
|
}
|
|
|
|
virtual float LineOfSightDist( const Vector &vecDir = vec3_invalid, float zEye = FLT_MAX );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
virtual CanPlaySequence_t CanPlaySequence( bool fDisregardState, int interruptLevel );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
inline bool IsPreparingToThrow() { return m_hvStagedEnts.Count() > 0 && m_hvStagedEnts[0]; }
|
|
|
|
inline bool IsThisPropStaged( CBaseEntity *pEnt ) { return m_hvStagedEnts.HasElement( pEnt ); }
|
|
|
|
inline CBaseEntity *GetFirstStagingPosition() { return m_hvStagingPositions.Count() > 0 ? m_hvStagingPositions[0] : NULL; }
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// This is ambiguous since arms are detachable and you can theoretically give them something else
|
|
enum AdvisorAccessory_t
|
|
{
|
|
ADVISOR_ACCESSORY_NONE,
|
|
|
|
// Arms
|
|
ADVISOR_ACCESSORY_ARMS,
|
|
ADVISOR_ACCESSORY_R_ARM,
|
|
ADVISOR_ACCESSORY_L_ARM,
|
|
ADVISOR_ACCESSORY_ARMS_STUBBED,
|
|
};
|
|
|
|
int GetCurrentAccessories();
|
|
void SetCurrentAccessories( AdvisorAccessory_t iNewAccessory );
|
|
|
|
CBaseAnimating *CreateDetachedArm( bool bLeft );
|
|
#endif
|
|
|
|
//
|
|
// CAI_BaseNPC:
|
|
//
|
|
virtual float MaxYawSpeed() { return 90.0f; } //120.0f 90.0f
|
|
inline float MaxPitchSpeed() const { return 10.0f; }
|
|
|
|
virtual Class_T Classify();
|
|
|
|
#ifdef EZ2
|
|
Disposition_t IRelationType( CBaseEntity *pTarget );
|
|
#endif
|
|
|
|
#if NPC_ADVISOR_HAS_BEHAVIOR
|
|
virtual int GetSoundInterests();
|
|
virtual int SelectSchedule();
|
|
#ifdef MAPBASE
|
|
virtual void BuildScheduleTestBits();
|
|
virtual void PrescheduleThink();
|
|
#endif
|
|
virtual void StartTask( const Task_t *pTask );
|
|
virtual void RunTask( const Task_t *pTask );
|
|
virtual void OnScheduleChange( void );
|
|
|
|
void HandleAnimEvent(animevent_t *pEvent);
|
|
int MeleeAttack1Conditions(float flDot, float flDist);
|
|
|
|
void Event_Killed(const CTakeDamageInfo &info);
|
|
|
|
#ifdef EZ2
|
|
virtual bool IsShieldOn( void );
|
|
virtual void ApplyShieldedEffects( void );
|
|
|
|
virtual int TranslateSchedule( int scheduleType );
|
|
|
|
Activity NPC_TranslateActivity( Activity activity );
|
|
|
|
void StartFlying();
|
|
void StopFlying();
|
|
void FlyThink();
|
|
|
|
void UpdateAdvisorFacing();
|
|
|
|
void StartSounds( void );
|
|
virtual void StopLoopingSounds();
|
|
|
|
bool HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sourceEnt );
|
|
|
|
// Override to handle shield
|
|
void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
|
|
#endif
|
|
|
|
#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 );
|
|
|
|
void StopPinPlayer(inputdata_t &inputdata);
|
|
|
|
#ifdef EZ2
|
|
void InputStartFlying( inputdata_t &inputdata );
|
|
void InputStopFlying( inputdata_t &inputdata );
|
|
void InputSetFollowTarget( inputdata_t &inputdata );
|
|
void InputStartPsychicShield( inputdata_t &inputdata );
|
|
void InputStopPsychicShield( inputdata_t &inputdata );
|
|
void InputDetachRightArm( inputdata_t &inputdata );
|
|
void InputDetachLeftArm( inputdata_t &inputdata );
|
|
void InputDisablePinFailsafe( inputdata_t &inputdata ) { m_bPinFailsafeActive = false; }
|
|
void InputEnablePinFailsafe( inputdata_t &inputdata ) { m_bPinFailsafeActive = true; }
|
|
|
|
COutputEvent m_OnMindBlast;
|
|
#endif
|
|
|
|
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 );
|
|
|
|
#ifdef EZ2
|
|
void BeginLevitateObject( CBaseEntity *pEnt );
|
|
void EndLevitateObject( CBaseEntity *pEnt );
|
|
#endif
|
|
|
|
|
|
#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
|
|
|
|
int beamonce = 1; //Makes sure it only plays once the beam
|
|
|
|
// 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;
|
|
|
|
#ifdef EZ2
|
|
// Start glow effects for this NPC
|
|
virtual void StartEye( void );
|
|
// Context think function for advisor particle
|
|
void ParticleThink();
|
|
|
|
// The way the advisor's throw particle effect works is mostly clientside... awkward. I think in order to make the gameplay match up with the effect, it should not be saved.
|
|
// This will cause some problems saving and loading during an advisor's attack
|
|
bool m_bThrowBeamShield;
|
|
// "Throw beam shield" is for the effect when the advisor picks up a prop, "Psychic shield" is for the active shield it puts up
|
|
bool m_bPsychicShieldOn;
|
|
bool m_bPinFailsafeActive = true;
|
|
bool m_bAdvisorFlyer;
|
|
EHANDLE m_hAdvisorFlyer;
|
|
|
|
// The target entity the advisor should follow. When "flying", this is equivalent to m_hAdvisorFlyer.
|
|
EHANDLE m_hFollowTarget;
|
|
|
|
// 0 = right, 1 = left, 2 = both
|
|
int m_iThrowAnimDir;
|
|
|
|
float m_fStaggerUntilTime;
|
|
|
|
bool m_bBleeding;
|
|
|
|
bool m_bLevitatingObjects; // For save/restore
|
|
|
|
// Consider m_pEyeGlow to be m_pCameraGlow1
|
|
CSprite *m_pCameraGlow2;
|
|
|
|
CSoundPatch *m_pBreathSound;
|
|
|
|
Vector m_goalEyeDirection;
|
|
|
|
PoseParameter_t m_ParameterCameraYaw; // "camera_yaw"
|
|
PoseParameter_t m_ParameterCameraPitch; // "camera_pitch"
|
|
PoseParameter_t m_ParameterFlexHorz; // "flex_horz"
|
|
PoseParameter_t m_ParameterFlexVert; // "flex_vert"
|
|
PoseParameter_t m_ParameterArmsExtent; // "arms_extent"
|
|
PoseParameter_t m_ParameterHurt; // "hurt"
|
|
|
|
static int gm_nMouthAttachment;
|
|
static int gm_nLFrontTopJetAttachment;
|
|
static int gm_nRFrontTopJetAttachment;
|
|
static int gm_nLRearTopJetAttachment;
|
|
static int gm_nRRearTopJetAttachment;
|
|
#endif
|
|
|
|
|
|
#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
|
|
};
|
|
|
|
#ifdef EZ2
|
|
int CNPC_Advisor::gm_nMouthAttachment = -1;
|
|
int CNPC_Advisor::gm_nLFrontTopJetAttachment = -1;
|
|
int CNPC_Advisor::gm_nRFrontTopJetAttachment = -1;
|
|
int CNPC_Advisor::gm_nLRearTopJetAttachment = -1;
|
|
int CNPC_Advisor::gm_nRRearTopJetAttachment = -1;
|
|
#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"),
|
|
#ifdef EZ2
|
|
DEFINE_KEYFIELD( m_bAdvisorFlyer, FIELD_BOOLEAN, "AdvisorFlyer" ),
|
|
DEFINE_KEYFIELD( m_bPinFailsafeActive, FIELD_BOOLEAN, "pin_failsafe_active"),
|
|
|
|
DEFINE_FIELD( m_hAdvisorFlyer, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_hFollowTarget, FIELD_EHANDLE ),
|
|
|
|
DEFINE_FIELD( m_bPsychicShieldOn, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fStaggerUntilTime, FIELD_TIME ),
|
|
|
|
DEFINE_FIELD( m_bBleeding, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( m_bLevitatingObjects, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD( m_iThrowAnimDir, FIELD_INTEGER ),
|
|
|
|
DEFINE_FIELD( m_goalEyeDirection, FIELD_VECTOR ),
|
|
|
|
DEFINE_FIELD( m_ParameterCameraYaw, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_ParameterCameraPitch, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_ParameterFlexHorz, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_ParameterFlexVert, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_ParameterArmsExtent, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_ParameterHurt, FIELD_INTEGER ),
|
|
#endif
|
|
|
|
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" ),
|
|
#ifdef EZ2
|
|
DEFINE_OUTPUT( m_OnMindBlast, "OnMindBlast" ),
|
|
|
|
DEFINE_SOUNDPATCH( m_pBreathSound ),
|
|
#endif
|
|
|
|
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 ),
|
|
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "StopPinPlayer", StopPinPlayer),
|
|
|
|
#ifdef EZ2
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "StartFlying", InputStartFlying ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "StopFlying", InputStopFlying ),
|
|
DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetFollowTarget", InputSetFollowTarget ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnablePinFailsafe", InputEnablePinFailsafe ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisablePinFailsafe", InputDisablePinFailsafe ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "StartPsychicShield", InputStartPsychicShield ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "StopPsychicShield", InputStopPsychicShield ),
|
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DetachRightArm", InputDetachRightArm ),
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DetachLeftArm", InputDetachLeftArm ),
|
|
|
|
DEFINE_THINKFUNC( FlyThink )
|
|
#endif
|
|
|
|
#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();
|
|
|
|
GetAbsOrigin();
|
|
|
|
SetModel( STRING( GetModelName() ) );
|
|
|
|
m_iHealth = sk_advisor_health.GetFloat();
|
|
m_takedamage = DAMAGE_NO;
|
|
|
|
SetHullType( HULL_LARGE_CENTERED );
|
|
SetHullSizeNormal();
|
|
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags(FSOLID_NOT_STANDABLE); //FSOLID_NOT_SOLID
|
|
|
|
SetMoveType( MOVETYPE_FLY );
|
|
#ifdef EZ2
|
|
// Advisors cannot move unassisted
|
|
CapabilitiesRemove( bits_CAP_MOVE_GROUND );
|
|
|
|
// Advisors are slugs with CHARACTER!
|
|
CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );
|
|
#endif
|
|
|
|
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();
|
|
CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1);
|
|
|
|
NPCInit();
|
|
|
|
SetGoalEnt( NULL );
|
|
|
|
AddEFlags( EFL_NO_DISSOLVE );
|
|
|
|
#ifdef EZ2
|
|
GetMotor()->RecalculateYawSpeed();
|
|
|
|
SetActivity( ACT_IDLE );
|
|
#endif
|
|
}
|
|
|
|
|
|
#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
|
|
|
|
#ifdef EZ2
|
|
if ( m_bAdvisorFlyer )
|
|
{
|
|
StartFlying();
|
|
}
|
|
|
|
// Start looping sounds
|
|
StartSounds();
|
|
#endif
|
|
}
|
|
#pragma warning(pop)
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::UpdateOnRemove()
|
|
{
|
|
if ( m_pLevitateController )
|
|
{
|
|
physenv->DestroyMotionController( m_pLevitateController );
|
|
|
|
#ifdef EZ2
|
|
for (int i = m_physicsObjects.Count()-1; i >= 0; i--)
|
|
{
|
|
EndLevitateObject( m_physicsObjects[i] );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::OnRestore()
|
|
{
|
|
BaseClass::OnRestore();
|
|
#ifdef EZ2
|
|
if (m_bLevitatingObjects)
|
|
#endif
|
|
StartLevitatingObjects();
|
|
}
|
|
|
|
#ifdef EZ2
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::SetModel( const char *szModelName )
|
|
{
|
|
BaseClass::SetModel( szModelName );
|
|
|
|
Init( m_ParameterCameraYaw, "camera_yaw" );
|
|
Init( m_ParameterCameraPitch, "camera_pitch" );
|
|
Init( m_ParameterFlexHorz, "flex_horz" );
|
|
Init( m_ParameterFlexVert, "flex_vert" );
|
|
Init( m_ParameterArmsExtent, "arms_extent" );
|
|
Init( m_ParameterHurt, "hurt" );
|
|
|
|
gm_nMouthAttachment = LookupAttachment( "attach_mouth" );
|
|
gm_nLFrontTopJetAttachment = LookupAttachment( "attach_l_front_top_jet" );
|
|
gm_nRFrontTopJetAttachment = LookupAttachment( "attach_r_front_top_jet" );
|
|
gm_nLRearTopJetAttachment = LookupAttachment( "attach_l_front_top_jet1" );
|
|
gm_nRRearTopJetAttachment = LookupAttachment( "attach_r_rear_top_jet" );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return the pointer for a given sprite given an index
|
|
//-----------------------------------------------------------------------------
|
|
CSprite *CNPC_Advisor::GetGlowSpritePtr( int index )
|
|
{
|
|
switch (index)
|
|
{
|
|
case 0:
|
|
return m_pEyeGlow;
|
|
case 1:
|
|
return m_pCameraGlow2;
|
|
}
|
|
|
|
return BaseClass::GetGlowSpritePtr( index );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the glow sprite at the given index
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::SetGlowSpritePtr( int index, CSprite *sprite )
|
|
{
|
|
switch (index)
|
|
{
|
|
case 0:
|
|
m_pEyeGlow = sprite;
|
|
break;
|
|
case 1:
|
|
m_pCameraGlow2 = sprite;
|
|
break;
|
|
}
|
|
|
|
BaseClass::SetGlowSpritePtr( index, sprite );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return the glow attributes for a given index
|
|
//-----------------------------------------------------------------------------
|
|
EyeGlow_t *CNPC_Advisor::GetEyeGlowData( int index )
|
|
{
|
|
EyeGlow_t * eyeGlow = new EyeGlow_t();
|
|
|
|
switch (index){
|
|
case 0:
|
|
eyeGlow->spriteName = MAKE_STRING( "sprites/light_glow02.vmt" );
|
|
eyeGlow->attachment = MAKE_STRING( "camera_bottom_lens" );
|
|
|
|
eyeGlow->alpha = 255;
|
|
eyeGlow->brightness = 224;
|
|
|
|
eyeGlow->red = 0;
|
|
eyeGlow->green = 255;
|
|
eyeGlow->blue = 255;
|
|
|
|
eyeGlow->renderMode = kRenderWorldGlow;
|
|
eyeGlow->scale = 0.1f;
|
|
eyeGlow->proxyScale = 2.0f;
|
|
|
|
return eyeGlow;
|
|
case 1:
|
|
eyeGlow->spriteName = MAKE_STRING( "sprites/light_glow02.vmt" );
|
|
eyeGlow->attachment = MAKE_STRING( "camera_top_lens" );
|
|
|
|
eyeGlow->alpha = 255;
|
|
eyeGlow->brightness = 224;
|
|
|
|
eyeGlow->red = 0;
|
|
eyeGlow->green = 255;
|
|
eyeGlow->blue = 255;
|
|
|
|
eyeGlow->renderMode = kRenderWorldGlow;
|
|
eyeGlow->scale = 0.1f;
|
|
eyeGlow->proxyScale = 2.0f;
|
|
|
|
return eyeGlow;
|
|
}
|
|
|
|
return BaseClass::GetEyeGlowData(index);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::SetEyeState( AdvisorEyeState_t state )
|
|
{
|
|
if (!m_pEyeGlow || !m_pCameraGlow2)
|
|
return;
|
|
|
|
switch (state)
|
|
{
|
|
case ADVISOR_EYE_NORMAL:
|
|
{
|
|
if (IsShieldOn())
|
|
{
|
|
m_pEyeGlow->SetRenderColor( 255, 255, 255 );
|
|
m_pCameraGlow2->SetRenderColor( 255, 255, 255 );
|
|
|
|
m_pEyeGlow->SetScale( 0.25 );
|
|
m_pCameraGlow2->SetScale( 0.25 );
|
|
}
|
|
else
|
|
{
|
|
m_pEyeGlow->SetRenderColor( 0, 255, 255 );
|
|
m_pCameraGlow2->SetRenderColor( 0, 255, 255 );
|
|
|
|
m_pEyeGlow->SetScale( 0.1 );
|
|
m_pCameraGlow2->SetScale( 0.1 );
|
|
}
|
|
} break;
|
|
|
|
case ADVISOR_EYE_CHARGE:
|
|
{
|
|
// Turn yellow
|
|
m_pEyeGlow->SetRenderColor( 255, 255, 0 );
|
|
m_pCameraGlow2->SetRenderColor( 255, 255, 0 );
|
|
} break;
|
|
|
|
case ADVISOR_EYE_DAMAGED:
|
|
{
|
|
// Start flashing
|
|
m_pEyeGlow->m_nRenderFX = kRenderFxFlickerSlow;
|
|
m_pCameraGlow2->m_nRenderFX = kRenderFxFlickerSlow;
|
|
} break;
|
|
|
|
case ADVISOR_EYE_UNDAMAGED:
|
|
{
|
|
// Stop flashing
|
|
m_pEyeGlow->m_nRenderFX = kRenderFxNone;
|
|
m_pCameraGlow2->m_nRenderFX = kRenderFxNone;
|
|
} break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Parameters for scene event AI_GameText
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_Advisor::GetGameTextSpeechParams( hudtextparms_t ¶ms )
|
|
{
|
|
params.channel = 3;
|
|
params.x = -1;
|
|
params.y = 0.6;
|
|
params.effect = 0;
|
|
|
|
params.r1 = 136;
|
|
params.g1 = 180;
|
|
params.b1 = 185;
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_Advisor::GetHeadDebounce( void )
|
|
{
|
|
return advisor_head_debounce.GetFloat();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Maintain eye, head, body postures, etc.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::MaintainLookTargets( float flInterval )
|
|
{
|
|
BaseClass::MaintainLookTargets( flInterval );
|
|
|
|
// Turn our eye
|
|
if (HasCondition(COND_IN_PVS))
|
|
{
|
|
// Determine eye target position
|
|
Vector vecEyeTarget = EyePosition() + (EyeDirection3D() * 100.0f);
|
|
if (GetEnemy() && m_flThrowPhysicsTime + advisor_throw_warn_time.GetFloat() >= gpGlobals->curtime)
|
|
{
|
|
vecEyeTarget = GetEnemy()->EyePosition();
|
|
}
|
|
else if (GetLooktarget())
|
|
{
|
|
vecEyeTarget = GetLooktarget()->EyePosition();
|
|
}
|
|
|
|
// calc current animation head bias, movement needs to clamp accumulated with this
|
|
QAngle angBias;
|
|
QAngle vTargetAngles;
|
|
|
|
int iCamera = LookupAttachment( "camera" );
|
|
int iForward = LookupAttachment( "forward" );
|
|
|
|
matrix3x4_t eyesToWorld;
|
|
matrix3x4_t forwardToWorld, worldToForward;
|
|
|
|
GetAttachment( iCamera, eyesToWorld );
|
|
|
|
GetAttachment( iForward, forwardToWorld );
|
|
MatrixInvert( forwardToWorld, worldToForward );
|
|
|
|
MatrixInvert( forwardToWorld, worldToForward );
|
|
|
|
matrix3x4_t targetXform;
|
|
targetXform = forwardToWorld;
|
|
Vector vTargetDir = vecEyeTarget - EyePosition();
|
|
|
|
Studio_AlignIKMatrix( targetXform, vTargetDir );
|
|
|
|
matrix3x4_t headXform;
|
|
ConcatTransforms( worldToForward, targetXform, headXform );
|
|
MatrixAngles( headXform, vTargetAngles );
|
|
|
|
// partially debounce head goal
|
|
float s0 = 1.0 - advisor_camera_influence.GetFloat() + advisor_camera_debounce.GetFloat() * advisor_camera_influence.GetFloat();
|
|
float s1 = (1.0 - s0);
|
|
// limit velocity of head turns
|
|
m_goalEyeDirection.x = UTIL_Approach( m_goalEyeDirection.x * s0 + vTargetAngles.x * s1, m_goalEyeDirection.x, 10.0 );
|
|
m_goalEyeDirection.y = UTIL_Approach( m_goalEyeDirection.y * s0 + vTargetAngles.y * s1, m_goalEyeDirection.y, 30.0 );
|
|
//m_goalEyeDirection.z = UTIL_Approach( m_goalEyeDirection.z * s0 + vTargetAngles.z * s1, m_goalEyeDirection.z, 10.0 );
|
|
|
|
/*
|
|
if ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
|
|
{
|
|
// Msg( "yaw %.1f (%f) pitch %.1f (%.1f)\n", m_goalEyeDirection.y, vTargetAngles.y, vTargetAngles.x, m_goalEyeDirection.x );
|
|
// Msg( "yaw %.2f (goal %.2f) (influence %.2f) (flex %.2f)\n", flLimit, m_goalEyeDirection.y, flHeadInfluence, Get( m_FlexweightHeadRightLeft ) );
|
|
}
|
|
*/
|
|
|
|
float flTarget;
|
|
|
|
flTarget = ClampWithBias( m_ParameterCameraYaw, m_goalEyeDirection.y, angBias.y );
|
|
if ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
|
|
{
|
|
Msg( "yaw %5.1f : (%5.1f : 0.0) %5.1f (%5.1f)\n", flTarget, m_goalEyeDirection.y/*, Get( m_FlexweightHeadRightLeft )*/, angBias.y, Get( m_ParameterCameraYaw ) );
|
|
}
|
|
Set( m_ParameterCameraYaw, flTarget );
|
|
|
|
flTarget = ClampWithBias( m_ParameterCameraPitch, m_goalEyeDirection.x, angBias.x );
|
|
if ( (m_debugOverlays & OVERLAY_NPC_SELECTED_BIT) )
|
|
{
|
|
Msg( "pitch %5.1f : (%5.1f : 0.0) %5.1f (%5.1f)\n", flTarget, m_goalEyeDirection.x/*, Get( m_FlexweightHeadUpDown )*/, angBias.x, Get( m_ParameterCameraPitch ) );
|
|
}
|
|
Set( m_ParameterCameraPitch, flTarget );
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// Pass a direction to get how far an NPC would see if facing
|
|
// that direction. Pass nothing to get the length of the NPC's
|
|
// current line of sight.
|
|
//---------------------------------------------------------
|
|
float CNPC_Advisor::LineOfSightDist( const Vector &vecDir, float zEye )
|
|
{
|
|
Vector testDir;
|
|
if( vecDir == vec3_invalid )
|
|
{
|
|
testDir = EyeDirection3D();
|
|
testDir.x = 0;
|
|
}
|
|
else
|
|
{
|
|
testDir = vecDir;
|
|
}
|
|
|
|
if ( zEye == FLT_MAX )
|
|
zEye = EyePosition().z;
|
|
|
|
CTraceFilterLOS traceFilter( this, COLLISION_GROUP_NONE, m_hAdvisorFlyer );
|
|
trace_t tr;
|
|
|
|
// Need to center trace so don't get erratic results based on orientation
|
|
Vector testPos( GetAbsOrigin().x, GetAbsOrigin().y, zEye );
|
|
AI_TraceLOS( testPos, testPos + testDir * MAX_COORD_RANGE, this, &tr, &traceFilter );
|
|
return (tr.startpos - tr.endpos ).Length();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CanPlaySequence_t CNPC_Advisor::CanPlaySequence( bool fDisregardNPCState, int interruptLevel )
|
|
{
|
|
CanPlaySequence_t eReturn = BaseClass::CanPlaySequence( fDisregardNPCState, interruptLevel );
|
|
|
|
// Allows the advisor to play scripted sequences while dying.
|
|
// Mainly for death animations.
|
|
if ( eReturn == CANNOT_PLAY && !IsAlive() && !m_hCine )
|
|
{
|
|
eReturn = CAN_PLAY_NOW;
|
|
}
|
|
|
|
return eReturn;
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns this monster's classification in the relationship table.
|
|
//-----------------------------------------------------------------------------
|
|
Class_T CNPC_Advisor::Classify()
|
|
{
|
|
return CLASS_COMBINE;
|
|
}
|
|
|
|
|
|
#ifdef EZ2
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
Disposition_t CNPC_Advisor::IRelationType( CBaseEntity * pTarget )
|
|
{
|
|
// Don't attack entities we're about to throw
|
|
if ( m_hvStagedEnts.HasElement(pTarget) )
|
|
{
|
|
return D_NU;
|
|
}
|
|
|
|
return BaseClass::IRelationType( pTarget );
|
|
}
|
|
#endif
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
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();
|
|
|
|
#ifdef EZ2
|
|
BeginLevitateObject( pEnt );
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef EZ2
|
|
m_bLevitatingObjects = true;
|
|
#endif
|
|
}
|
|
|
|
// This function is used by both version of the entity finder below
|
|
bool CNPC_Advisor::CanLevitateEntity( CBaseEntity *pEntity, int minMass, int maxMass )
|
|
{
|
|
#ifdef EZ2
|
|
// Allowed to pick up physics NPCs which aren't our flyer
|
|
if (!pEntity || pEntity == m_hAdvisorFlyer)
|
|
#else
|
|
if (!pEntity || pEntity->IsNPC())
|
|
#endif
|
|
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) */ );
|
|
}
|
|
|
|
#ifdef EZ2
|
|
void CNPC_Advisor::BeginLevitateObject( CBaseEntity *pEnt )
|
|
{
|
|
if (pEnt && pEnt->IsNPC())
|
|
{
|
|
// Important for stopping turrets from being annoying and strange while under no gravity
|
|
ITippableTurret *pTippable = dynamic_cast<ITippableTurret*>(pEnt);
|
|
if (pTippable)
|
|
{
|
|
//Msg( "Setting low gravity on %s\n", pEnt->GetDebugName() );
|
|
pTippable->SetLowGravity( true );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CNPC_Advisor::EndLevitateObject( CBaseEntity *pEnt )
|
|
{
|
|
if (pEnt && pEnt->IsNPC())
|
|
{
|
|
// Important for stopping turrets from being annoying and strange while under no gravity
|
|
ITippableTurret *pTippable = dynamic_cast<ITippableTurret*>(pEnt);
|
|
if (pTippable)
|
|
{
|
|
//Msg( "Deactivating low gravity on %s\n", pEnt->GetDebugName() );
|
|
pTippable->SetLowGravity( false );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#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" );
|
|
|
|
#ifdef EZ2
|
|
// TODO: Show an effect on the throwable object
|
|
//DispatchParticleEffect( "Advisor_Throw_Obj_Charge", PATTACH_ABSORIGIN_FOLLOW, pThrowable );
|
|
#endif
|
|
|
|
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 )
|
|
{
|
|
#ifdef EZ2
|
|
for (int i = m_physicsObjects.Count()-1; i >= 0; i--)
|
|
{
|
|
EndLevitateObject( m_physicsObjects[i] );
|
|
|
|
m_physicsObjects.FastRemove( i );
|
|
}
|
|
#else
|
|
m_physicsObjects.RemoveAll();
|
|
#endif
|
|
|
|
for ( touchlink_t *link = touchroot->nextLink; link != touchroot; link = link->nextLink )
|
|
{
|
|
CBaseEntity *pTouch = link->entityTouched;
|
|
if ( CanLevitateEntity( pTouch, advisor_throw_min_mass.GetInt(), advisor_throw_max_mass.GetInt() ) )
|
|
{
|
|
if ( pTouch->GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
//Msg( " %d added %s\n", m_physicsObjects.Count(), STRING( list[i]->GetModelName() ) );
|
|
m_physicsObjects.AddToTail( pTouch );
|
|
|
|
#ifdef EZ2
|
|
BeginLevitateObject( pTouch );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
// 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;
|
|
|
|
}
|
|
#ifdef EZ2
|
|
case TASK_ADVISOR_STAGGER:
|
|
case TASK_ADVISOR_WAIT_FOR_ACTIVITY:
|
|
break;
|
|
#endif
|
|
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 )
|
|
{
|
|
#ifndef EZ2
|
|
//Needed for the npc face constantly at player
|
|
GetMotor()->SetIdealYawToTargetAndUpdate(GetEnemyLKP(), AI_KEEP_YAW_SPEED);
|
|
#else
|
|
if ( !m_bAdvisorFlyer )
|
|
{
|
|
UpdateAdvisorFacing();
|
|
}
|
|
#endif
|
|
|
|
// Used by TASK_ADVISOR_STAGGER
|
|
CTakeDamageInfo info( this, this, vec3_origin, GetAbsOrigin(), 20, DMG_BLAST );
|
|
|
|
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(), STRING( 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 );
|
|
|
|
#ifdef EZ2
|
|
SetEyeState( ADVISOR_EYE_CHARGE );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
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 )
|
|
{
|
|
//Removes the nopickup flag from the prop
|
|
pPhys->SetGameFlags(pPhys->GetGameFlags() & (~FVPHYSICS_NO_PLAYER_PICKUP));
|
|
}
|
|
}
|
|
|
|
Write_AllBeamsOff();
|
|
m_hvStagedEnts.RemoveAll();
|
|
|
|
#ifdef EZ2
|
|
SetEyeState( ADVISOR_EYE_NORMAL );
|
|
#endif
|
|
|
|
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);
|
|
|
|
//Removes the nopickup flag from the prop
|
|
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" );
|
|
|
|
#ifdef EZ2
|
|
// Throw animation
|
|
Activity actThrows[] { ACT_ADVISOR_OBJECT_R_THROW, ACT_ADVISOR_OBJECT_L_THROW, ACT_ADVISOR_OBJECT_BOTH_THROW };
|
|
SetIdealActivity( actThrows[m_iThrowAnimDir] );
|
|
|
|
SetEyeState( ADVISOR_EYE_NORMAL );
|
|
#endif
|
|
|
|
Write_BeamOff(m_hvStagedEnts[0]);
|
|
m_hvStagedEnts.Remove(0);
|
|
if (!ThrowObjectPrepare())
|
|
{
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// wait, bide time
|
|
// PullObjectToStaging(pThrowable, m_hvStagingPositions[ii]->GetAbsOrigin());
|
|
|
|
#ifdef EZ2
|
|
// Telegraph our progress in animation
|
|
Activity actHolds[] { ACT_ADVISOR_OBJECT_R_HOLD, ACT_ADVISOR_OBJECT_L_HOLD, ACT_ADVISOR_OBJECT_BOTH_HOLD };
|
|
|
|
// Determine where our object is
|
|
m_iThrowAnimDir = 2;
|
|
//if (pThrowable->GetMass() < 5000.0f)
|
|
{
|
|
//Vector vecForward;
|
|
//GetVectors( &vecForward, NULL, NULL );
|
|
|
|
Vector2D vec2DDelta = (pThrowable->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D());
|
|
Vector2DNormalize( vec2DDelta );
|
|
|
|
QAngle angAngles;
|
|
VectorAngles( Vector(vec2DDelta.x, vec2DDelta.y, 0), angAngles );
|
|
|
|
float flObjectAngle = AngleNormalizePositive( AngleDistance( angAngles.y, GetAbsAngles().y ) );
|
|
//if (flObjectAngle <= 160.0f || flObjectAngle >= 200.0f)
|
|
{
|
|
if (flObjectAngle > 180.0f)
|
|
m_iThrowAnimDir = 0; // right
|
|
else
|
|
m_iThrowAnimDir = 1; // left
|
|
}
|
|
Msg( "flObjectAngle = %f\n", flObjectAngle );
|
|
|
|
//float flDot = DotProduct2D( vec2DDelta, vecForward.AsVector2D() );
|
|
//if (abs(flDot) < 0.75f)
|
|
//{
|
|
// if (flDot < 0)
|
|
// m_iThrowAnimDir = 0; // right
|
|
// else
|
|
// m_iThrowAnimDir = 1; // left
|
|
//}
|
|
//Msg( "Dot: %f\n", flDot );
|
|
}
|
|
|
|
SetIdealActivity( actHolds[m_iThrowAnimDir] );
|
|
|
|
//if (GetActivity() == actStarts[m_iThrowAnimDir])
|
|
//{
|
|
// if (IsActivityFinished())
|
|
// SetActivity( actHolds[m_iThrowAnimDir] );
|
|
//}
|
|
//else if (GetActivity() != actHolds[m_iThrowAnimDir])
|
|
//{
|
|
// SetActivity( actStarts[m_iThrowAnimDir] );
|
|
//}
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TASK_ADVISOR_PIN_PLAYER:
|
|
{
|
|
|
|
#ifdef EZ2
|
|
CBaseEntity * pEnemy = AI_GetSinglePlayer();
|
|
#else
|
|
CBaseEntity * pEnemy = GetEnemy();
|
|
#endif
|
|
|
|
// bail out if the pin entity went away.
|
|
CBaseEntity *pPinEnt = m_hPlayerPinPos;
|
|
if (!pPinEnt)
|
|
{
|
|
pEnemy->SetGravity(1.0f);
|
|
pEnemy->SetMoveType( MOVETYPE_WALK );
|
|
Write_BeamOff(GetEnemy());
|
|
beamonce = 1;
|
|
#ifdef EZ2
|
|
if (GetTask() == pTask)
|
|
#endif
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
|
|
// failsafe: don't do this for more than ten seconds.
|
|
if ( gpGlobals->curtime > m_playerPinFailsafeTime && m_bPinFailsafeActive )
|
|
{
|
|
pEnemy->SetGravity(1.0f);
|
|
pEnemy->SetMoveType( MOVETYPE_WALK );
|
|
Write_BeamOff(GetEnemy());
|
|
beamonce = 1;
|
|
Warning( "Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe!\n" );
|
|
#ifdef EZ2
|
|
if (GetTask() == pTask)
|
|
#endif
|
|
TaskFail("Advisor did not leave PIN PLAYER mode. Aborting due to ten second failsafe!\n");
|
|
break;
|
|
}
|
|
|
|
#ifndef EZ2
|
|
// if the player isn't the enemy, bail out.
|
|
if ( !GetEnemy()->IsPlayer() )
|
|
{
|
|
GetEnemy()->SetGravity(1.0f);
|
|
GetEnemy()->SetMoveType( MOVETYPE_WALK );
|
|
Write_BeamOff(GetEnemy());
|
|
beamonce = 1;
|
|
TaskFail( "Player is not the enemy?!" );
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
//FUgly, yet I can't think right now a quicker solution to only make one beam on this loop case
|
|
if (beamonce == 1)
|
|
{
|
|
Write_BeamOn(GetEnemy());
|
|
beamonce++;
|
|
}
|
|
|
|
pEnemy->SetMoveType( MOVETYPE_FLY );
|
|
pEnemy->SetGravity( 0 );
|
|
|
|
// use exponential falloff to peg the player to the pin point
|
|
const Vector &desiredPos = pPinEnt->GetAbsOrigin();
|
|
const Vector &playerPos = pEnemy->GetAbsOrigin();
|
|
|
|
Vector displacement = desiredPos - playerPos;
|
|
|
|
float desiredDisplacementLen = ExponentialDecay(0.250f,gpGlobals->frametime);// * sqrt(displacementLen);
|
|
|
|
pEnemy->SetAbsVelocity( displacement * (1.0f - desiredDisplacementLen) * sk_advisor_pin_speed.GetFloat() );
|
|
|
|
break;
|
|
|
|
}
|
|
#ifdef EZ2
|
|
case TASK_ADVISOR_STAGGER:
|
|
SetCondition( COND_ADVISOR_STAGGER );
|
|
// Make it look like we're taking damage even though we're taking very little
|
|
m_iHealth += 19;
|
|
OnTakeDamage( info );
|
|
TaskComplete();
|
|
break;
|
|
|
|
case TASK_ADVISOR_WAIT_FOR_ACTIVITY:
|
|
if (GetIdealActivity() != ACT_IDLE && IsActivityFinished())
|
|
{
|
|
SetIdealActivity( ACT_IDLE );
|
|
TaskComplete();
|
|
return;
|
|
}
|
|
break;
|
|
|
|
// HACKHACK: If we're running a script, make sure we still pin the player
|
|
case TASK_PRE_SCRIPT:
|
|
case TASK_ENABLE_SCRIPT:
|
|
case TASK_WAIT_FOR_SCRIPT:
|
|
case TASK_FACE_SCRIPT:
|
|
case TASK_PLAY_SCRIPT:
|
|
case TASK_PLAY_SCRIPT_POST_IDLE:
|
|
{
|
|
if (m_hPlayerPinPos)
|
|
{
|
|
m_playerPinFailsafeTime = gpGlobals->curtime + 10.0f;
|
|
ChainRunTask( TASK_ADVISOR_PIN_PLAYER );
|
|
}
|
|
|
|
BaseClass::RunTask( pTask );
|
|
break;
|
|
}
|
|
#endif
|
|
default:
|
|
{
|
|
BaseClass::RunTask( pTask );
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
// Failsafe for restoring mobility to the player if the Advisor gets killed while PinPlayer task
|
|
//----------------------------------------------------------------------------------------------
|
|
void CNPC_Advisor::Event_Killed(const CTakeDamageInfo &info)
|
|
{
|
|
m_OnDeath.FireOutput(info.GetAttacker(), this);
|
|
#ifndef EZ2
|
|
if (info.GetAttacker())
|
|
{
|
|
info.GetAttacker()->SetGravity(1.0f);
|
|
info.GetAttacker()->SetMoveType(MOVETYPE_WALK);
|
|
Write_BeamOff(info.GetAttacker());
|
|
beamonce = 1;
|
|
}
|
|
#else
|
|
CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
|
|
if ( pLocalPlayer )
|
|
{
|
|
pLocalPlayer->SetGravity( 1.0f );
|
|
pLocalPlayer->SetMoveType( MOVETYPE_WALK );
|
|
Write_BeamOff( pLocalPlayer );
|
|
beamonce = 1;
|
|
}
|
|
|
|
StopFlying();
|
|
#endif
|
|
|
|
BaseClass::Event_Killed(info);
|
|
}
|
|
|
|
#ifdef EZ2
|
|
bool CNPC_Advisor::IsShieldOn( void )
|
|
{
|
|
return m_bThrowBeamShield || ( m_bPsychicShieldOn && !HasCondition( COND_ADVISOR_STAGGER ) );
|
|
}
|
|
|
|
void CNPC_Advisor::ApplyShieldedEffects( )
|
|
{
|
|
// Modulus with the skin allows multiple sets of shielded / unshielded skins
|
|
if ( IsAlive() && IsShieldOn() && m_nSkin % 2 == ADVISOR_SKIN_NOSHIELD )
|
|
{
|
|
EmitSound( "NPC_Advisor.shieldup" );
|
|
SetBloodColor( DONT_BLEED );
|
|
SetEyeState( ADVISOR_EYE_NORMAL ); // Refresh eye state
|
|
m_nSkin++;
|
|
}
|
|
else if ( !IsShieldOn() && m_nSkin % 2 == ADVISOR_SKIN_SHIELD)
|
|
{
|
|
EmitSound( "NPC_Advisor.shielddown" );
|
|
SetBloodColor( BLOOD_COLOR_GREEN );
|
|
SetEyeState( ADVISOR_EYE_NORMAL ); // Refresh eye state
|
|
m_nSkin--;
|
|
}
|
|
}
|
|
|
|
int CNPC_Advisor::TranslateSchedule( int scheduleType )
|
|
{
|
|
switch (scheduleType)
|
|
{
|
|
|
|
case SCHED_IDLE_STAND:
|
|
{
|
|
return SCHED_ADVISOR_IDLE_STAND;
|
|
break;
|
|
}
|
|
|
|
case SCHED_MELEE_ATTACK1:
|
|
{
|
|
return SCHED_ADVISOR_MELEE_ATTACK1;
|
|
break;
|
|
}
|
|
|
|
case SCHED_CHASE_ENEMY:
|
|
{
|
|
return SCHED_STANDOFF;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return BaseClass::TranslateSchedule( scheduleType );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
Activity CNPC_Advisor::NPC_TranslateActivity( Activity activity )
|
|
{
|
|
if (activity == ACT_MELEE_ATTACK1)
|
|
{
|
|
// Determine activity based on arm type
|
|
switch (GetCurrentAccessories())
|
|
{
|
|
case ADVISOR_ACCESSORY_ARMS_STUBBED: // Try to attack fruitlessly
|
|
case ADVISOR_ACCESSORY_ARMS:
|
|
{
|
|
// Alternate between left, right, and both
|
|
switch (RandomInt( 0, 2 ))
|
|
{
|
|
case 0: break;
|
|
case 1: activity = ACT_MELEE_ATTACK2; break;
|
|
case 2: activity = ACT_MELEE_ATTACK_SWING; break;
|
|
}
|
|
}
|
|
break;
|
|
case ADVISOR_ACCESSORY_R_ARM:
|
|
// Maintain ACT_MELEE_ATTACK1
|
|
break;
|
|
case ADVISOR_ACCESSORY_L_ARM:
|
|
activity = ACT_MELEE_ATTACK2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return BaseClass::NPC_TranslateActivity( activity );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_Advisor::GetCurrentAccessories()
|
|
{
|
|
// For now, try to identify from the bodygroup
|
|
int iArms = FindBodygroupByName( "arms" );
|
|
if (iArms == -1)
|
|
return ADVISOR_ACCESSORY_NONE;
|
|
|
|
int iBody = GetBodygroup( iArms );
|
|
|
|
// The advisor model starts with arms as the first and no arms as the second.
|
|
// Beyond that, just use the value directly.
|
|
if (iBody == 0)
|
|
return ADVISOR_ACCESSORY_ARMS;
|
|
else if (iBody == 1)
|
|
return ADVISOR_ACCESSORY_NONE;
|
|
|
|
return iBody;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::SetCurrentAccessories( AdvisorAccessory_t iNewAccessory )
|
|
{
|
|
// For now, try to identify from the bodygroup
|
|
int iArms = FindBodygroupByName( "arms" );
|
|
if (iArms == -1)
|
|
return;
|
|
|
|
// The advisor model starts with arms as the first and no arms as the second.
|
|
// Beyond that, just use the value directly.
|
|
if (iNewAccessory == ADVISOR_ACCESSORY_NONE)
|
|
iNewAccessory = ADVISOR_ACCESSORY_ARMS;
|
|
else if (iNewAccessory == ADVISOR_ACCESSORY_ARMS)
|
|
iNewAccessory = ADVISOR_ACCESSORY_NONE;
|
|
|
|
SetBodygroup( iArms, iNewAccessory );
|
|
}
|
|
|
|
extern CBaseAnimating *CreateServerRagdollSubmodel( CBaseAnimating *pOwner, const char *pModelName, const Vector &position, const QAngle &angles, int collisionGroup );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CBaseAnimating *CNPC_Advisor::CreateDetachedArm( bool bLeft )
|
|
{
|
|
const char *pszModelName = bLeft ? "models/advisor_combat_arm_l_detached.mdl" : "models/advisor_combat_arm_r_detached.mdl";
|
|
|
|
CBaseAnimating *pAnimating = CreateServerRagdollSubmodel( this, pszModelName, GetAbsOrigin(), GetAbsAngles(), COLLISION_GROUP_DEBRIS );
|
|
if (!pAnimating)
|
|
return NULL;
|
|
|
|
pAnimating->m_nSkin = m_nSkin;
|
|
if (IsShieldOn())
|
|
pAnimating->m_nSkin--; // Don't use shield skin
|
|
|
|
// For now, fade out after about 3 minutes
|
|
pAnimating->SUB_StartFadeOut( 180.0f, false );
|
|
|
|
Vector vecVelocity;
|
|
AngularImpulse vecAngVelocity;
|
|
GetVelocity( &vecVelocity, &vecAngVelocity );
|
|
|
|
IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
|
|
int count = pAnimating->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
|
|
if ( count )
|
|
{
|
|
for ( int i = 0; i < count; i++ )
|
|
{
|
|
pList[i]->SetVelocity( &vecVelocity, &vecAngVelocity );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// failed to create a physics object
|
|
UTIL_Remove( pAnimating );
|
|
return NULL;
|
|
}
|
|
|
|
return pAnimating;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Create an advisor flyer and start the think function
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::StartFlying()
|
|
{
|
|
if ( m_hAdvisorFlyer == NULL )
|
|
{
|
|
m_hAdvisorFlyer = CreateNoSpawn( "npc_advisor_flyer", GetAbsOrigin(), GetAbsAngles(), this );
|
|
m_hAdvisorFlyer->KeyValue( "SpotlightDisabled", "1" );
|
|
m_hAdvisorFlyer->Spawn();
|
|
m_hAdvisorFlyer->AcceptInput( "DisablePhotos", this, this, variant_t(), 0 );
|
|
|
|
if ( GetEnemy() )
|
|
{
|
|
m_hAdvisorFlyer->MyNPCPointer()->UpdateEnemyMemory( GetEnemy(), GetEnemyLKP() );
|
|
}
|
|
}
|
|
|
|
m_hFollowTarget = m_hAdvisorFlyer;
|
|
|
|
SetContextThink( &CNPC_Advisor::FlyThink, gpGlobals->curtime + TICK_INTERVAL, ADVISOR_FLY_THINK );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Destroy the advisor flyer and stop think function
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::StopFlying()
|
|
{
|
|
if ( m_hAdvisorFlyer )
|
|
{
|
|
m_hAdvisorFlyer->SUB_Remove();
|
|
m_hAdvisorFlyer = NULL;
|
|
|
|
m_hFollowTarget = NULL;
|
|
|
|
SetContextThink( NULL, 0.0f, ADVISOR_FLY_THINK );
|
|
}
|
|
}
|
|
|
|
void CNPC_Advisor::FlyThink()
|
|
{
|
|
UpdateAdvisorFacing();
|
|
|
|
if ( m_hFollowTarget )
|
|
{
|
|
// Something went bad! The flyer is too far, let's teleport
|
|
if ( m_hFollowTarget->GetAbsOrigin().DistTo( GetAbsOrigin() ) > 512.0f )
|
|
{
|
|
// Fire a mind blast effect to blind the player while we do something janky
|
|
m_OnMindBlast.FireOutput( this, this, 0.0f );
|
|
|
|
DispatchParticleEffect( "tele_warp", PATTACH_ABSORIGIN_FOLLOW, this, 0, false );
|
|
Teleport( &m_hFollowTarget->GetAbsOrigin(), &GetAbsAngles(), &GetAbsVelocity() );
|
|
}
|
|
// Move towards the advisor flyer
|
|
else
|
|
{
|
|
// Gotta go fast!
|
|
float flAdvisorFlightSpeed = m_hFollowTarget == m_hAdvisorFlyer ? 128.0f : 1.0f;
|
|
SetAbsVelocity( ( m_hFollowTarget->GetAbsOrigin() - GetAbsOrigin() ) * flAdvisorFlightSpeed );
|
|
}
|
|
}
|
|
|
|
SetContextThink( &CNPC_Advisor::FlyThink, gpGlobals->curtime + TICK_INTERVAL, ADVISOR_FLY_THINK );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Face the target
|
|
// In the original Episode 2 code, advisors would always face towards the player
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::UpdateAdvisorFacing()
|
|
{
|
|
/*if (m_hAdvisorFlyer)
|
|
{
|
|
Vector vecDir;
|
|
AngleVectors( m_hAdvisorFlyer->GetAbsAngles(), &vecDir );
|
|
float flYaw = UTIL_VecToYaw( vecDir );
|
|
float flPitch = -UTIL_VecToPitch( vecDir ); // Invert flyer pitch
|
|
|
|
GetAdvisorMotor()->SetIdealYaw( flYaw );
|
|
GetAdvisorMotor()->SetIdealPitch( flPitch );
|
|
}
|
|
else*/ if (advisor_use_facing_override.GetBool())
|
|
{
|
|
switch ( GetState() )
|
|
{
|
|
case NPC_STATE_COMBAT:
|
|
GetAdvisorMotor()->SetIdealYawAndPitchToTarget( GetEnemyLKP()/*, AI_KEEP_YAW_SPEED*/ );
|
|
break;
|
|
case NPC_STATE_SCRIPT:
|
|
GetAdvisorMotor()->SetIdealPitch( 0.0f );
|
|
break;
|
|
default:
|
|
if ( GetLooktarget() )
|
|
{
|
|
GetAdvisorMotor()->SetIdealYawAndPitchToTarget( GetLooktarget()->GetAbsOrigin()/*, AI_KEEP_YAW_SPEED*/ );
|
|
}
|
|
else if ( HasCondition( COND_SEE_PLAYER ) )
|
|
{
|
|
CBasePlayer * pPlayer = AI_GetSinglePlayer();
|
|
GetAdvisorMotor()->SetIdealYawAndPitchToTarget( pPlayer->GetAbsOrigin()/*, AI_KEEP_YAW_SPEED*/ );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (advisor_update_yaw.GetBool())
|
|
{
|
|
//GetMotor()->SetIdealYaw( CalcReasonableFacing( true ) );
|
|
GetMotor()->UpdateYaw( -1 );
|
|
}
|
|
|
|
if (GetState() != NPC_STATE_SCRIPT)
|
|
{
|
|
if (advisor_use_flyer_poses.GetBool())
|
|
{
|
|
Vector vecForward;
|
|
GetVectors( &vecForward, NULL, NULL );
|
|
float flCurrentYaw = UTIL_VecToYaw( vecForward );
|
|
float flCurrentPitch = UTIL_VecToPitch( vecForward );
|
|
|
|
float flYawDiff = RemapValClamped( -AngleDistance(flCurrentYaw, GetMotor()->GetIdealYaw()), -30.0, 30.0f, -75.0f, 75.0f );
|
|
float flPitchDiff = RemapValClamped( -AngleDistance(flCurrentPitch, GetAdvisorMotor()->GetIdealPitch()), -30.0, 30.0f, -75.0f, 75.0f );
|
|
|
|
float yaw = GetPoseParameter( m_ParameterFlexHorz );
|
|
float pitch = GetPoseParameter( m_ParameterFlexVert );
|
|
|
|
SetPoseParameter( m_ParameterFlexHorz, UTIL_Approach( flYawDiff, yaw, MaxYawSpeed() ) );
|
|
SetPoseParameter( m_ParameterFlexVert, UTIL_Approach( flPitchDiff, pitch, MaxPitchSpeed() ) );
|
|
|
|
// Set arms extent as combination of velocity and distance to enemy
|
|
// (higher up = more constricted)
|
|
float flArmsExtent;
|
|
if (GetEnemy())
|
|
{
|
|
Vector vecDelta = (GetAbsOrigin() - GetEnemy()->GetAbsOrigin());
|
|
float flVelocityLengthSqr = GetLocalVelocity().LengthSqr() + GetLocalAngularVelocity().LengthSqr();
|
|
flArmsExtent = RemapVal( (flVelocityLengthSqr + vecDelta.LengthSqr()), 0, Square( 1000 ), 0.0f, 1.0f );
|
|
}
|
|
else
|
|
{
|
|
flArmsExtent = (GetAbsVelocity().Length()) * 0.005f;
|
|
}
|
|
//Msg( "flYawDiff: %f, flArmsExtent: %f (yaw: %f, ideal yaw: %f)\n", flYawDiff, flArmsExtent, flCurrentYaw, GetMotor()->GetIdealYaw() );
|
|
SetPoseParameter( m_ParameterArmsExtent, flArmsExtent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetPoseParameter( m_ParameterFlexHorz, 0.0f );
|
|
SetPoseParameter( m_ParameterFlexVert, 0.0f );
|
|
SetPoseParameter( m_ParameterArmsExtent, 0.0f );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::StartSounds( void )
|
|
{
|
|
//Initialize the additive sound channels
|
|
CPASAttenuationFilter filter( this );
|
|
|
|
if (m_pBreathSound == NULL)
|
|
{
|
|
m_pBreathSound = CSoundEnvelopeController::GetController().SoundCreate( filter, entindex(), CHAN_ITEM, "NPC_Advisor.BreatheLoop", ATTN_NORM );
|
|
|
|
if (m_pBreathSound)
|
|
{
|
|
CSoundEnvelopeController::GetController().Play( m_pBreathSound, 0.5f, 100 );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::StopLoopingSounds()
|
|
{
|
|
//Stop all sounds
|
|
CSoundEnvelopeController::GetController().SoundDestroy( m_pBreathSound );
|
|
|
|
m_pBreathSound = NULL;
|
|
|
|
BaseClass::StopLoopingSounds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_Advisor::HandleInteraction( int interactionType, void *data, CBaseCombatCharacter *sourceEnt )
|
|
{
|
|
if ( interactionType == g_interactionXenGrenadeConsume )
|
|
{
|
|
DevMsg( "Advisor %s has been displaced by the Xen grenade or displacer pistol!\n", GetDebugName() );
|
|
// "Don't be sucked up" case, which is default
|
|
|
|
// Invoke effects similar to that of being lifted by a barnacle.
|
|
AddEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
AddSolidFlags( FSOLID_NOT_SOLID );
|
|
AddEffects( EF_NODRAW );
|
|
KillSprites( 0.0f );
|
|
SetSleepState( AISS_IGNORE_INPUT );
|
|
Sleep();
|
|
|
|
Write_AllBeamsOff();
|
|
|
|
IPhysicsObject *pPhys = VPhysicsGetObject();
|
|
if (pPhys)
|
|
{
|
|
pPhys->EnableMotion( false );
|
|
}
|
|
|
|
if ( m_bAdvisorFlyer )
|
|
{
|
|
StopFlying();
|
|
}
|
|
|
|
SetAbsVelocity( vec3_origin );
|
|
|
|
// Make all children nodraw
|
|
CBaseEntity *pChild = FirstMoveChild();
|
|
while (pChild)
|
|
{
|
|
pChild->AddEffects( EF_NODRAW );
|
|
pChild = pChild->NextMovePeer();
|
|
}
|
|
|
|
SetNextThink( TICK_NEVER_THINK );
|
|
|
|
// Return true to indicate we shouldn't be sucked up
|
|
return true;
|
|
}
|
|
|
|
if ( interactionType == g_interactionXenGrenadeRelease )
|
|
{
|
|
DevMsg( "Advisor %s has been released by the Xen grenade or displacer pistol!\n", GetDebugName() );
|
|
DisplacementInfo_t *info = static_cast<DisplacementInfo_t*>( data );
|
|
if ( !info )
|
|
return false;
|
|
|
|
Teleport( info->vecTargetPos, &info->pDisplacer->GetAbsAngles(), NULL );
|
|
|
|
|
|
Wake();
|
|
StartEye();
|
|
RemoveEFlags( EFL_IS_BEING_LIFTED_BY_BARNACLE );
|
|
RemoveSolidFlags( FSOLID_NOT_SOLID );
|
|
RemoveEffects( EF_NODRAW );
|
|
|
|
IPhysicsObject *pPhys = VPhysicsGetObject();
|
|
if (pPhys)
|
|
{
|
|
pPhys->EnableMotion( true );
|
|
pPhys->Wake();
|
|
}
|
|
|
|
// Make all children draw
|
|
CBaseEntity *pChild = FirstMoveChild();
|
|
while (pChild)
|
|
{
|
|
pChild->RemoveEffects( EF_NODRAW );
|
|
pChild = pChild->NextMovePeer();
|
|
}
|
|
|
|
if ( m_bAdvisorFlyer )
|
|
{
|
|
StartFlying();
|
|
}
|
|
|
|
CBaseEntity * pAttacker = info->pOwner == NULL ? this : info->pOwner;
|
|
CTakeDamageInfo damageInfo( pAttacker, pAttacker, vec3_origin, GetAbsOrigin(), 100, DMG_BLAST );
|
|
OnTakeDamage( damageInfo );
|
|
|
|
// Pretend we spawned from a recipe
|
|
info->pSink->SpawnEffects( this );
|
|
|
|
m_fStaggerUntilTime = gpGlobals->curtime + 3.0f;
|
|
|
|
SetNextThink( gpGlobals->curtime );
|
|
|
|
return true;
|
|
}
|
|
|
|
if ( interactionType == g_interactionXenGrenadePull )
|
|
{
|
|
DevMsg( "Advisor being pulled!\n" );
|
|
m_fStaggerUntilTime = gpGlobals->curtime + 1.0f;
|
|
|
|
// Let the advisor be pulled normally
|
|
return false;
|
|
}
|
|
|
|
if ( interactionType == g_interactionXenGrenadeRagdoll )
|
|
{
|
|
// Don't.
|
|
return true;
|
|
}
|
|
|
|
// TODO - Add an interaction for telefragging
|
|
|
|
return BaseClass::HandleInteraction( interactionType, data, sourceEnt );
|
|
}
|
|
|
|
//=========================================================
|
|
// TraceAttack is overridden here to handle shield impacts.
|
|
//=========================================================
|
|
void CNPC_Advisor::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
|
|
{
|
|
m_fNoDamageDecal = false;
|
|
|
|
QAngle vecAngles;
|
|
VectorAngles( ptr->plane.normal, vecAngles );
|
|
|
|
// Shield particle
|
|
if ( IsShieldOn() )
|
|
{
|
|
DispatchParticleEffect( "warp_shield_impact_BMan", ptr->endpos, vecAngles, this );
|
|
|
|
// Make a blocked sound
|
|
EmitSound( "NPC_Advisor.shieldblock" );
|
|
|
|
// Do not handle trace
|
|
m_fNoDamageDecal = true;
|
|
|
|
// Fire a fake bullet in the normal's direction
|
|
//FireBulletsInfo_t bulletsInfo;
|
|
//bulletsInfo.m_iShots = 1;
|
|
//bulletsInfo.m_vecSrc = ptr->endpos;
|
|
//bulletsInfo.m_vecDirShooting = ptr->plane.normal;
|
|
//bulletsInfo.m_vecSpread = VECTOR_CONE_5DEGREES;
|
|
//bulletsInfo.m_flDistance = MAX_TRACE_LENGTH;
|
|
//bulletsInfo.m_iAmmoType = 0;
|
|
//bulletsInfo.m_flDamage = 0;
|
|
//bulletsInfo.m_iTracerFreq = 1;
|
|
//FireBullets( bulletsInfo );
|
|
|
|
// Make one spark fly off to represent the bullet that hit the shield
|
|
g_pEffects->MetalSparks( ptr->endpos, ptr->plane.normal );
|
|
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Dispatch a bleeding particle effect
|
|
DispatchParticleEffect( RandomInt(0,1) ? "blood_advisor_shrapnel_spurt_1" : "blood_advisor_shrapnel_spurt_2", ptr->endpos, vecAngles, this );
|
|
//DispatchParticleEffect( RandomInt(0,1) ? "blood_advisor_shrapnel_spurt_1" : "blood_advisor_shrapnel_spurt_2", ptr->endpos, vecAngles, this, PATTACH_ABSORIGIN_FOLLOW );
|
|
}
|
|
|
|
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Custom trace filter used for advisor staging LOS traces
|
|
//-----------------------------------------------------------------------------
|
|
class CTraceFilterAdvisorStage : public CTraceFilterSkipTwoEntities
|
|
{
|
|
public:
|
|
CTraceFilterAdvisorStage( IHandleEntity *pHandleEntity, int collisionGroup, IHandleEntity *pHandleEntity2 = NULL )
|
|
: CTraceFilterSkipTwoEntities( pHandleEntity, pHandleEntity2, collisionGroup )
|
|
{
|
|
m_pAdvisor = static_cast<CNPC_Advisor *>(pHandleEntity);
|
|
}
|
|
|
|
bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
|
|
{
|
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
|
|
|
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
// Don't hit staged props
|
|
if ( m_pAdvisor->IsThisPropStaged( pEntity ) )
|
|
return false;
|
|
}
|
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask );
|
|
}
|
|
|
|
private:
|
|
CNPC_Advisor *m_pAdvisor;
|
|
};
|
|
#endif
|
|
|
|
|
|
#endif
|
|
|
|
// helper function for testing whether or not an avisor is allowed to grab an object
|
|
static bool AdvisorCanPickObject( CBasePlayer *pPlayer, CBaseEntity *pEnt, CNPC_Advisor *pAdvisor = NULL )
|
|
{
|
|
#ifdef EZ2
|
|
if (pPlayer != NULL)
|
|
{
|
|
#endif
|
|
Assert( pPlayer != NULL );
|
|
|
|
// Is the player carrying something?
|
|
CBaseEntity *pHeldObject = GetPlayerHeldEntity(pPlayer);
|
|
|
|
if( !pHeldObject )
|
|
{
|
|
pHeldObject = PhysCannonGetHeldEntity( pPlayer->GetActiveWeapon() );
|
|
}
|
|
|
|
if( pHeldObject == pEnt )
|
|
{
|
|
return false;
|
|
}
|
|
#ifdef EZ2
|
|
}
|
|
|
|
// Is this object within mass thresholds?
|
|
if ( pEnt->GetMass() < advisor_throw_stage_min_mass.GetInt() || pEnt->GetMass() > advisor_throw_stage_max_mass.GetInt() )
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if ( pEnt->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#ifdef EZ2
|
|
if (pAdvisor)
|
|
{
|
|
// Can the prop reach our first staging position?
|
|
// TODO: Check all staging positions
|
|
CBaseEntity *pStagingPos = pAdvisor->GetFirstStagingPosition();
|
|
if (pStagingPos)
|
|
{
|
|
CTraceFilterAdvisorStage traceFilter( pAdvisor, COLLISION_GROUP_NONE, pEnt );
|
|
trace_t tr;
|
|
|
|
Vector stagingPos = pStagingPos->GetAbsOrigin();
|
|
AI_TraceLine( pEnt->GetAbsOrigin(), stagingPos, MASK_SOLID, &traceFilter, &tr );
|
|
|
|
if (tr.fraction != 1.0f)
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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() );
|
|
#ifndef EZ2
|
|
Assert(pPlayer);
|
|
if (!pPlayer)
|
|
return NULL;
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifdef EZ2
|
|
if (pThrowEnt == GetEnemy()) // Don't pick our enemy
|
|
continue;
|
|
#endif
|
|
|
|
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);
|
|
|
|
// If pPhys is NULL, skip
|
|
if (pPhys == NULL)
|
|
{
|
|
DevWarning("WARNING: Advisor attempted to push NULL physics object out of the way of an object.\n");
|
|
continue;
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
#ifdef EZ2
|
|
// I AM BULLETPROOF
|
|
if ( IsShieldOn() )
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// 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);
|
|
}
|
|
|
|
#ifdef EZ2
|
|
float flHealthRatio = (float)GetHealth() / (float)GetMaxHealth();
|
|
if ( flHealthRatio < advisor_camera_flash_health_ratio.GetFloat() )
|
|
{
|
|
SetEyeState( ADVISOR_EYE_DAMAGED );
|
|
}
|
|
else
|
|
{
|
|
SetEyeState( ADVISOR_EYE_UNDAMAGED );
|
|
}
|
|
|
|
// TODO: This needs an elaborate clientside implementation in order to function
|
|
//if ( flHealthRatio < advisor_bleed_health_ratio.GetFloat() )
|
|
//{
|
|
// if (!m_bBleeding)
|
|
// {
|
|
// //DispatchParticleEffect( "blood_antlionguard_injured_heavy", PATTACH_POINT_FOLLOW, this, gm_nMouthAttachment );
|
|
// //DispatchParticleEffect( "blood_drip_synth_01", PATTACH_ABSORIGIN_FOLLOW, this );
|
|
// m_bBleeding = true;
|
|
// }
|
|
//}
|
|
//else if (m_bBleeding)
|
|
//{
|
|
// // HACKHACK
|
|
// StopParticleEffects( this );
|
|
// m_bBleeding = false;
|
|
//}
|
|
|
|
if (advisor_hurt_pose.GetBool())
|
|
{
|
|
SetPoseParameter( m_ParameterHurt, 1.0f - flHealthRatio );
|
|
}
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
|
|
#if NPC_ADVISOR_HAS_BEHAVIOR
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For innate melee attack
|
|
// Input :
|
|
// Output :
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_Advisor::MeleeAttack1Conditions(float flDot, float flDist)
|
|
{
|
|
if (flDist > 128)
|
|
{
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
}
|
|
else if (flDot < 0.7)
|
|
{
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
return COND_CAN_MELEE_ATTACK1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Returns the best new schedule for this NPC based on current conditions.
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_Advisor::SelectSchedule()
|
|
{
|
|
#ifndef EZ2
|
|
if ( IsInAScript() )
|
|
return SCHED_ADVISOR_IDLE_STAND;
|
|
#else
|
|
if ( HasCondition( COND_ADVISOR_STAGGER ) )
|
|
{
|
|
Write_AllBeamsOff();
|
|
ApplyShieldedEffects();
|
|
return SCHED_ADVISOR_STAGGER;
|
|
}
|
|
#endif
|
|
|
|
// Kick attack?
|
|
if (HasCondition(COND_CAN_MELEE_ATTACK1))
|
|
{
|
|
return SCHED_MELEE_ATTACK1;
|
|
}
|
|
|
|
#ifdef EZ2
|
|
// Don't pin the player immediately after throwing an object!
|
|
if ( IsAlive() && m_hPlayerPinPos.IsValid() && gpGlobals->curtime - m_flLastThrowTime > sk_advisor_pin_throw_cooldown.GetFloat() )
|
|
{
|
|
ClearCondition( COND_ADVISOR_FORCE_PIN );
|
|
return SCHED_ADVISOR_TOSS_PLAYER;
|
|
}
|
|
#endif
|
|
|
|
switch ( m_NPCState )
|
|
{
|
|
#ifndef EZ2
|
|
case NPC_STATE_IDLE:
|
|
case NPC_STATE_ALERT:
|
|
{
|
|
|
|
return SCHED_ADVISOR_IDLE_STAND;
|
|
}
|
|
#endif
|
|
|
|
case NPC_STATE_COMBAT:
|
|
{
|
|
if (GetEnemy() && GetEnemy()->IsAlive() && HasCondition(COND_HAVE_ENEMY_LOS))
|
|
{
|
|
#ifndef EZ2
|
|
if ( m_hPlayerPinPos.IsValid() )//false
|
|
return SCHED_ADVISOR_TOSS_PLAYER;
|
|
else
|
|
#endif
|
|
return SCHED_ADVISOR_COMBAT;
|
|
|
|
}
|
|
|
|
#ifndef EZ2
|
|
return SCHED_ADVISOR_IDLE_STAND;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
#ifdef EZ2
|
|
//-----------------------------------------------------------------------------
|
|
// TASK_RANGE_ATTACK1
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::BuildScheduleTestBits()
|
|
{
|
|
BaseClass::BuildScheduleTestBits();
|
|
|
|
// Don't interrupt scripts. RunTask() allows pinning to occur simultaneously
|
|
if ( GetState() != NPC_STATE_SCRIPT )
|
|
{
|
|
SetCustomInterruptCondition( COND_ADVISOR_FORCE_PIN );
|
|
}
|
|
|
|
if ( IsCurSchedule( SCHED_ADVISOR_COMBAT, false ) )
|
|
{
|
|
// Don't interrupt during barrage task
|
|
if (!GetTask() || GetTask()->iTask != TASK_ADVISOR_BARRAGE_OBJECTS)
|
|
SetCustomInterruptCondition( COND_CAN_MELEE_ATTACK1 );
|
|
}
|
|
//else if ( IsCurSchedule( SCHED_MELEE_ATTACK1, false ) )
|
|
//{
|
|
// // Don't get stuck trying to face
|
|
// if (GetTask() && GetTask()->iTask == TASK_FACE_ENEMY)
|
|
// SetCustomInterruptCondition( COND_TOO_FAR_TO_ATTACK );
|
|
//}
|
|
else if ( IsCurSchedule( SCHED_STANDOFF, false ) )
|
|
{
|
|
// Interrupt when we can use our combat schedule again
|
|
SetCustomInterruptCondition( COND_HAVE_ENEMY_LOS );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::PrescheduleThink()
|
|
{
|
|
BaseClass::PrescheduleThink();
|
|
|
|
if ( IsPreparingToThrow() && m_iThrowAnimDir != 2 )
|
|
{
|
|
Vector vecLookOrigin = m_hvStagedEnts[0]->GetAbsOrigin();
|
|
|
|
if (m_hvStagedEnts.Count() > 1)
|
|
{
|
|
// Get a vague center
|
|
for (int i = 1; i < m_hvStagedEnts.Count(); i++)
|
|
{
|
|
if (m_hvStagedEnts[i])
|
|
vecLookOrigin += ((vecLookOrigin - m_hvStagedEnts[i]->GetAbsOrigin()) * 0.5f);
|
|
}
|
|
}
|
|
|
|
AddLookTarget( vecLookOrigin, 1.0, 0.5 );
|
|
}
|
|
else if ( IsCurSchedule( SCHED_ADVISOR_COMBAT, false ) )
|
|
{
|
|
AddLookTarget( GetEnemy(), 1.0, 0.5/*, 0.2*/ );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CNPC_Advisor::HandleAnimEvent(animevent_t *pEvent)
|
|
{
|
|
if (pEvent->type & AE_TYPE_NEWEVENTSYSTEM)
|
|
{
|
|
if ( pEvent->event == ADVISOR_AE_DETACH_R_ARM )
|
|
{
|
|
inputdata_t dummy;
|
|
InputDetachRightArm( dummy );
|
|
}
|
|
else if ( pEvent->event == ADVISOR_AE_DETACH_L_ARM )
|
|
{
|
|
inputdata_t dummy;
|
|
InputDetachLeftArm( dummy );
|
|
}
|
|
else
|
|
{
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (pEvent->event)
|
|
{
|
|
case ADVISOR_MELEE_LEFT:
|
|
{
|
|
#ifdef EZ2
|
|
// If we don't have a left arm, early out
|
|
int iAccessory = GetCurrentAccessories();
|
|
if (iAccessory != ADVISOR_ACCESSORY_ARMS && iAccessory != ADVISOR_ACCESSORY_L_ARM)
|
|
break;
|
|
#endif
|
|
|
|
CBaseEntity *pHurt = CheckTraceHullAttack(70, -Vector(128, 128, 224), Vector(128, 128, 64), sk_advisor_melee_dmg.GetFloat(), DMG_SLASH);
|
|
if (pHurt)
|
|
{
|
|
Vector forward, up;
|
|
AngleVectors(GetLocalAngles(), &forward, NULL, &up);
|
|
|
|
if (pHurt->GetFlags() & (FL_NPC | FL_CLIENT))
|
|
{
|
|
pHurt->ViewPunch(QAngle(5, 0, random->RandomInt(-10, 10)));
|
|
}
|
|
// Spawn some extra blood if we hit a BCC
|
|
CBaseCombatCharacter* pBCC = ToBaseCombatCharacter(pHurt);
|
|
if (pBCC)
|
|
{
|
|
SpawnBlood(pBCC->EyePosition(), g_vecAttackDir, pBCC->BloodColor(), sk_advisor_melee_dmg.GetFloat());
|
|
}
|
|
// Play a attack hit sound
|
|
#ifdef EZ2
|
|
EmitSound("NPC_Advisor.Melee_Hit");
|
|
#else
|
|
EmitSound("NPC_Stalker.Hit");
|
|
#endif
|
|
}
|
|
#ifdef EZ2
|
|
else
|
|
{
|
|
EmitSound( "NPC_Advisor.Melee_Miss" );
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case ADVISOR_MELEE_RIGHT:
|
|
{
|
|
#ifdef EZ2
|
|
// If we don't have a right arm, early out
|
|
int iAccessory = GetCurrentAccessories();
|
|
if (iAccessory != ADVISOR_ACCESSORY_ARMS && iAccessory != ADVISOR_ACCESSORY_R_ARM)
|
|
break;
|
|
#endif
|
|
|
|
CBaseEntity *pHurt = CheckTraceHullAttack(70, -Vector(128, 128, 224), Vector(128, 128, 64), sk_advisor_melee_dmg.GetFloat(), DMG_SLASH);
|
|
if (pHurt)
|
|
{
|
|
Vector forward, up;
|
|
AngleVectors(GetLocalAngles(), &forward, NULL, &up);
|
|
|
|
if (pHurt->GetFlags() & (FL_NPC | FL_CLIENT))
|
|
{
|
|
pHurt->ViewPunch(QAngle(5, 0, random->RandomInt(-10, 10)));
|
|
}
|
|
// Spawn some extra blood if we hit a BCC
|
|
CBaseCombatCharacter* pBCC = ToBaseCombatCharacter(pHurt);
|
|
if (pBCC)
|
|
{
|
|
SpawnBlood(pBCC->EyePosition(), g_vecAttackDir, pBCC->BloodColor(), sk_advisor_melee_dmg.GetFloat());
|
|
}
|
|
// Play a attack hit sound
|
|
#ifdef EZ2
|
|
EmitSound("NPC_Advisor.Melee_Hit");
|
|
#else
|
|
EmitSound("NPC_Stalker.Hit");
|
|
#endif
|
|
}
|
|
#ifdef EZ2
|
|
else
|
|
{
|
|
EmitSound( "NPC_Advisor.Melee_Miss" );
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
default:
|
|
BaseClass::HandleAnimEvent(pEvent);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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();
|
|
|
|
#ifdef EZ2
|
|
if ( GetModelName() == NULL_STRING )
|
|
{
|
|
SetModelName( AllocPooledString( "models/advisor_combat.mdl" ) );
|
|
}
|
|
|
|
UTIL_PrecacheOther( "npc_advisor_flyer" );
|
|
|
|
PrecacheScriptSound( "NPC_Advisor.BreatheLoop" );
|
|
PrecacheScriptSound( "NPC_Advisor.shieldblock" );
|
|
PrecacheScriptSound( "NPC_Advisor.shieldup" );
|
|
PrecacheScriptSound( "NPC_Advisor.shielddown" );
|
|
PrecacheScriptSound( "DoSpark" );
|
|
|
|
PrecacheParticleSystem( "Advisor_Psychic_Shield_Idle" );
|
|
//PrecacheParticleSystem( "Advisor_Throw_Obj_Charge" );
|
|
PrecacheParticleSystem( "tele_warp" );
|
|
|
|
PrecacheParticleSystem( "blood_advisor_shrapnel_impact" );
|
|
PrecacheParticleSystem( "blood_advisor_shrapnel_spurt_1" );
|
|
PrecacheParticleSystem( "blood_advisor_shrapnel_spurt_2" );
|
|
//PrecacheParticleSystem( "blood_antlionguard_injured_heavy" );
|
|
//PrecacheParticleSystem( "blood_drip_synth_01" );
|
|
#endif
|
|
|
|
PrecacheModel( STRING( GetModelName() ) );
|
|
|
|
#if NPC_ADVISOR_HAS_BEHAVIOR
|
|
PrecacheModel( "sprites/lgtning.vmt" );
|
|
#endif
|
|
|
|
#ifdef EZ2
|
|
PrecacheModel( "models/advisor_combat_arm_r_detached.mdl" );
|
|
PrecacheModel( "models/advisor_combat_arm_l_detached.mdl" );
|
|
#endif
|
|
|
|
PrecacheScriptSound( "NPC_Advisor.Blast" );
|
|
PrecacheScriptSound( "BaseCombatCharacter.CorpseGib" ); //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");
|
|
|
|
#ifdef EZ2
|
|
PrecacheScriptSound( "NPC_Advisor.Melee_Hit" );
|
|
PrecacheScriptSound( "NPC_Advisor.Melee_Miss" );
|
|
|
|
PrecacheScriptSound( "NPC_Advisor.ArmDetach" );
|
|
#else
|
|
PrecacheScriptSound("NPC_Stalker.Hit");
|
|
#endif
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
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());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Sets the number of staging points to levitate the props before being launched
|
|
//-----------------------------------------------------------------------------
|
|
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;
|
|
#ifdef EZ2
|
|
SetCondition( COND_ADVISOR_FORCE_PIN );
|
|
#endif
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//Stops the Advisor to try pin the player into a point in space
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::StopPinPlayer(inputdata_t &inputdata)
|
|
{
|
|
#ifdef EZ2
|
|
ClearCondition( COND_ADVISOR_FORCE_PIN );
|
|
#endif
|
|
m_hPlayerPinPos = NULL;
|
|
}
|
|
|
|
#ifdef EZ2
|
|
void CNPC_Advisor::InputStartFlying( inputdata_t & inputdata )
|
|
{
|
|
// Make sure to persist the "Advisor Flying" key value if the advisor receives an input to start or stop flying
|
|
// This is important for displacement via the xen grenade or displacer pistol
|
|
m_bAdvisorFlyer = true;
|
|
StartFlying();
|
|
}
|
|
|
|
void CNPC_Advisor::InputStopFlying( inputdata_t & inputdata )
|
|
{
|
|
// Make sure to persist the "Advisor Flying" key value if the advisor receives an input to start or stop flying
|
|
// This is important for displacement via the xen grenade or displacer pistol
|
|
m_bAdvisorFlyer = false;
|
|
StopFlying();
|
|
}
|
|
|
|
void CNPC_Advisor::InputSetFollowTarget( inputdata_t & inputdata )
|
|
{
|
|
m_hFollowTarget = inputdata.value.Entity();
|
|
|
|
if (m_hFollowTarget)
|
|
SetContextThink( &CNPC_Advisor::FlyThink, gpGlobals->curtime + TICK_INTERVAL, ADVISOR_FLY_THINK );
|
|
else
|
|
SetContextThink( NULL, 0.0f, ADVISOR_FLY_THINK );
|
|
}
|
|
|
|
void CNPC_Advisor::InputStartPsychicShield( inputdata_t & inputdata )
|
|
{
|
|
m_bPsychicShieldOn = true;
|
|
ApplyShieldedEffects();
|
|
}
|
|
|
|
void CNPC_Advisor::InputStopPsychicShield( inputdata_t & inputdata )
|
|
{
|
|
m_bPsychicShieldOn = false;
|
|
ApplyShieldedEffects();
|
|
}
|
|
|
|
void CNPC_Advisor::InputDetachRightArm( inputdata_t &inputdata )
|
|
{
|
|
int iCurAccessory = GetCurrentAccessories();
|
|
AdvisorAccessory_t iNewBody = ADVISOR_ACCESSORY_NONE;
|
|
|
|
switch (iCurAccessory)
|
|
{
|
|
case ADVISOR_ACCESSORY_ARMS:
|
|
iNewBody = ADVISOR_ACCESSORY_L_ARM;
|
|
break;
|
|
|
|
case ADVISOR_ACCESSORY_R_ARM:
|
|
iNewBody = ADVISOR_ACCESSORY_ARMS_STUBBED;
|
|
break;
|
|
}
|
|
|
|
if (iNewBody != ADVISOR_ACCESSORY_NONE)
|
|
{
|
|
SetCurrentAccessories( iNewBody );
|
|
|
|
EmitSound( "NPC_Advisor.ArmDetach" );
|
|
|
|
CreateDetachedArm( false );
|
|
}
|
|
}
|
|
|
|
void CNPC_Advisor::InputDetachLeftArm( inputdata_t &inputdata )
|
|
{
|
|
int iCurAccessory = GetCurrentAccessories();
|
|
AdvisorAccessory_t iNewBody = ADVISOR_ACCESSORY_NONE;
|
|
|
|
switch (iCurAccessory)
|
|
{
|
|
case ADVISOR_ACCESSORY_ARMS:
|
|
iNewBody = ADVISOR_ACCESSORY_R_ARM;
|
|
break;
|
|
|
|
case ADVISOR_ACCESSORY_L_ARM:
|
|
iNewBody = ADVISOR_ACCESSORY_ARMS_STUBBED;
|
|
break;
|
|
}
|
|
|
|
if (iNewBody != ADVISOR_ACCESSORY_NONE)
|
|
{
|
|
SetCurrentAccessories( iNewBody );
|
|
|
|
CreateDetachedArm( true );
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::OnScheduleChange( void )
|
|
{
|
|
Write_AllBeamsOff();
|
|
m_hvStagedEnts.RemoveAll();
|
|
|
|
#ifdef EZ2
|
|
if (!m_hPlayerPinPos)
|
|
{
|
|
// Make sure we're not pinning
|
|
CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
|
|
if (pLocalPlayer && pLocalPlayer->GetMoveType() == MOVETYPE_FLY)
|
|
{
|
|
inputdata_t dummy;
|
|
StopPinPlayer( dummy );
|
|
pLocalPlayer->SetGravity( 1.0f );
|
|
pLocalPlayer->SetMoveType( MOVETYPE_WALK );
|
|
Write_BeamOff( pLocalPlayer );
|
|
beamonce = 1;
|
|
}
|
|
}
|
|
|
|
SetEyeState( ADVISOR_EYE_NORMAL );
|
|
|
|
SetIdealActivity( ACT_IDLE );
|
|
#endif
|
|
|
|
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;
|
|
|
|
#ifdef EZ2
|
|
if (m_fStaggerUntilTime > gpGlobals->curtime)
|
|
{
|
|
// Pause flight
|
|
if ( m_bAdvisorFlyer && m_hAdvisorFlyer )
|
|
{
|
|
StopFlying();
|
|
}
|
|
|
|
// Stop pinning
|
|
inputdata_t dummy;
|
|
StopPinPlayer( dummy );
|
|
CBasePlayer *pLocalPlayer = AI_GetSinglePlayer();
|
|
if (pLocalPlayer)
|
|
{
|
|
pLocalPlayer->SetGravity( 1.0f );
|
|
pLocalPlayer->SetMoveType( MOVETYPE_WALK );
|
|
Write_BeamOff( pLocalPlayer );
|
|
beamonce = 1;
|
|
}
|
|
|
|
SetCondition( COND_ADVISOR_STAGGER );
|
|
}
|
|
else
|
|
{
|
|
// Resume flight
|
|
if ( m_bAdvisorFlyer && !m_hAdvisorFlyer )
|
|
{
|
|
StartFlying();
|
|
}
|
|
ClearCondition( COND_ADVISOR_STAGGER );
|
|
}
|
|
|
|
// Don't fall to ground!
|
|
ClearCondition( COND_FLOATING_OFF_GROUND );
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 );
|
|
|
|
// Protect against null pointer exceptions
|
|
if ( pEnt == NULL )
|
|
return;
|
|
|
|
#ifdef EZ2
|
|
m_bThrowBeamShield = true;
|
|
ApplyShieldedEffects();
|
|
#endif
|
|
|
|
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 );
|
|
|
|
// Protect against null pointer exceptions
|
|
if ( pEnt == NULL )
|
|
return;
|
|
|
|
#ifdef EZ2
|
|
m_bThrowBeamShield = false;
|
|
ApplyShieldedEffects();
|
|
#endif
|
|
|
|
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 )
|
|
{
|
|
#ifdef EZ2
|
|
m_bThrowBeamShield = false;
|
|
ApplyShieldedEffects();
|
|
#endif
|
|
|
|
EntityMessageBegin( this, true );
|
|
WRITE_BYTE( ADVISOR_MSG_STOP_ALL_BEAMS );
|
|
MessageEnd();
|
|
}
|
|
|
|
#ifdef EZ2
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Overridden to handle particles
|
|
// Weird place to start this, but it worked great for Zombigaunts
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_Advisor::StartEye( void )
|
|
{
|
|
// Start particle
|
|
if (GetSleepState() == AISS_AWAKE)
|
|
{
|
|
SetContextThink( &CNPC_Advisor::ParticleThink, gpGlobals->curtime + 0.1, ADVISOR_PARTICLE_THINK );
|
|
}
|
|
|
|
BaseClass::StartEye();
|
|
}
|
|
|
|
|
|
void CNPC_Advisor::ParticleThink()
|
|
{
|
|
if ( m_bPsychicShieldOn && !HasCondition( COND_ADVISOR_STAGGER ) )
|
|
{
|
|
DispatchParticleEffect( "Advisor_Psychic_Shield_Idle", PATTACH_ABSORIGIN_FOLLOW, this, 0, false );
|
|
}
|
|
|
|
ApplyShieldedEffects();
|
|
|
|
float flHealthRatio = (float)GetHealth() / (float)GetMaxHealth();
|
|
if ( flHealthRatio < advisor_sparks_health_ratio_max.GetFloat() )
|
|
{
|
|
// Random chance of sparks coming from one of our jets, increases with lower health
|
|
float flChance = RandomFloat( 0, flHealthRatio );
|
|
if (flChance <= advisor_sparks_health_ratio_min.GetFloat())
|
|
{
|
|
Vector vecSparkPosition, vecSparkDir;
|
|
switch (RandomInt( 0, 3 ))
|
|
{
|
|
case 0: GetAttachment( gm_nLFrontTopJetAttachment, vecSparkPosition, &vecSparkDir ); break;
|
|
case 1: GetAttachment( gm_nRFrontTopJetAttachment, vecSparkPosition, &vecSparkDir ); break;
|
|
case 2: GetAttachment( gm_nLRearTopJetAttachment, vecSparkPosition, &vecSparkDir ); break;
|
|
case 3: GetAttachment( gm_nRRearTopJetAttachment, vecSparkPosition, &vecSparkDir ); break;
|
|
}
|
|
|
|
g_pEffects->Sparks( vecSparkPosition, (1.0f / flHealthRatio), RandomInt( 1, 4 ), &vecSparkDir );
|
|
EmitSound( "DoSpark" );
|
|
}
|
|
}
|
|
|
|
SetNextThink( gpGlobals->curtime + 1.0f, ADVISOR_PARTICLE_THINK );
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
float CNPC_AdvisorFlyer::GetGoalDistance()
|
|
{
|
|
if (GetOwnerEntity())
|
|
{
|
|
CNPC_Advisor *pAdvisor = static_cast<CNPC_Advisor *>(GetOwnerEntity());
|
|
|
|
// If preparing to throw or have no defenses, get away
|
|
if (pAdvisor->IsPreparingToThrow() || !(pAdvisor->CapabilitiesGet() & bits_CAP_INNATE_MELEE_ATTACK1))
|
|
{
|
|
return BaseClass::GetGoalDistance() * 2.5f;
|
|
}
|
|
}
|
|
|
|
return BaseClass::GetGoalDistance();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
float CNPC_AdvisorFlyer::MinGroundDist()
|
|
{
|
|
if (GetOwnerEntity())
|
|
{
|
|
CNPC_Advisor *pAdvisor = static_cast<CNPC_Advisor *>(GetOwnerEntity());
|
|
|
|
// If the advisor is in melee range, get closer
|
|
if (pAdvisor->HasCondition( COND_CAN_MELEE_ATTACK1 ))
|
|
{
|
|
return 72.0f;
|
|
}
|
|
}
|
|
|
|
return 96.0f;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
float CAI_AdvisorMotor::GetPitchSpeed() const
|
|
{
|
|
if (GetAdvisorOuter())
|
|
{
|
|
return GetAdvisorOuter()->MaxPitchSpeed();
|
|
}
|
|
|
|
return 18.0f;
|
|
}
|
|
|
|
inline CNPC_Advisor *CAI_AdvisorMotor::GetAdvisorOuter()
|
|
{
|
|
return static_cast<CNPC_Advisor*>(GetOuter());
|
|
}
|
|
|
|
inline const CNPC_Advisor *CAI_AdvisorMotor::GetAdvisorOuter() const
|
|
{
|
|
return static_cast<const CNPC_Advisor*>(GetOuter());
|
|
}
|
|
|
|
inline CNPC_AdvisorFlyer *CAI_AdvisorMotor::GetAdvisorFlyer()
|
|
{
|
|
return GetAdvisorOuter()->GetAdvisorFlyer();
|
|
}
|
|
|
|
inline const CNPC_AdvisorFlyer *CAI_AdvisorMotor::GetAdvisorFlyer() const
|
|
{
|
|
return GetAdvisorOuter()->GetAdvisorFlyer();
|
|
}
|
|
#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.
|
|
CBaseEntity *pEnt = static_cast<CBaseEntity *>(pObject->GetGameData());
|
|
if (pAdvisor->DidThrow(pEnt))
|
|
{
|
|
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 );
|
|
}
|
|
}
|
|
|
|
#ifdef EZ2
|
|
// DEACTIVATED FOR NOW
|
|
// We could really use a solution which pushes props we aren't staging
|
|
/*
|
|
float flOurRadius = 64.0f;
|
|
if (pEnt)
|
|
flOurRadius = pEnt->BoundingRadius() * 1.5f;
|
|
|
|
Vector vecAdvisorCenter = pAdvisor->GetAbsOrigin();
|
|
float flAdvisorDistSqr = (vecAdvisorCenter - pos).LengthSqr() - Square( flOurRadius );
|
|
if (flAdvisorDistSqr <= Square(advisor_push_max_radius.GetFloat()))
|
|
{
|
|
Vector vecAdvisorVel;
|
|
VectorMultiply( pAdvisor->GetAbsVelocity(), advisor_push_delta.GetFloat(), vecAdvisorVel );
|
|
|
|
float flTotalRadius = flOurRadius + pAdvisor->BoundingRadius() * 2.0f;
|
|
float t1, t2;
|
|
if ( IntersectRayWithSphere( vecAdvisorCenter, vecAdvisorVel,
|
|
pos, flTotalRadius, &t1, &t2 ) )
|
|
{
|
|
// Push it away from the advisor's path
|
|
Vector vecClosest;
|
|
CalcClosestPointOnLineSegment( pos, vecAdvisorCenter, vecAdvisorCenter + vecAdvisorVel, vecClosest, NULL );
|
|
|
|
Vector vecForceDir = (pos - vecClosest);
|
|
VectorNormalize( vecForceDir );
|
|
vecForceDir *= pObject->GetMass();
|
|
|
|
//angular += vecForceDir;
|
|
|
|
// The closer we are to the advisor, the more force we'll get
|
|
vecForceDir *= RemapVal( flAdvisorDistSqr, Square(advisor_push_max_radius.GetFloat()), 0.0f, 0.0f, advisor_push_max_force_scale.GetFloat() );
|
|
|
|
linear += vecForceDir;
|
|
}
|
|
}
|
|
*/
|
|
#endif
|
|
|
|
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 )
|
|
|
|
#ifdef EZ2
|
|
DECLARE_ACTIVITY( ACT_ADVISOR_OBJECT_R_HOLD )
|
|
DECLARE_ACTIVITY( ACT_ADVISOR_OBJECT_R_THROW )
|
|
DECLARE_ACTIVITY( ACT_ADVISOR_OBJECT_L_HOLD )
|
|
DECLARE_ACTIVITY( ACT_ADVISOR_OBJECT_L_THROW )
|
|
DECLARE_ACTIVITY( ACT_ADVISOR_OBJECT_BOTH_HOLD )
|
|
DECLARE_ACTIVITY( ACT_ADVISOR_OBJECT_BOTH_THROW )
|
|
|
|
DECLARE_ANIMEVENT( ADVISOR_AE_DETACH_R_ARM )
|
|
DECLARE_ANIMEVENT( ADVISOR_AE_DETACH_L_ARM )
|
|
#endif
|
|
|
|
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
|
|
#ifdef EZ2
|
|
DECLARE_CONDITION( COND_ADVISOR_FORCE_PIN )
|
|
DECLARE_CONDITION( COND_ADVISOR_STAGGER )
|
|
#endif
|
|
|
|
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
|
|
|
|
#ifdef EZ2
|
|
DECLARE_TASK( TASK_ADVISOR_STAGGER )
|
|
DECLARE_TASK( TASK_ADVISOR_WAIT_FOR_ACTIVITY )
|
|
#endif
|
|
|
|
#ifndef EZ2
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ADVISOR_COMBAT,
|
|
|
|
" Tasks"
|
|
" TASK_ADVISOR_FIND_OBJECTS 0"
|
|
" TASK_ADVISOR_LEVITATE_OBJECTS 0"
|
|
" TASK_ADVISOR_STAGE_OBJECTS 1"
|
|
" TASK_FACE_ENEMY 0"
|
|
" TASK_ADVISOR_BARRAGE_OBJECTS 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_ADVISOR_PHASE_INTERRUPT"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_CAN_MELEE_ATTACK1"
|
|
)
|
|
|
|
//=========================================================
|
|
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_FACE_ENEMY 0"
|
|
" TASK_ADVISOR_PIN_PLAYER 0"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
#else
|
|
//=========================================================
|
|
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"
|
|
" TASK_ADVISOR_WAIT_FOR_ACTIVITY 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_ADVISOR_PHASE_INTERRUPT"
|
|
" COND_ENEMY_DEAD"
|
|
//" COND_CAN_MELEE_ATTACK1" // See BuildScheduleTestBits()
|
|
" COND_ADVISOR_STAGGER"
|
|
)
|
|
|
|
//=========================================================
|
|
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_FACE_ENEMY 0"
|
|
" TASK_ADVISOR_PIN_PLAYER 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_ADVISOR_STAGGER"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ADVISOR_STAGGER,
|
|
|
|
" Tasks"
|
|
" TASK_ADVISOR_STAGGER 0"
|
|
" TASK_BIG_FLINCH 0"
|
|
" TASK_WAIT 1"
|
|
""
|
|
" Interrupts"
|
|
" COND_ADVISOR_PHASE_INTERRUPT"
|
|
)
|
|
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_ADVISOR_MELEE_ATTACK1,
|
|
|
|
" Tasks"
|
|
" TASK_MELEE_ATTACK1 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
#endif
|
|
|
|
AI_END_CUSTOM_NPC()
|
|
#endif
|