hl2_src-leak-2017/src/game/server/hl2/npc_rollermine.cpp

3036 lines
83 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_squadslot.h"
#include "ai_basenpc.h"
#include "ai_navigator.h"
#include "ai_interactions.h"
#include "ndebugoverlay.h"
#include "explode.h"
#include "bitstring.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "decals.h"
#include "antlion_dust.h"
#include "ai_memory.h"
#include "ai_squad.h"
#include "ai_senses.h"
#include "beam_shared.h"
#include "iservervehicle.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "physics_saverestore.h"
#include "vphysics/constraints.h"
#include "vehicle_base.h"
#include "eventqueue.h"
#include "te_effect_dispatch.h"
#include "npc_rollermine.h"
#include "func_break.h"
#include "soundenvelope.h"
#include "mapentities.h"
#include "RagdollBoogie.h"
#include "physics_collisionevent.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define ROLLERMINE_MAX_TORQUE_FACTOR 5
extern short g_sModelIndexWExplosion;
ConVar sk_rollermine_shock( "sk_rollermine_shock","0");
ConVar sk_rollermine_stun_delay("sk_rollermine_stun_delay", "1");
ConVar sk_rollermine_vehicle_intercept( "sk_rollermine_vehicle_intercept","1");
enum
{
ROLLER_SKIN_REGULAR = 0,
ROLLER_SKIN_FRIENDLY,
ROLLER_SKIN_DETONATE,
};
//-----------------------------------------------------------------------------
// CRollerController implementation
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Purpose: This class only implements the IMotionEvent-specific behavior
// It keeps track of the forces so they can be integrated
//-----------------------------------------------------------------------------
class CRollerController : public IMotionEvent
{
DECLARE_SIMPLE_DATADESC();
public:
IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
AngularImpulse m_vecAngular;
Vector m_vecLinear;
void Off( void ) { m_fIsStopped = true; }
void On( void ) { m_fIsStopped = false; }
bool IsOn( void ) { return !m_fIsStopped; }
private:
bool m_fIsStopped;
};
BEGIN_SIMPLE_DATADESC( CRollerController )
DEFINE_FIELD( m_vecAngular, FIELD_VECTOR ),
DEFINE_FIELD( m_vecLinear, FIELD_VECTOR ),
DEFINE_FIELD( m_fIsStopped, FIELD_BOOLEAN ),
END_DATADESC()
//-----------------------------------------------------------------------------
IMotionEvent::simresult_e CRollerController::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
if( m_fIsStopped )
{
return SIM_NOTHING;
}
linear = m_vecLinear;
angular = m_vecAngular;
return IMotionEvent::SIM_LOCAL_ACCELERATION;
}
//-----------------------------------------------------------------------------
#define ROLLERMINE_IDLE_SEE_DIST 2048
#define ROLLERMINE_NORMAL_SEE_DIST 2048
#define ROLLERMINE_WAKEUP_DIST 256
#define ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE 300 // See every other than vehicles upto this distance (i.e. old idle see dist)
#define ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL 800 // See every other than vehicles upto this distance (i.e. old normal see dist)
#define ROLLERMINE_RETURN_TO_PLAYER_DIST (200*200)
#define ROLLERMINE_MIN_ATTACK_DIST 1
#define ROLLERMINE_MAX_ATTACK_DIST 4096
#define ROLLERMINE_OPEN_THRESHOLD 256
#define ROLLERMINE_VEHICLE_OPEN_THRESHOLD 400
#define ROLLERMINE_VEHICLE_HOP_THRESHOLD 300
#define ROLLERMINE_HOP_DELAY 2 // Don't allow hops faster than this
//#define ROLLERMINE_REQUIRED_TO_EXPLODE_VEHICLE 4
#define ROLLERMINE_FEAR_DISTANCE (300*300)
//=========================================================
// Custom schedules
//=========================================================
enum
{
SCHED_ROLLERMINE_RANGE_ATTACK1 = LAST_SHARED_SCHEDULE,
SCHED_ROLLERMINE_CHASE_ENEMY,
SCHED_ROLLERMINE_BURIED_WAIT,
SCHED_ROLLERMINE_BURIED_UNBURROW,
SCHED_ROLLERMINE_FLEE,
SCHED_ROLLERMINE_ALERT_STAND,
SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES,
SCHED_ROLLERMINE_PATH_TO_PLAYER,
SCHED_ROLLERMINE_ROLL_TO_PLAYER,
SCHED_ROLLERMINE_POWERDOWN,
};
//=========================================================
// Custom tasks
//=========================================================
enum
{
TASK_ROLLERMINE_CHARGE_ENEMY = LAST_SHARED_TASK,
TASK_ROLLERMINE_BURIED_WAIT,
TASK_ROLLERMINE_UNBURROW,
TASK_ROLLERMINE_GET_PATH_TO_FLEE,
TASK_ROLLERMINE_NUDGE_TOWARDS_NODES,
TASK_ROLLERMINE_RETURN_TO_PLAYER,
TASK_ROLLERMINE_POWERDOWN,
};
// This are little 'sound event' flags. Set the flag after you play the
// sound, and the sound will not be allowed to play until the flag is then cleared.
#define ROLLERMINE_SE_CLEAR 0x00000000
#define ROLLERMINE_SE_CHARGE 0x00000001
#define ROLLERMINE_SE_TAUNT 0x00000002
#define ROLLERMINE_SE_SHARPEN 0x00000004
#define ROLLERMINE_SE_TOSSED 0x00000008
enum rollingsoundstate_t { ROLL_SOUND_NOT_READY = 0, ROLL_SOUND_OFF, ROLL_SOUND_CLOSED, ROLL_SOUND_OPEN };
//=========================================================
//=========================================================
class CNPC_RollerMine : public CNPCBaseInteractive<CAI_BaseNPC>, public CDefaultPlayerPickupVPhysics
{
DECLARE_CLASS( CNPC_RollerMine, CNPCBaseInteractive<CAI_BaseNPC> );
DECLARE_SERVERCLASS();
public:
CNPC_RollerMine( void ) { m_bTurnedOn = true; m_bUniformSight = false; }
~CNPC_RollerMine( void );
void Spawn( void );
bool CreateVPhysics();
void RunAI();
void StartTask( const Task_t *pTask );
void RunTask( const Task_t *pTask );
void SpikeTouch( CBaseEntity *pOther );
void ShockTouch( CBaseEntity *pOther );
void CloseTouch( CBaseEntity *pOther );
void EmbedTouch( CBaseEntity *pOther );
float GetAttackDamageScale( CBaseEntity *pVictim );
void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
void Precache( void );
void OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason );
void OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason );
void StopLoopingSounds( void );
void PrescheduleThink();
bool ShouldSavePhysics() { return true; }
void OnRestore();
void Bury( trace_t *tr );
bool QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC = false );
int RangeAttack1Conditions ( float flDot, float flDist );
int SelectSchedule( void );
int TranslateSchedule( int scheduleType );
int GetHackedIdleSchedule( void );
bool OverrideMove( float flInterval ) { return true; }
bool IsValidEnemy( CBaseEntity *pEnemy );
bool IsPlayerVehicle( CBaseEntity *pEntity );
bool IsShocking() { return gpGlobals->curtime < m_flShockTime ? true : false; }
void UpdateRollingSound();
void UpdatePingSound();
void StopRollingSound();
void StopPingSound();
float RollingSpeed();
float GetStunDelay();
void EmbedOnGroundImpact();
void UpdateEfficiency( bool bInPVS ) { SetEfficiency( ( GetSleepState() != AISS_AWAKE ) ? AIE_DORMANT : AIE_NORMAL ); SetMoveEfficiency( AIME_NORMAL ); }
void DrawDebugGeometryOverlays()
{
if (m_debugOverlays & OVERLAY_BBOX_BIT)
{
float dist = GetSenses()->GetDistLook();
Vector range(dist, dist, 64);
NDebugOverlay::Box( GetAbsOrigin(), -range, range, 255, 0, 0, 0, 0 );
}
BaseClass::DrawDebugGeometryOverlays();
}
// UNDONE: Put this in the qc file!
Vector EyePosition()
{
// This takes advantage of the fact that the system knows
// that the abs origin is at the center of the rollermine
// and that the OBB is actually world-aligned despite the
// fact that SOLID_VPHYSICS is being used
Vector eye = CollisionProp()->GetCollisionOrigin();
eye.z += CollisionProp()->OBBMaxs().z;
return eye;
}
int OnTakeDamage( const CTakeDamageInfo &info );
void TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator );
Class_T Classify()
{
if( !m_bTurnedOn )
return CLASS_NONE;
//About to blow up after being hacked so do damage to the player.
if ( m_bHackedByAlyx && ( m_flPowerDownDetonateTime > 0.0f && m_flPowerDownDetonateTime <= gpGlobals->curtime ) )
return CLASS_COMBINE;
return ( m_bHeld || m_bHackedByAlyx ) ? CLASS_HACKED_ROLLERMINE : CLASS_COMBINE;
}
virtual bool ShouldGoToIdleState()
{
return gpGlobals->curtime > m_flGoIdleTime ? true : false;
}
virtual void OnStateChange( NPC_STATE OldState, NPC_STATE NewState );
// Vehicle interception
bool EnemyInVehicle( void );
float VehicleHeading( CBaseEntity *pVehicle );
NPC_STATE SelectIdealState();
// Vehicle sticking
void StickToVehicle( CBaseEntity *pOther );
void AnnounceArrivalToOthers( CBaseEntity *pOther );
void UnstickFromVehicle( void );
CBaseEntity *GetVehicleStuckTo( void );
int CountRollersOnMyVehicle( CUtlVector<CNPC_RollerMine*> *pRollerList );
void InputConstraintBroken( inputdata_t &inputdata );
void InputRespondToChirp( inputdata_t &inputdata );
void InputRespondToExplodeChirp( inputdata_t &inputdata );
void InputJoltVehicle( inputdata_t &inputdata );
void InputTurnOn( inputdata_t &inputdata );
void InputTurnOff( inputdata_t &inputdata );
void InputPowerdown( inputdata_t &inputdata );
void PreventUnstickUntil( float flTime ) { m_flPreventUnstickUntil = flTime; }
virtual unsigned int PhysicsSolidMaskForEntity( void ) const;
void SetRollerSkin( void );
COutputEvent m_OnPhysGunDrop;
COutputEvent m_OnPhysGunPickup;
protected:
DEFINE_CUSTOM_AI;
DECLARE_DATADESC();
bool BecomePhysical();
void WakeNeighbors();
bool WakeupMine( CAI_BaseNPC *pNPC );
void Open( void );
void Close( void );
void Explode( void );
void PreDetonate( void );
void Hop( float height );
void ShockTarget( CBaseEntity *pOther );
bool IsActive() { return m_flActiveTime > gpGlobals->curtime ? false : true; }
// INPCInteractive Functions
virtual bool CanInteractWith( CAI_BaseNPC *pUser ) { return true; }
virtual bool HasBeenInteractedWith() { return m_bHackedByAlyx; }
virtual void NotifyInteraction( CAI_BaseNPC *pUser );
CSoundPatch *m_pRollSound;
CSoundPatch *m_pPingSound;
CRollerController m_RollerController;
IPhysicsMotionController *m_pMotionController;
float m_flSeeVehiclesOnlyBeyond;
float m_flChargeTime;
float m_flGoIdleTime;
float m_flShockTime;
float m_flForwardSpeed;
int m_iSoundEventFlags;
rollingsoundstate_t m_rollingSoundState;
CNetworkVar( bool, m_bIsOpen );
CNetworkVar( float, m_flActiveTime ); //If later than the current time, this will force the mine to be active
bool m_bHeld; //Whether or not the player is holding the mine
EHANDLE m_hVehicleStuckTo;
float m_flPreventUnstickUntil;
float m_flNextHop;
bool m_bStartBuried;
bool m_bBuried;
bool m_bIsPrimed;
bool m_wakeUp;
bool m_bEmbedOnGroundImpact;
CNetworkVar( bool, m_bHackedByAlyx );
// Constraint used to stick us to a vehicle
IPhysicsConstraint *m_pConstraint;
bool m_bTurnedOn;
bool m_bUniformSight;
CNetworkVar( bool, m_bPowerDown );
float m_flPowerDownTime;
float m_flPowerDownDetonateTime;
static string_t gm_iszDropshipClassname;
};
string_t CNPC_RollerMine::gm_iszDropshipClassname;
LINK_ENTITY_TO_CLASS( npc_rollermine, CNPC_RollerMine );
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
BEGIN_DATADESC( CNPC_RollerMine )
DEFINE_SOUNDPATCH( m_pRollSound ),
DEFINE_SOUNDPATCH( m_pPingSound ),
DEFINE_EMBEDDED( m_RollerController ),
DEFINE_PHYSPTR( m_pMotionController ),
DEFINE_FIELD( m_flSeeVehiclesOnlyBeyond, FIELD_FLOAT ),
DEFINE_FIELD( m_flActiveTime, FIELD_TIME ),
DEFINE_FIELD( m_flChargeTime, FIELD_TIME ),
DEFINE_FIELD( m_flGoIdleTime, FIELD_TIME ),
DEFINE_FIELD( m_flShockTime, FIELD_TIME ),
DEFINE_FIELD( m_flForwardSpeed, FIELD_FLOAT ),
DEFINE_FIELD( m_bIsOpen, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hVehicleStuckTo, FIELD_EHANDLE ),
DEFINE_FIELD( m_flPreventUnstickUntil, FIELD_TIME ),
DEFINE_FIELD( m_flNextHop, FIELD_FLOAT ),
DEFINE_FIELD( m_bIsPrimed, FIELD_BOOLEAN ),
DEFINE_FIELD( m_iSoundEventFlags, FIELD_INTEGER ),
DEFINE_FIELD( m_rollingSoundState, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_bStartBuried, FIELD_BOOLEAN, "StartBuried" ),
DEFINE_FIELD( m_bBuried, FIELD_BOOLEAN ),
DEFINE_FIELD( m_wakeUp, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bEmbedOnGroundImpact, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bPowerDown, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flPowerDownTime, FIELD_TIME ),
DEFINE_FIELD( m_flPowerDownDetonateTime, FIELD_TIME ),
DEFINE_PHYSPTR( m_pConstraint ),
DEFINE_FIELD( m_bTurnedOn, FIELD_BOOLEAN ),
DEFINE_KEYFIELD( m_bUniformSight, FIELD_BOOLEAN, "uniformsightdist" ),
DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputConstraintBroken ),
DEFINE_INPUTFUNC( FIELD_VOID, "RespondToChirp", InputRespondToChirp ),
DEFINE_INPUTFUNC( FIELD_VOID, "RespondToExplodeChirp", InputRespondToExplodeChirp ),
DEFINE_INPUTFUNC( FIELD_VOID, "JoltVehicle", InputJoltVehicle ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
DEFINE_INPUTFUNC( FIELD_VOID, "PowerDown", InputPowerdown ),
// Function Pointers
DEFINE_ENTITYFUNC( SpikeTouch ),
DEFINE_ENTITYFUNC( ShockTouch ),
DEFINE_ENTITYFUNC( CloseTouch ),
DEFINE_ENTITYFUNC( EmbedTouch ),
DEFINE_THINKFUNC( Explode ),
DEFINE_THINKFUNC( PreDetonate ),
DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
DEFINE_BASENPCINTERACTABLE_DATADESC(),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CNPC_RollerMine, DT_RollerMine )
SendPropInt(SENDINFO(m_bIsOpen), 1, SPROP_UNSIGNED ),
SendPropFloat(SENDINFO(m_flActiveTime), 0, SPROP_NOSCALE ),
SendPropInt(SENDINFO(m_bHackedByAlyx), 1, SPROP_UNSIGNED ),
SendPropInt(SENDINFO(m_bPowerDown), 1, SPROP_UNSIGNED ),
END_SEND_TABLE()
bool NPC_Rollermine_IsRollermine( CBaseEntity *pEntity )
{
CNPC_RollerMine *pRoller = dynamic_cast<CNPC_RollerMine *>(pEntity);
return pRoller ? true : false;
}
CBaseEntity *NPC_Rollermine_DropFromPoint( const Vector &originStart, CBaseEntity *pOwner, const char *pszTemplate )
{
CBaseEntity *pEntity = NULL;
CNPC_RollerMine *pMine = NULL;
// Use the template, if we have it
if ( pszTemplate && pszTemplate[0] )
{
MapEntity_ParseEntity( pEntity, pszTemplate, NULL );
pMine = dynamic_cast<CNPC_RollerMine *>(pEntity);
}
else
{
pMine = (CNPC_RollerMine*)CreateEntityByName("npc_rollermine");
}
if ( pMine )
{
pMine->SetAbsOrigin( originStart );
pMine->SetOwnerEntity( pOwner );
pMine->Spawn();
if ( !pszTemplate || !pszTemplate[0] )
{
pMine->EmbedOnGroundImpact();
}
}
else
{
Warning( "NULL Ent in Rollermine Create!\n" );
}
return pMine;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNPC_RollerMine::~CNPC_RollerMine( void )
{
if ( m_pMotionController != NULL )
{
physenv->DestroyMotionController( m_pMotionController );
m_pMotionController = NULL;
}
UnstickFromVehicle();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Precache( void )
{
PrecacheModel( "models/roller.mdl" );
PrecacheModel( "models/roller_spikes.mdl" );
PrecacheModel( "sprites/bluelight1.vmt" );
PrecacheModel( "sprites/rollermine_shock.vmt" );
PrecacheModel( "sprites/rollermine_shock_yellow.vmt" );
PrecacheScriptSound( "NPC_RollerMine.Taunt" );
PrecacheScriptSound( "NPC_RollerMine.OpenSpikes" );
PrecacheScriptSound( "NPC_RollerMine.Warn" );
PrecacheScriptSound( "NPC_RollerMine.Shock" );
PrecacheScriptSound( "NPC_RollerMine.ExplodeChirp" );
PrecacheScriptSound( "NPC_RollerMine.Chirp" );
PrecacheScriptSound( "NPC_RollerMine.ChirpRespond" );
PrecacheScriptSound( "NPC_RollerMine.ExplodeChirpRespond" );
PrecacheScriptSound( "NPC_RollerMine.JoltVehicle" );
PrecacheScriptSound( "NPC_RollerMine.Tossed" );
PrecacheScriptSound( "NPC_RollerMine.Hurt" );
PrecacheScriptSound( "NPC_RollerMine.Roll" );
PrecacheScriptSound( "NPC_RollerMine.RollWithSpikes" );
PrecacheScriptSound( "NPC_RollerMine.Ping" );
PrecacheScriptSound( "NPC_RollerMine.Held" );
PrecacheScriptSound( "NPC_RollerMine.Reprogram" );
PrecacheMaterial( "effects/rollerglow" );
gm_iszDropshipClassname = AllocPooledString( "npc_combinedropship" ); // For fast string compares.
#ifdef HL2_EPISODIC
PrecacheScriptSound( "RagdollBoogie.Zap" );
#endif
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Spawn( void )
{
Precache();
SetSolid( SOLID_VPHYSICS );
AddSolidFlags( FSOLID_FORCE_WORLD_ALIGNED | FSOLID_NOT_STANDABLE );
BaseClass::Spawn();
AddEFlags( EFL_NO_DISSOLVE );
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_SQUAD );
m_pRollSound = NULL;
m_bIsOpen = true;
Close();
m_bPowerDown = false;
m_flFieldOfView = -1.0f;
m_flForwardSpeed = -1200;
m_bloodColor = DONT_BLEED;
SetHullType(HULL_SMALL_CENTERED);
SetHullSizeNormal();
m_flActiveTime = 0;
m_bBuried = m_bStartBuried;
if ( m_bStartBuried )
{
trace_t tr;
Bury( &tr );
}
NPCInit();
m_takedamage = DAMAGE_EVENTS_ONLY;
SetDistLook( ROLLERMINE_IDLE_SEE_DIST );
if( m_bUniformSight )
{
m_flSeeVehiclesOnlyBeyond = ROLLERMINE_IDLE_SEE_DIST;
}
else
{
m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE;
}
//Suppress superfluous warnings from animation system
m_flGroundSpeed = 20;
m_NPCState = NPC_STATE_NONE;
m_rollingSoundState = ROLL_SOUND_OFF;
m_pConstraint = NULL;
m_hVehicleStuckTo = NULL;
m_flPreventUnstickUntil = 0;
m_flNextHop = 0;
m_flPowerDownDetonateTime = 0.0f;
m_bPowerDown = false;
m_flPowerDownTime = 0.0f;
//Set their yaw speed to 0 so the motor doesn't rotate them.
GetMotor()->SetYawSpeed( 0.0f );
SetRollerSkin();
}
//-----------------------------------------------------------------------------
// Set the contents types that are solid by default to this NPC
//-----------------------------------------------------------------------------
unsigned int CNPC_RollerMine::PhysicsSolidMaskForEntity( void ) const
{
if ( HasSpawnFlags( SF_ROLLERMINE_PROP_COLLISION ) )
return MASK_SOLID;
return MASK_NPCSOLID;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Bury( trace_t *tr )
{
AI_TraceHull( GetAbsOrigin() + Vector(0,0,64), GetAbsOrigin() - Vector( 0, 0, MAX_TRACE_LENGTH ), Vector(-16,-16,-16), Vector(16,16,16), MASK_NPCSOLID, this, GetCollisionGroup(), tr );
//NDebugOverlay::Box( tr->startpos, Vector(-16,-16,-16), Vector(16,16,16), 255, 0, 0, 64, 10.0 );
//NDebugOverlay::Box( tr->endpos, Vector(-16,-16,-16), Vector(16,16,16), 0, 255, 0, 64, 10.0 );
// Move into the ground layer
Vector buriedPos = tr->endpos - Vector( 0, 0, GetHullHeight() * 0.5 );
Teleport( &buriedPos, NULL, &vec3_origin );
SetMoveType( MOVETYPE_NONE );
SetSchedule( SCHED_ROLLERMINE_BURIED_WAIT );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::WakeupMine( CAI_BaseNPC *pNPC )
{
if ( pNPC && pNPC->m_iClassname == m_iClassname && pNPC != this )
{
CNPC_RollerMine *pMine = dynamic_cast<CNPC_RollerMine *>(pNPC);
if ( pMine )
{
if ( pMine->m_NPCState == NPC_STATE_IDLE )
{
pMine->m_wakeUp = false;
pMine->SetIdealState( NPC_STATE_ALERT );
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::WakeNeighbors()
{
if ( !m_wakeUp || !IsActive() )
return;
m_wakeUp = false;
if ( m_pSquad )
{
AISquadIter_t iter;
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
{
WakeupMine( pSquadMember );
}
return;
}
CBaseEntity *entityList[64];
Vector range(ROLLERMINE_WAKEUP_DIST,ROLLERMINE_WAKEUP_DIST,64);
int boxCount = UTIL_EntitiesInBox( entityList, ARRAYSIZE(entityList), GetAbsOrigin()-range, GetAbsOrigin()+range, FL_NPC );
//NDebugOverlay::Box( GetAbsOrigin(), -range, range, 255, 0, 0, 64, 10.0 );
int wakeCount = 0;
while ( boxCount > 0 )
{
boxCount--;
CAI_BaseNPC *pNPC = entityList[boxCount]->MyNPCPointer();
if ( WakeupMine( pNPC ) )
{
wakeCount++;
if ( wakeCount >= 2 )
return;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnStateChange( NPC_STATE OldState, NPC_STATE NewState )
{
if ( NewState == NPC_STATE_IDLE )
{
SetDistLook( ROLLERMINE_IDLE_SEE_DIST );
m_flDistTooFar = ROLLERMINE_IDLE_SEE_DIST;
if( m_bUniformSight )
{
m_flSeeVehiclesOnlyBeyond = ROLLERMINE_IDLE_SEE_DIST;
}
else
{
m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_IDLE;
}
m_RollerController.m_vecAngular = vec3_origin;
m_wakeUp = true;
}
else
{
if ( OldState == NPC_STATE_IDLE )
{
// wake the neighbors!
WakeNeighbors();
}
SetDistLook( ROLLERMINE_NORMAL_SEE_DIST );
if( m_bUniformSight )
{
m_flSeeVehiclesOnlyBeyond = ROLLERMINE_NORMAL_SEE_DIST;
}
else
{
m_flSeeVehiclesOnlyBeyond = ROLLERMINE_SEE_VEHICLESONLY_BEYOND_NORMAL;
}
m_flDistTooFar = ROLLERMINE_NORMAL_SEE_DIST;
}
BaseClass::OnStateChange( OldState, NewState );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
NPC_STATE CNPC_RollerMine::SelectIdealState( void )
{
switch ( m_NPCState )
{
case NPC_STATE_COMBAT:
{
if ( HasCondition( COND_ENEMY_TOO_FAR ) )
{
ClearEnemyMemory();
SetEnemy( NULL );
m_flGoIdleTime = gpGlobals->curtime + 10;
return NPC_STATE_ALERT;
}
}
}
return BaseClass::SelectIdealState();
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::BecomePhysical( void )
{
VPhysicsDestroyObject();
RemoveSolidFlags( FSOLID_NOT_SOLID );
//Setup the physics controller on the roller
IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, GetSolidFlags() , false );
if ( pPhysicsObject == NULL )
return false;
m_pMotionController = physenv->CreateMotionController( &m_RollerController );
m_pMotionController->AttachObject( pPhysicsObject, true );
SetMoveType( MOVETYPE_VPHYSICS );
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnRestore()
{
BaseClass::OnRestore();
if ( m_pMotionController )
{
m_pMotionController->SetEventHandler( &m_RollerController );
}
// If we're stuck to a vehicle over a level transition, restart our jolt inputs
if ( GetVehicleStuckTo() )
{
if ( !g_EventQueue.HasEventPending( this, "JoltVehicle" ) )
{
g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::CreateVPhysics()
{
if ( m_bBuried )
{
VPhysicsInitStatic();
return true;
}
else
{
return BecomePhysical();
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_RollerMine::RunAI()
{
if( m_bTurnedOn )
{
// Scare combine if hacked by Alyx.
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
Vector vecVelocity;
if ( pPhysicsObject != NULL )
{
pPhysicsObject->GetVelocity( &vecVelocity, NULL );
}
if( !m_bHeld && vecVelocity.Length() > 64.0 )
{
if( m_bHackedByAlyx )
{
// Scare combine
CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_COMBINE_ONLY | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
}
else
{
// Scare player allies
CSoundEnt::InsertSound( (SOUND_DANGER | SOUND_CONTEXT_EXCLUDE_COMBINE | SOUND_CONTEXT_REACT_TO_SOURCE | SOUND_CONTEXT_DANGER_APPROACH), WorldSpaceCenter() + Vector( 0, 0, 32 ) + vecVelocity * 0.5f, 120.0f, 0.2f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
}
}
BaseClass::RunAI();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
int CNPC_RollerMine::RangeAttack1Conditions( float flDot, float flDist )
{
if( HasCondition( COND_SEE_ENEMY ) == false )
return COND_NONE;
if ( EnemyInVehicle() )
return COND_CAN_RANGE_ATTACK1;
if( flDist > ROLLERMINE_MAX_ATTACK_DIST )
return COND_TOO_FAR_TO_ATTACK;
if (flDist < ROLLERMINE_MIN_ATTACK_DIST )
return COND_TOO_CLOSE_TO_ATTACK;
return COND_CAN_RANGE_ATTACK1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
int CNPC_RollerMine::SelectSchedule( void )
{
if ( m_bPowerDown )
return SCHED_ROLLERMINE_POWERDOWN;
if ( m_bBuried )
{
if ( HasCondition(COND_NEW_ENEMY) || HasCondition(COND_LIGHT_DAMAGE) )
return SCHED_ROLLERMINE_BURIED_UNBURROW;
return SCHED_ROLLERMINE_BURIED_WAIT;
}
//If we're held, don't try and do anything
if ( ( m_bHeld ) || !IsActive() || m_hVehicleStuckTo )
return SCHED_ALERT_STAND;
// If we can see something we're afraid of, run from it
if ( HasCondition( COND_SEE_FEAR ) )
return SCHED_ROLLERMINE_FLEE;
switch( m_NPCState )
{
case NPC_STATE_COMBAT:
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
return SCHED_ROLLERMINE_RANGE_ATTACK1;
return SCHED_ROLLERMINE_CHASE_ENEMY;
break;
default:
break;
}
// Rollermines never wait to fall to the ground
ClearCondition( COND_FLOATING_OFF_GROUND );
return BaseClass::SelectSchedule();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNPC_RollerMine::GetHackedIdleSchedule( void )
{
// If we've been hacked, return to the player
if ( !m_bHackedByAlyx || m_bHeld )
return SCHED_NONE;
// Are we near the player?
CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
if ( !pPlayer )
return SCHED_NONE;
if ( !HasCondition(COND_SEE_PLAYER) )
return SCHED_ROLLERMINE_PATH_TO_PLAYER;
if ( GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) > ROLLERMINE_RETURN_TO_PLAYER_DIST )
return SCHED_ROLLERMINE_ROLL_TO_PLAYER;
return SCHED_NONE;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CNPC_RollerMine::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_IDLE_STAND:
{
int iSched = GetHackedIdleSchedule();
if ( iSched != SCHED_NONE )
return iSched;
return SCHED_IDLE_STAND;
}
break;
case SCHED_ALERT_STAND:
{
int iSched = GetHackedIdleSchedule();
if ( iSched != SCHED_NONE )
return iSched;
return SCHED_ROLLERMINE_ALERT_STAND;
}
break;
case SCHED_ROLLERMINE_RANGE_ATTACK1:
if( HasCondition(COND_ENEMY_OCCLUDED) )
{
// Because of an unfortunate arrangement of cascading failing schedules, the rollermine
// could end up here with instructions to drive towards the target, although the target is
// not in sight. Nudge around randomly until we're back on the nodegraph.
return SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES;
}
break;
}
return scheduleType;
}
#if 0
#define ROLLERMINE_DETECTION_RADIUS 350
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::DetectedEnemyInProximity( void )
{
CBaseEntity *pEnt = NULL;
CBaseEntity *pBestEnemy = NULL;
float flBestDist = MAX_TRACE_LENGTH;
while ( ( pEnt = gEntList.FindEntityInSphere( pEnt, GetAbsOrigin(), ROLLERMINE_DETECTION_RADIUS ) ) != NULL )
{
if ( IRelationType( pEnt ) != D_HT )
continue;
float distance = ( pEnt->GetAbsOrigin() - GetAbsOrigin() ).Length();
if ( distance >= flBestDist )
continue;
pBestEnemy = pEnt;
flBestDist = distance;
}
if ( pBestEnemy != NULL )
{
SetEnemy( pBestEnemy );
return true;
}
return false;
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSightEnt -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::QuerySeeEntity(CBaseEntity *pSightEnt, bool bOnlyHateOrFearIfNPC)
{
if ( IRelationType( pSightEnt ) == D_FR )
{
// Only see feared objects up close
float flDist = (WorldSpaceCenter() - pSightEnt->WorldSpaceCenter()).LengthSqr();
if ( flDist > ROLLERMINE_FEAR_DISTANCE )
return false;
}
return BaseClass::QuerySeeEntity(pSightEnt, bOnlyHateOrFearIfNPC);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StartTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_FACE_REASONABLE:
case TASK_FACE_SAVEPOSITION:
case TASK_FACE_LASTPOSITION:
case TASK_FACE_TARGET:
case TASK_FACE_AWAY_FROM_SAVEPOSITION:
case TASK_FACE_HINTNODE:
case TASK_FACE_ENEMY:
case TASK_FACE_PLAYER:
case TASK_FACE_PATH:
case TASK_FACE_IDEAL:
// This only applies to NPCs that aren't spheres with omnidirectional eyesight.
TaskComplete();
break;
case TASK_ROLLERMINE_UNBURROW:
{
AddSolidFlags( FSOLID_NOT_SOLID );
SetMoveType( MOVETYPE_NOCLIP );
SetAbsVelocity( Vector( 0, 0, 256 ) );
Open();
trace_t tr;
AI_TraceLine( GetAbsOrigin()+Vector(0,0,1), GetAbsOrigin()-Vector(0,0,64), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction < 1.0f )
{
UTIL_CreateAntlionDust( tr.endpos + Vector(0,0,24), GetLocalAngles() );
}
}
return;
break;
case TASK_ROLLERMINE_BURIED_WAIT:
if ( HasCondition( COND_SEE_ENEMY ) )
{
TaskComplete();
}
break;
case TASK_STOP_MOVING:
//Stop turning
m_RollerController.m_vecAngular = vec3_origin;
TaskComplete();
return;
break;
case TASK_WAIT_FOR_MOVEMENT:
{
// TASK_RUN_PATH and TASK_WALK_PATH work different on the rollermine and run until movement is done,
// so movement is already complete when entering this task.
TaskComplete();
}
break;
case TASK_WALK_PATH:
case TASK_RUN_PATH:
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject == NULL )
{
assert(0);
TaskFail("Roller lost internal physics object?");
return;
}
pPhysicsObject->Wake();
}
break;
case TASK_ROLLERMINE_CHARGE_ENEMY:
case TASK_ROLLERMINE_RETURN_TO_PLAYER:
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject == NULL )
{
assert(0);
TaskFail("Roller lost internal physics object?");
return;
}
pPhysicsObject->Wake();
m_flChargeTime = gpGlobals->curtime;
}
break;
case TASK_ROLLERMINE_GET_PATH_TO_FLEE:
{
// Find the nearest thing we're afraid of, and move away from it.
float flNearest = ROLLERMINE_FEAR_DISTANCE;
EHANDLE hNearestEnemy = NULL;
AIEnemiesIter_t iter;
for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst( &iter ); pEMemory != NULL; pEMemory = GetEnemies()->GetNext( &iter ) )
{
CBaseEntity *pEnemy = pEMemory->hEnemy;
if ( !pEnemy || !pEnemy->IsAlive() )
continue;
if ( IRelationType( pEnemy ) != D_FR )
continue;
float flDist = (WorldSpaceCenter() - pEnemy->WorldSpaceCenter()).LengthSqr();
if ( flDist < flNearest )
{
flNearest = flDist;
hNearestEnemy = pEnemy;
}
}
if ( !hNearestEnemy )
{
TaskFail("Couldn't find nearest feared object.");
break;
}
GetMotor()->SetIdealYawToTarget( hNearestEnemy->WorldSpaceCenter() );
ChainStartTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
}
break;
case TASK_ROLLERMINE_NUDGE_TOWARDS_NODES:
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if( pPhysicsObject )
{
// Try a few times to find a direction to shove ourself
for( int i = 0 ; i < 4 ; i++ )
{
int x,y;
x = random->RandomInt( -1, 1 );
y = random->RandomInt( -1, 1 );
Vector vecNudge(x, y, 0.0f);
trace_t tr;
// Try to move in a direction with a couple of feet of clearance.
UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + vecNudge * 24.0f, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
if( tr.fraction == 1.0 )
{
vecNudge *= (pPhysicsObject->GetMass() * 75.0f);
vecNudge += Vector(0,0,pPhysicsObject->GetMass() * 75.0f);
pPhysicsObject->ApplyForceCenter( vecNudge );
break;
}
}
}
TaskComplete();
}
break;
case TASK_ROLLERMINE_POWERDOWN:
break;
default:
BaseClass::StartTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::RunTask( const Task_t *pTask )
{
switch( pTask->iTask )
{
case TASK_ROLLERMINE_UNBURROW:
{
Vector vCenter = WorldSpaceCenter();
// Robin: HACK: Bloat the rollermine check to catch the model switch (roller.mdl->roller_spikes.mdl)
trace_t tr;
AI_TraceHull( vCenter, vCenter, Vector(-16,-16,-16), Vector(16,16,16), MASK_NPCSOLID, this, GetCollisionGroup(), &tr );
if ( tr.fraction == 1 && tr.allsolid != 1 && (tr.startsolid != 1) )
{
if ( BecomePhysical() )
{
Hop( 256 );
m_bBuried = false;
TaskComplete();
SetIdealState( NPC_STATE_ALERT );
}
}
}
return;
break;
case TASK_ROLLERMINE_BURIED_WAIT:
if ( HasCondition( COND_SEE_ENEMY ) || HasCondition( COND_LIGHT_DAMAGE ) )
{
TaskComplete();
}
break;
case TASK_ROLLERMINE_GET_PATH_TO_FLEE:
{
ChainRunTask( TASK_MOVE_AWAY_PATH, pTask->flTaskData );
}
break;
case TASK_WAIT_FOR_MOVEMENT:
{
// TASK_RUN_PATH and TASK_WALK_PATH work different on the rollermine and run until movement is done,
// so movement is already complete when entering this task.
TaskComplete();
}
break;
case TASK_RUN_PATH:
case TASK_WALK_PATH:
if ( m_bHeld || m_hVehicleStuckTo )
{
TaskFail( "Player interrupted by grabbing" );
break;
}
// If we were fleeing, but we've lost sight of the thing scaring us, stop
if ( IsCurSchedule(SCHED_ROLLERMINE_FLEE) && !HasCondition( COND_SEE_FEAR ) )
{
TaskComplete();
break;
}
if ( !GetNavigator()->IsGoalActive() )
{
TaskComplete();
return;
}
// Start turning early
if( (GetLocalOrigin() - GetNavigator()->GetCurWaypointPos() ).Length() <= 64 )
{
if( GetNavigator()->CurWaypointIsGoal() )
{
// Hit the brakes a bit.
float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() );
Vector vecRight;
AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );
m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, -m_flForwardSpeed * 5 );
TaskComplete();
return;
}
GetNavigator()->AdvancePath();
}
{
float yaw = UTIL_VecToYaw( GetNavigator()->GetCurWaypointPos() - GetLocalOrigin() );
Vector vecRight;
Vector vecToPath; // points at the path
AngleVectors( QAngle( 0, yaw, 0 ), &vecToPath, &vecRight, NULL );
// figure out if the roller is turning. If so, cut the throttle a little.
float flDot;
Vector vecVelocity;
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject == NULL )
{
assert(0);
TaskFail("Roller lost internal physics object?");
return;
}
pPhysicsObject->GetVelocity( &vecVelocity, NULL );
VectorNormalize( vecVelocity );
vecVelocity.z = 0;
flDot = DotProduct( vecVelocity, vecToPath );
m_RollerController.m_vecAngular = vec3_origin;
if( flDot > 0.25 && flDot < 0.7 )
{
// Feed a little torque backwards into the axis perpendicular to the velocity.
// This will help get rid of momentum that would otherwise make us overshoot our goal.
Vector vecCompensate;
vecCompensate.x = vecVelocity.y;
vecCompensate.y = -vecVelocity.x;
vecCompensate.z = 0;
m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
}
if( m_bHackedByAlyx )
{
// Move faster.
m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * 2.0f );
}
else
{
m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed );
}
}
break;
case TASK_ROLLERMINE_CHARGE_ENEMY:
{
if ( !GetEnemy() )
{
TaskFail( FAIL_NO_ENEMY );
break;
}
if ( m_bHeld || m_hVehicleStuckTo )
{
TaskComplete();
break;
}
CBaseEntity *pEnemy = GetEnemy();
Vector vecTargetPosition = pEnemy->GetAbsOrigin();
// If we're chasing a vehicle, try and get ahead of it
if ( EnemyInVehicle() )
{
CBaseCombatCharacter *pCCEnemy = pEnemy->MyCombatCharacterPointer();
float flT;
// Project it's velocity and find our closest point on that line. Do it all in 2d space.
Vector vecVehicleVelocity = pCCEnemy->GetVehicleEntity()->GetSmoothedVelocity();
Vector vecProjected = vecTargetPosition + (vecVehicleVelocity * 1.0);
Vector2D vecProjected2D( vecProjected.x, vecProjected.y );
Vector2D vecTargetPosition2D( vecTargetPosition.x, vecTargetPosition.y );
Vector2D vecOrigin2D( GetAbsOrigin().x, GetAbsOrigin().y );
Vector2D vecIntercept2D;
CalcClosestPointOnLine2D( vecOrigin2D, vecTargetPosition2D, vecProjected2D, vecIntercept2D, &flT );
Vector vecIntercept( vecIntercept2D.x, vecIntercept2D.y, GetAbsOrigin().z );
//NDebugOverlay::Line( vecTargetPosition, vecProjected, 0,255,0, true, 0.1 );
// If we're ahead of the line somewhere, try to intercept
if ( flT > 0 )
{
// If it's beyond the end of the intercept line, just move towards the end of the line
if ( flT > 1 )
{
vecIntercept.x = vecProjected.x;
vecIntercept.y = vecProjected.y;
}
// If we're closer to the intercept point than to the vehicle, move towards the intercept
if ( (GetAbsOrigin() - vecTargetPosition).LengthSqr() > (GetAbsOrigin() - vecIntercept).LengthSqr() )
{
//NDebugOverlay::Box( vecIntercept, -Vector(20,20,20), Vector(20,20,20), 255,0,0, 0.1, 0.1 );
// Only use this position if it's clear
if ( enginetrace->GetPointContents( vecIntercept ) != CONTENTS_SOLID )
{
vecTargetPosition = vecIntercept;
}
}
}
//NDebugOverlay::Box( vecTargetPosition, -Vector(20,20,20), Vector(20,20,20), 255,255,255, 0.1, 0.1 );
}
float flTorqueFactor;
Vector vecToTarget = vecTargetPosition - GetLocalOrigin();
float yaw = UTIL_VecToYaw( vecToTarget );
Vector vecRight;
AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );
//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin() + (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()), 0,255,0, true, 0.1 );
float flDot;
// Figure out whether to continue the charge.
// (Have I overrun the target?)
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject == NULL )
{
// Assert(0);
TaskFail("Roller lost internal physics object?");
return;
}
Vector vecVelocity;
pPhysicsObject->GetVelocity( &vecVelocity, NULL );
VectorNormalize( vecVelocity );
VectorNormalize( vecToTarget );
flDot = DotProduct( vecVelocity, vecToTarget );
// more torque the longer the roller has been going.
flTorqueFactor = 1 + (gpGlobals->curtime - m_flChargeTime) * 2;
float flMaxTorque = ROLLERMINE_MAX_TORQUE_FACTOR;
// Friendly rollermines go a little slower
if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
{
flMaxTorque *= 0.75;
}
if( flTorqueFactor < 1 )
{
flTorqueFactor = 1;
}
else if( flTorqueFactor > flMaxTorque)
{
flTorqueFactor = flMaxTorque;
}
Vector vecCompensate;
vecCompensate.x = vecVelocity.y;
vecCompensate.y = -vecVelocity.x;
vecCompensate.z = 0;
VectorNormalize( vecCompensate );
m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor );
// Taunt when I get closer
if( !(m_iSoundEventFlags & ROLLERMINE_SE_TAUNT) && UTIL_DistApprox( GetLocalOrigin(), vecTargetPosition ) <= 400 )
{
m_iSoundEventFlags |= ROLLERMINE_SE_TAUNT; // Don't repeat.
EmitSound( "NPC_RollerMine.Taunt" );
}
// Jump earlier when chasing a vehicle
float flThreshold = ROLLERMINE_OPEN_THRESHOLD;
if ( EnemyInVehicle() )
{
flThreshold = ROLLERMINE_VEHICLE_OPEN_THRESHOLD;
}
// Open the spikes if i'm close enough to cut the enemy!!
if( ( m_bIsOpen == false ) && ( ( UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() ) <= flThreshold ) || !IsActive() ) )
{
Open();
}
else if ( m_bIsOpen )
{
float flDistance = UTIL_DistApprox( GetAbsOrigin(), GetEnemy()->GetAbsOrigin() );
if ( flDistance >= flThreshold )
{
// Otherwise close them if the enemy is getting away!
Close();
}
else if ( EnemyInVehicle() && flDistance < ROLLERMINE_VEHICLE_HOP_THRESHOLD )
{
// Keep trying to hop when we're ramming a vehicle, so we're visible to the player
if ( vecVelocity.x != 0 && vecVelocity.y != 0 && flTorqueFactor > 3 && flDot > 0.0 )
{
Hop( 300 );
}
}
}
// If we drive past, close the blades and make a new plan.
if ( !EnemyInVehicle() )
{
if( vecVelocity.x != 0 && vecVelocity.y != 0 )
{
if( gpGlobals->curtime - m_flChargeTime > 1.0 && flTorqueFactor > 1 && flDot < 0.0 )
{
if( m_bIsOpen )
{
Close();
}
TaskComplete();
}
}
}
}
break;
case TASK_ROLLERMINE_RETURN_TO_PLAYER:
{
if ( ConditionsGathered() && !HasCondition(COND_SEE_PLAYER) )
{
TaskFail( FAIL_NO_PLAYER );
return;
}
CBaseEntity *pPlayer = gEntList.FindEntityByName( NULL, "!player" );
if ( !pPlayer || m_bHeld || m_hVehicleStuckTo )
{
TaskFail( FAIL_NO_TARGET );
return;
}
Vector vecTargetPosition = pPlayer->GetAbsOrigin();
float flTorqueFactor;
Vector vecToTarget = vecTargetPosition - GetLocalOrigin();
float yaw = UTIL_VecToYaw( vecToTarget );
Vector vecRight;
AngleVectors( QAngle( 0, yaw, 0 ), NULL, &vecRight, NULL );
float flDot;
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject == NULL )
{
TaskFail("Roller lost internal physics object?");
return;
}
Vector vecVelocity;
pPhysicsObject->GetVelocity( &vecVelocity, NULL );
VectorNormalize( vecVelocity );
VectorNormalize( vecToTarget );
flDot = DotProduct( vecVelocity, vecToTarget );
// more torque the longer the roller has been going.
flTorqueFactor = 1 + (gpGlobals->curtime - m_flChargeTime) * 2;
float flMaxTorque = ROLLERMINE_MAX_TORQUE_FACTOR * 0.75;
if( flTorqueFactor < 1 )
{
flTorqueFactor = 1;
}
else if( flTorqueFactor > flMaxTorque)
{
flTorqueFactor = flMaxTorque;
}
Vector vecCompensate;
vecCompensate.x = vecVelocity.y;
vecCompensate.y = -vecVelocity.x;
vecCompensate.z = 0;
VectorNormalize( vecCompensate );
m_RollerController.m_vecAngular = WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecCompensate, m_flForwardSpeed * -0.75 );
m_RollerController.m_vecAngular += WorldToLocalRotation( SetupMatrixAngles(GetLocalAngles()), vecRight, m_flForwardSpeed * flTorqueFactor );
// Once we're near the player, slow & stop
if ( GetAbsOrigin().DistToSqr( vecTargetPosition ) < (ROLLERMINE_RETURN_TO_PLAYER_DIST*2.0) )
{
TaskComplete();
}
}
break;
case TASK_ROLLERMINE_POWERDOWN:
{
if ( m_flPowerDownTime <= gpGlobals->curtime )
{
m_flNextHop = gpGlobals->curtime;
m_flPowerDownTime = gpGlobals->curtime + RandomFloat( 0.3, 0.9 );
EmitSound( "NPC_RollerMine.Hurt" );
CSoundEnt::InsertSound ( SOUND_DANGER, GetAbsOrigin(), 400, 0.5f, this );
if ( m_bIsOpen == false )
{
Open();
}
else
{
Close();
}
}
if ( m_flPowerDownDetonateTime <= gpGlobals->curtime )
{
SetThink( &CNPC_RollerMine::PreDetonate );
SetNextThink( gpGlobals->curtime + 0.5f );
}
// No TaskComplete() here, because the task will never complete. The rollermine will explode.
}
break;
default:
BaseClass::RunTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Open( void )
{
// Friendly rollers cannot open
if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
return;
if ( m_bIsOpen == false )
{
SetModel( "models/roller_spikes.mdl" );
SetRollerSkin();
EmitSound( "NPC_RollerMine.OpenSpikes" );
SetTouch( &CNPC_RollerMine::ShockTouch );
m_bIsOpen = true;
// Don't hop if we're constrained
if ( !m_pConstraint )
{
if ( EnemyInVehicle() )
{
Hop( 256 );
}
else if ( !GetEnemy() || GetEnemy()->Classify() != CLASS_BULLSEYE ) // Don't hop when attacking bullseyes
{
Hop( 128 );
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::SetRollerSkin( void )
{
if ( m_bPowerDown == true )
{
m_nSkin = (int)ROLLER_SKIN_DETONATE;
}
else if ( m_bHackedByAlyx == true )
{
m_nSkin = (int)ROLLER_SKIN_FRIENDLY;
}
else
{
m_nSkin = (int)ROLLER_SKIN_REGULAR;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Close( void )
{
// Not allowed to close while primed, because we're going to detonate on touch
if ( m_bIsPrimed )
return;
if ( m_bIsOpen && !IsShocking() )
{
SetModel( "models/roller.mdl" );
SetRollerSkin();
SetTouch( NULL );
m_bIsOpen = false;
m_iSoundEventFlags = ROLLERMINE_SE_CLEAR;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_RollerMine::SpikeTouch( CBaseEntity *pOther )
{
/*
if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
return;
if ( m_bHeld )
return;
if ( pOther->IsPlayer() )
return;
if ( pOther->m_takedamage != DAMAGE_YES )
return;
// If we just hit a breakable glass object, don't explode. We want to blow through it.
CBreakable *pBreakable = dynamic_cast<CBreakable*>(pOther);
if ( pBreakable && pBreakable->GetMaterialType() == matGlass )
return;
Explode();
EmitSound( "NPC_RollerMine.Warn" );
*/
//FIXME: Either explode within certain rules, never explode, or just shock the hit victim
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::CloseTouch( CBaseEntity *pOther )
{
if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
return;
if ( IsShocking() )
return;
bool bOtherIsDead = ( pOther->MyNPCPointer() && !pOther->MyNPCPointer()->IsAlive() );
bool bOtherIsNotarget = ( ( pOther->GetFlags() & FL_NOTARGET ) != 0 );
if ( !bOtherIsDead && !bOtherIsNotarget )
{
Disposition_t disp = IRelationType(pOther);
if ( (disp == D_HT || disp == D_FR) )
{
ShockTouch( pOther );
return;
}
}
Close();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::EmbedTouch( CBaseEntity *pOther )
{
if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
return;
m_bEmbedOnGroundImpact = false;
// Did we hit the world?
if ( pOther->entindex() == 0 )
{
m_bBuried = true;
trace_t tr;
Bury( &tr );
// Destroy out physics object and become static
VPhysicsDestroyObject();
CreateVPhysics();
// Drop a decal on the ground where we impacted
UTIL_DecalTrace( &tr, "Rollermine.Crater" );
// Make some dust
UTIL_CreateAntlionDust( tr.endpos, GetLocalAngles() );
}
// Don't try and embed again
SetTouch( NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::IsPlayerVehicle( CBaseEntity *pEntity )
{
IServerVehicle *pVehicle = pEntity->GetServerVehicle();
if ( pVehicle )
{
CBasePlayer *pPlayer = ToBasePlayer( pVehicle->GetPassenger() );
if ( pPlayer != NULL )
{
Disposition_t disp = IRelationType(pPlayer);
if ( disp == D_HT || disp == D_FR )
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pVictim -
// Output : float
//-----------------------------------------------------------------------------
float CNPC_RollerMine::GetAttackDamageScale( CBaseEntity *pVictim )
{
// If we're friendly, don't damage players or player-friendly NPCs, even with collisions
if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
{
if ( pVictim->IsPlayer() )
return 0;
if ( pVictim->MyNPCPointer() )
{
// If we don't hate the player, we're immune
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);
if ( pPlayer && pVictim->MyNPCPointer()->IRelationType( pPlayer ) != D_HT )
return 0.0;
}
}
return BaseClass::GetAttackDamageScale( pVictim );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::ShockTarget( CBaseEntity *pOther )
{
CBeam *pBeam;
if( m_bHackedByAlyx )
{
pBeam = CBeam::BeamCreate( "sprites/rollermine_shock_yellow.vmt", 4 );
}
else
{
pBeam = CBeam::BeamCreate( "sprites/rollermine_shock.vmt", 4 );
}
int startAttach = -1;
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOther);
if ( pBeam != NULL )
{
pBeam->EntsInit( pOther, this );
if ( pAnimating && pAnimating->GetModel() )
{
startAttach = pAnimating->LookupAttachment("beam_damage" );
pBeam->SetStartAttachment( startAttach );
}
// Change this up a little for first person hits
if ( pOther->IsPlayer() )
{
pBeam->SetEndWidth( 8 );
pBeam->SetNoise( 4 );
pBeam->LiveForTime( 0.2f );
}
else
{
pBeam->SetEndWidth( 16 );
pBeam->SetNoise( 16 );
pBeam->LiveForTime( 0.5f );
}
pBeam->SetEndAttachment( 1 );
pBeam->SetWidth( 1 );
pBeam->SetBrightness( 255 );
pBeam->SetColor( 255, 255, 255 );
pBeam->RelinkBeam();
}
Vector shockPos = pOther->WorldSpaceCenter();
if ( startAttach > 0 && pAnimating )
{
pAnimating->GetAttachment( startAttach, shockPos );
}
Vector shockDir = ( GetAbsOrigin() - shockPos );
VectorNormalize( shockDir );
CPVSFilter filter( shockPos );
te->GaussExplosion( filter, 0.0f, shockPos, shockDir, 0 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::NotifyInteraction( CAI_BaseNPC *pUser )
{
// For now, turn green so we can tell who is hacked.
m_bHackedByAlyx = true;
SetRollerSkin();
GetEnemies()->SetFreeKnowledgeDuration( 30.0f );
// Play the hax0red sound
EmitSound( "NPC_RollerMine.Reprogram" );
// Force the rollermine open here. At very least, this ensures that the
// correct, smaller bounding box is recomputed around it.
Open();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::ShockTouch( CBaseEntity *pOther )
{
if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS) )
return;
if ( m_bHeld || m_hVehicleStuckTo || gpGlobals->curtime < m_flShockTime )
return;
// error?
Assert( !m_bIsPrimed );
Disposition_t disp = IRelationType(pOther);
// Ignore anyone that I'm friendly or neutral to.
if( disp != D_HT && disp != D_FR)
return;
IPhysicsObject *pPhysics = VPhysicsGetObject();
// Calculate a collision force
Vector impulse = WorldSpaceCenter() - pOther->WorldSpaceCenter();
impulse.z = 0;
VectorNormalize( impulse );
impulse.z = 0.75;
VectorNormalize( impulse );
impulse *= 600;
// Stun the roller
m_flActiveTime = gpGlobals->curtime + GetStunDelay();
// If we're a 'friendly' rollermine, just push the player a bit
if ( HasSpawnFlags( SF_ROLLERMINE_FRIENDLY ) )
{
if ( pOther->IsPlayer() )
{
Vector vecForce = -impulse * 0.5;
pOther->ApplyAbsVelocityImpulse( vecForce );
}
return;
}
// jump up at a 30 degree angle away from the guy we hit
SetTouch( &CNPC_RollerMine::CloseTouch );
Vector vel;
pPhysics->SetVelocity( &impulse, NULL );
EmitSound( "NPC_RollerMine.Shock" );
// Do a shock effect
ShockTarget( pOther );
m_flShockTime = gpGlobals->curtime + 1.25;
// Calculate physics force
Vector out;
pOther->CollisionProp()->CalcNearestPoint( WorldSpaceCenter(), &out );
Vector vecForce = ( -impulse * pPhysics->GetMass() * 10 );
CTakeDamageInfo info( this, this, vecForce, out, sk_rollermine_shock.GetFloat(), DMG_SHOCK );
if( FClassnameIs( pOther, "npc_combine_s" ) )
{
if( pOther->GetHealth() <= (pOther->GetMaxHealth() / 2) )
{
// Instant special death for a combine soldier who has less than half health.
Vector vecDamageForce = pOther->WorldSpaceCenter() - WorldSpaceCenter();
VectorNormalize( vecDamageForce );
IPhysicsObject *pPhysics = pOther->VPhysicsGetObject();
if( pPhysics )
{
vecDamageForce *= (pPhysics->GetMass() * 200.0f);
// Slam Z component with some good, reliable upwards velocity.
vecDamageForce.z = pPhysics->GetMass() * 200.0f;
}
pOther->MyCombatCharacterPointer()->BecomeRagdollBoogie( this, vecDamageForce, 5.0f, SF_RAGDOLL_BOOGIE_ELECTRICAL );
return;
}
else
{
info.SetDamage( pOther->GetMaxHealth()/2 );
}
}
pOther->TakeDamage( info );
// Knock players back a bit
if ( pOther->IsPlayer() )
{
vecForce = -impulse;
pOther->ApplyAbsVelocityImpulse( vecForce );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
// Make sure we don't keep hitting the same entity
int otherIndex = !index;
CBaseEntity *pOther = pEvent->pEntities[otherIndex];
if ( pEvent->deltaCollisionTime < 0.5 && (pOther == this) )
return;
BaseClass::VPhysicsCollision( index, pEvent );
// If we've just hit a vehicle, we want to stick to it
if ( m_bHeld || m_hVehicleStuckTo || !IsPlayerVehicle( pOther ) )
{
// Are we supposed to be embedding ourselves?
if ( m_bEmbedOnGroundImpact )
{
// clear the flag so we don't queue more than once
m_bEmbedOnGroundImpact = false;
// call this when physics is done
g_PostSimulationQueue.QueueCall( this, &CNPC_RollerMine::EmbedTouch, pOther );
}
return;
}
StickToVehicle( pOther );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StickToVehicle( CBaseEntity *pOther )
{
IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject();
if ( !pOtherPhysics )
return;
// Don't stick to the wheels
if ( pOtherPhysics->GetCallbackFlags() & CALLBACK_IS_VEHICLE_WHEEL )
return;
// Destroy our constraint. This can happen if we had our constraint broken
// and we still haven't cleaned up our constraint.
UnstickFromVehicle();
// We've hit the vehicle that the player's in.
// Stick to it and slow it down.
m_hVehicleStuckTo = pOther;
IPhysicsObject *pPhysics = VPhysicsGetObject();
// Constrain us to the vehicle
constraint_fixedparams_t fixed;
fixed.Defaults();
fixed.InitWithCurrentObjectState( pOtherPhysics, pPhysics );
fixed.constraint.Defaults();
fixed.constraint.forceLimit = ImpulseScale( pPhysics->GetMass(), 200 );
fixed.constraint.torqueLimit = ImpulseScale( pPhysics->GetMass(), 800 );
m_pConstraint = physenv->CreateFixedConstraint( pOtherPhysics, pPhysics, NULL, fixed );
m_pConstraint->SetGameData( (void *)this );
// Kick the vehicle so the player knows we've arrived
Vector impulse = pOther->GetAbsOrigin() - GetAbsOrigin();
VectorNormalize( impulse );
impulse.z = -0.75;
VectorNormalize( impulse );
impulse *= 600;
Vector vecForce = impulse * pPhysics->GetMass() * 10;
pOtherPhysics->ApplyForceOffset( vecForce, GetAbsOrigin() );
// Get the velocity at the point we're sticking to
Vector vecVelocity;
pOtherPhysics->GetVelocityAtPoint( GetAbsOrigin(), &vecVelocity );
AngularImpulse angNone( 0.0f, 0.0f, 0.0f );
pPhysics->SetVelocity( &vecVelocity, &angNone );
// Make sure we're spiky
Open();
AnnounceArrivalToOthers( pOther );
// Also, jolt the vehicle sometime in the future
g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNPC_RollerMine::CountRollersOnMyVehicle( CUtlVector<CNPC_RollerMine*> *pRollerList )
{
CBaseEntity *entityList[64];
Vector range(256,256,256);
pRollerList->AddToTail( this );
int boxCount = UTIL_EntitiesInBox( entityList, ARRAYSIZE(entityList), GetAbsOrigin()-range, GetAbsOrigin()+range, FL_NPC );
for ( int i = 0; i < boxCount; i++ )
{
CAI_BaseNPC *pNPC = entityList[i]->MyNPCPointer();
if ( pNPC && pNPC->m_iClassname == m_iClassname && pNPC != this )
{
// Found another rollermine
CNPC_RollerMine *pMine = dynamic_cast<CNPC_RollerMine*>(pNPC);
Assert( pMine );
// Is he stuck to the same vehicle?
if ( pMine->GetVehicleStuckTo() == GetVehicleStuckTo() )
{
pRollerList->AddToTail( pMine );
}
}
}
return pRollerList->Count();
}
//-----------------------------------------------------------------------------
// Purpose: Tell other rollermines on the vehicle that I've just arrived
// Input : *pOther -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::AnnounceArrivalToOthers( CBaseEntity *pOther )
{
// Now talk to any other rollermines stuck to the same vehicle
CUtlVector<CNPC_RollerMine*> aRollersOnVehicle;
int iRollers = CountRollersOnMyVehicle( &aRollersOnVehicle );
// Stop all rollers on the vehicle falling off due to the force of the arriving one
for ( int i = 0; i < iRollers; i++ )
{
aRollersOnVehicle[i]->PreventUnstickUntil( gpGlobals->curtime + 1 );
}
// See if we've got enough rollers on the vehicle to start being mean
/*
if ( iRollers >= ROLLERMINE_REQUIRED_TO_EXPLODE_VEHICLE )
{
// Alert the others
EmitSound( "NPC_RollerMine.ExplodeChirp" );
// Tell everyone to explode shortly
for ( int i = 0; i < iRollers; i++ )
{
variant_t emptyVariant;
g_EventQueue.AddEvent( aRollersOnVehicle[i], "RespondToExplodeChirp", RandomFloat(2,5), NULL, NULL );
}
}
else
{
*/
// If there's other rollers on the vehicle, talk to them
if ( iRollers > 1 )
{
// Chirp to the others
EmitSound( "NPC_RollerMine.Chirp" );
// Tell the others to respond (skip first slot, because that's me)
for ( int i = 1; i < iRollers; i++ )
{
variant_t emptyVariant;
g_EventQueue.AddEvent( aRollersOnVehicle[i], "RespondToChirp", RandomFloat(2,3), NULL, NULL );
}
}
// }
}
//-----------------------------------------------------------------------------
// Purpose: Physics system has just told us our constraint has been broken
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputConstraintBroken( inputdata_t &inputdata )
{
// Prevent rollermines being dislodged right as they stick
if ( m_flPreventUnstickUntil > gpGlobals->curtime )
return;
// We can't delete it here safely
UnstickFromVehicle();
Close();
// dazed
m_RollerController.m_vecAngular.Init();
m_flActiveTime = gpGlobals->curtime + GetStunDelay();
}
//-----------------------------------------------------------------------------
// Purpose: Respond to another rollermine that's chirped at us
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputRespondToChirp( inputdata_t &inputdata )
{
EmitSound( "NPC_RollerMine.ChirpRespond" );
}
//-----------------------------------------------------------------------------
// Purpose: Respond to another rollermine's signal to detonate
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputRespondToExplodeChirp( inputdata_t &inputdata )
{
EmitSound( "NPC_RollerMine.ExplodeChirpRespond" );
Explode();
}
//-----------------------------------------------------------------------------
// Purpose: Apply a physics force to the vehicle we're in
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputJoltVehicle( inputdata_t &inputdata )
{
Assert( GetVehicleStuckTo() );
// First, tell all rollers on the vehicle not to fall off
CUtlVector<CNPC_RollerMine*> aRollersOnVehicle;
int iRollers = CountRollersOnMyVehicle( &aRollersOnVehicle );
for ( int i = 0; i < iRollers; i++ )
{
aRollersOnVehicle[i]->PreventUnstickUntil( gpGlobals->curtime + 1 );
}
// Now smack the vehicle
Vector impulse = GetVehicleStuckTo()->GetAbsOrigin() - GetAbsOrigin();
VectorNormalize( impulse );
// Randomly apply a little vertical lift, to get the wheels off the ground
impulse.z = RandomFloat( 0.5, 1.0 );
VectorNormalize( impulse );
IPhysicsObject *pVehiclePhysics = GetVehicleStuckTo()->VPhysicsGetObject();
Vector vecForce = impulse * ImpulseScale( pVehiclePhysics->GetMass(), RandomFloat(150,250) );
pVehiclePhysics->ApplyForceOffset( vecForce, GetAbsOrigin() );
// Play sounds & effects
EmitSound( "NPC_RollerMine.JoltVehicle" );
// UNDONE: Good Zap effects
/*
CBeam *pBeam = CBeam::BeamCreate( "sprites/rollermine_shock.vmt", 4 );
if ( pBeam )
{
pBeam->EntsInit( GetVehicleStuckTo(), this );
CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>( GetVehicleStuckTo() );
if ( pAnimating )
{
int startAttach = pAnimating->LookupAttachment("beam_damage" );
pBeam->SetStartAttachment( startAttach );
}
pBeam->SetEndAttachment( 1 );
pBeam->SetWidth( 8 );
pBeam->SetEndWidth( 8 );
pBeam->SetBrightness( 255 );
pBeam->SetColor( 255, 255, 255 );
pBeam->LiveForTime( 0.5f );
pBeam->RelinkBeam();
pBeam->SetNoise( 30 );
}
*/
ShockTarget( GetVehicleStuckTo() );
// Jolt again soon
g_EventQueue.AddEvent( this, "JoltVehicle", RandomFloat(3,6), NULL, NULL );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputTurnOn( inputdata_t &inputdata )
{
m_RollerController.On();
m_bTurnedOn = true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputTurnOff( inputdata_t &inputdata )
{
m_RollerController.Off();
m_bTurnedOn = false;
StopLoopingSounds();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::InputPowerdown( inputdata_t &inputdata )
{
m_bPowerDown = true;
m_flPowerDownTime = gpGlobals->curtime + RandomFloat( 0.1, 0.5 );
m_flPowerDownDetonateTime = m_flPowerDownTime + RandomFloat( 1.5, 4.0 );
ClearSchedule( "Received power down input" );
}
//-----------------------------------------------------------------------------
// Purpose: If we were stuck to a vehicle, remove ourselves
//-----------------------------------------------------------------------------
void CNPC_RollerMine::UnstickFromVehicle( void )
{
if ( m_pConstraint )
{
physenv->DestroyConstraint( m_pConstraint );
m_pConstraint = NULL;
}
// Cancel any pending jolt events
g_EventQueue.CancelEventOn( this, "JoltVehicle" );
m_hVehicleStuckTo = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *CNPC_RollerMine::GetVehicleStuckTo( void )
{
if ( !m_pConstraint )
return NULL;
return m_hVehicleStuckTo;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPhysGunUser -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
// Are we just being punted?
if ( reason == PUNTED_BY_CANNON )
{
// Be stunned
m_flActiveTime = gpGlobals->curtime + GetStunDelay();
return;
}
//Stop turning
m_RollerController.m_vecAngular = vec3_origin;
UnstickFromVehicle();
m_OnPhysGunPickup.FireOutput( pPhysGunUser, this );
m_bHeld = true;
m_RollerController.Off();
EmitSound( "NPC_RollerMine.Held" );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPhysGunUser -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
m_bHeld = false;
m_flActiveTime = gpGlobals->curtime + GetStunDelay();
m_RollerController.On();
// explode on contact if launched from the physgun
if ( Reason == LAUNCHED_BY_CANNON )
{
if ( m_bIsOpen )
{
//m_bIsPrimed = true;
SetTouch( &CNPC_RollerMine::SpikeTouch );
// enable world/prop touch too
VPhysicsGetObject()->SetCallbackFlags( VPhysicsGetObject()->GetCallbackFlags() | CALLBACK_GLOBAL_TOUCH|CALLBACK_GLOBAL_TOUCH_STATIC );
}
EmitSound( "NPC_RollerMine.Tossed" );
}
m_OnPhysGunDrop.FireOutput( pPhysGunUser, this );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// Output : float
//-----------------------------------------------------------------------------
int CNPC_RollerMine::OnTakeDamage( const CTakeDamageInfo &info )
{
if ( !(info.GetDamageType() & DMG_BURN) )
{
if ( GetMoveType() == MOVETYPE_VPHYSICS )
{
AngularImpulse angVel;
angVel.Random( -400.0f, 400.0f );
VPhysicsGetObject()->AddVelocity( NULL, &angVel );
m_RollerController.m_vecAngular *= 0.8f;
VPhysicsTakeDamage( info );
}
SetCondition( COND_LIGHT_DAMAGE );
}
if ( info.GetDamageType() & (DMG_BURN|DMG_BLAST) )
{
if ( info.GetAttacker() && info.GetAttacker()->m_iClassname != m_iClassname )
{
SetThink( &CNPC_RollerMine::PreDetonate );
SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.5f ) );
}
else
{
// dazed
m_RollerController.m_vecAngular.Init();
m_flActiveTime = gpGlobals->curtime + GetStunDelay();
Hop( 300 );
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: Causes the roller to hop into the air
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Hop( float height )
{
if ( m_flNextHop > gpGlobals->curtime )
return;
if ( GetMoveType() == MOVETYPE_VPHYSICS )
{
IPhysicsObject *pPhysObj = VPhysicsGetObject();
pPhysObj->ApplyForceCenter( Vector(0,0,1) * height * pPhysObj->GetMass() );
AngularImpulse angVel;
angVel.Random( -400.0f, 400.0f );
pPhysObj->AddVelocity( NULL, &angVel );
m_flNextHop = gpGlobals->curtime + ROLLERMINE_HOP_DELAY;
}
}
//-----------------------------------------------------------------------------
// Purpose: Makes warning noise before actual explosion occurs
//-----------------------------------------------------------------------------
void CNPC_RollerMine::PreDetonate( void )
{
Hop( 300 );
SetTouch( NULL );
SetThink( &CNPC_RollerMine::Explode );
SetNextThink( gpGlobals->curtime + 0.5f );
EmitSound( "NPC_RollerMine.Hurt" );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::Explode( void )
{
m_takedamage = DAMAGE_NO;
//FIXME: Hack to make thrown mines more deadly and fun
float expDamage = m_bIsPrimed ? 100 : 25;
//If we've been hacked and we're blowing up cause we've been shut down then do moderate damage.
if ( m_bPowerDown == true )
{
expDamage = 50;
}
// Underwater explosion?
if ( UTIL_PointContents( GetAbsOrigin() ) & MASK_WATER )
{
CEffectData data;
data.m_vOrigin = WorldSpaceCenter();
data.m_flMagnitude = expDamage;
data.m_flScale = 128;
data.m_fFlags = ( SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE );
DispatchEffect( "WaterSurfaceExplosion", data );
}
else
{
ExplosionCreate( WorldSpaceCenter(), GetLocalAngles(), this, expDamage, 128, true );
}
CTakeDamageInfo info( this, this, 1, DMG_GENERIC );
Event_Killed( info );
// Remove myself a frame from now to avoid doing it in the middle of running AI
SetThink( &CNPC_RollerMine::SUB_Remove );
SetNextThink( gpGlobals->curtime );
}
const float MAX_ROLLING_SPEED = 720;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CNPC_RollerMine::RollingSpeed()
{
IPhysicsObject *pPhysics = VPhysicsGetObject();
if ( !m_hVehicleStuckTo && !m_bHeld && pPhysics && !pPhysics->IsAsleep() )
{
AngularImpulse angVel;
pPhysics->GetVelocity( NULL, &angVel );
float rollingSpeed = angVel.Length() - 90;
rollingSpeed = clamp( rollingSpeed, 1, MAX_ROLLING_SPEED );
rollingSpeed *= (1/MAX_ROLLING_SPEED);
return rollingSpeed;
}
return 0;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_RollerMine::GetStunDelay()
{
if( m_bHackedByAlyx )
{
return 0.1f;
}
else
{
return sk_rollermine_stun_delay.GetFloat();
}
}
//-----------------------------------------------------------------------------
// Purpose: We've been dropped by a dropship. Embed in the ground if we land on it.
//-----------------------------------------------------------------------------
void CNPC_RollerMine::EmbedOnGroundImpact()
{
m_bEmbedOnGroundImpact = true;
SetTouch( &CNPC_RollerMine::EmbedTouch );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::PrescheduleThink()
{
// Are we underwater?
if ( UTIL_PointContents( GetAbsOrigin() ) & MASK_WATER )
{
// As soon as we're far enough underwater, detonate
Vector vecAboveMe = GetAbsOrigin() + Vector(0,0,64);
if ( UTIL_PointContents( vecAboveMe ) & MASK_WATER )
{
Explode();
return;
}
}
UpdateRollingSound();
UpdatePingSound();
BaseClass::PrescheduleThink();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::UpdateRollingSound()
{
if ( m_rollingSoundState == ROLL_SOUND_NOT_READY )
return;
rollingsoundstate_t soundState = ROLL_SOUND_OFF;
float rollingSpeed = RollingSpeed();
if ( rollingSpeed > 0 )
{
soundState = m_bIsOpen ? ROLL_SOUND_OPEN : ROLL_SOUND_CLOSED;
}
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CSoundParameters params;
switch( soundState )
{
case ROLL_SOUND_CLOSED:
CBaseEntity::GetParametersForSound( "NPC_RollerMine.Roll", params, NULL );
break;
case ROLL_SOUND_OPEN:
CBaseEntity::GetParametersForSound( "NPC_RollerMine.RollWithSpikes", params, NULL );
break;
case ROLL_SOUND_OFF:
// no sound
break;
}
// start the new sound playing if necessary
if ( m_rollingSoundState != soundState )
{
StopRollingSound();
m_rollingSoundState = soundState;
if ( m_rollingSoundState == ROLL_SOUND_OFF )
return;
CPASAttenuationFilter filter( this );
m_pRollSound = controller.SoundCreate( filter, entindex(), params.channel, params.soundname, params.soundlevel );
controller.Play( m_pRollSound, params.volume, params.pitch );
m_rollingSoundState = soundState;
}
if ( m_pRollSound )
{
// for tuning
//DevMsg("SOUND: %s, VOL: %.1f\n", m_rollingSoundState == ROLL_SOUND_CLOSED ? "CLOSED" : "OPEN ", rollingSpeed );
controller.SoundChangePitch( m_pRollSound, params.pitchlow + (params.pitchhigh - params.pitchlow) * rollingSpeed, 0.1 );
controller.SoundChangeVolume( m_pRollSound, params.volume * rollingSpeed, 0.1 );
}
}
void CNPC_RollerMine::StopRollingSound()
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pRollSound );
m_pRollSound = NULL;
}
void CNPC_RollerMine::UpdatePingSound()
{
float pingSpeed = 0;
if ( m_bIsOpen && !IsShocking() && !m_bHeld )
{
CBaseEntity *pEnemy = GetEnemy();
if ( pEnemy )
{
pingSpeed = EnemyDistance( pEnemy );
pingSpeed = clamp( pingSpeed, 1, ROLLERMINE_OPEN_THRESHOLD );
pingSpeed *= (1.0f/ROLLERMINE_OPEN_THRESHOLD);
}
}
if ( pingSpeed > 0 )
{
pingSpeed = 1-pingSpeed;
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CSoundParameters params;
CBaseEntity::GetParametersForSound( "NPC_RollerMine.Ping", params, NULL );
if ( !m_pPingSound )
{
CPASAttenuationFilter filter( this );
m_pPingSound = controller.SoundCreate( filter, entindex(), params.channel, params.soundname, params.soundlevel );
controller.Play( m_pPingSound, params.volume, 101 );
}
controller.SoundChangePitch( m_pPingSound, params.pitchlow + (params.pitchhigh - params.pitchlow) * pingSpeed, 0.1 );
controller.SoundChangeVolume( m_pPingSound, params.volume, 0.1 );
//DevMsg("PING: %.1f\n", pingSpeed );
}
else
{
StopPingSound();
}
}
void CNPC_RollerMine::StopPingSound()
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pPingSound );
m_pPingSound = NULL;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_RollerMine::StopLoopingSounds( void )
{
StopRollingSound();
StopPingSound();
BaseClass::StopLoopingSounds();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pEnemy -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::IsValidEnemy( CBaseEntity *pEnemy )
{
// If the enemy's over the vehicle detection range, and it's not a player in a vehicle, ignore it
if ( pEnemy )
{
float flDistance = GetAbsOrigin().DistTo( pEnemy->GetAbsOrigin() );
if ( flDistance >= m_flSeeVehiclesOnlyBeyond )
{
// Handle vehicles
CBaseCombatCharacter *pCCEnemy = pEnemy->MyCombatCharacterPointer();
if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
{
// If we're buried, we only care when they're heading directly towards us
if ( m_bBuried )
return ( VehicleHeading( pCCEnemy->GetVehicle()->GetVehicleEnt() ) > DOT_20DEGREE );
// If we're not buried, chase him as long as he's not heading away from us
return ( VehicleHeading( pCCEnemy->GetVehicleEntity() ) > 0 );
}
return false;
}
// Never pick something I fear
if ( IRelationType( pEnemy ) == D_FR )
return false;
// Don't attack flying things.
if ( pEnemy->GetMoveType() == MOVETYPE_FLY )
return false;
}
return BaseClass::IsValidEnemy( pEnemy );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CNPC_RollerMine::EnemyInVehicle( void )
{
// Clearly the enemy is not...
if ( GetEnemy() == NULL )
return false;
// If the target is in a vehicle, let the convar choose
CBaseCombatCharacter *pCCEnemy = GetEnemy()->MyCombatCharacterPointer();
if ( pCCEnemy != NULL && pCCEnemy->IsInAVehicle() )
return ( sk_rollermine_vehicle_intercept.GetBool() );
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CNPC_RollerMine::VehicleHeading( CBaseEntity *pVehicle )
{
Vector vecVelocity = pVehicle->GetSmoothedVelocity();
float flSpeed = VectorNormalize( vecVelocity );
Vector vecToMine = GetAbsOrigin() - pVehicle->GetAbsOrigin();
VectorNormalize( vecToMine );
// If it's not moving, consider it moving towards us, but not directly
// This will enable already active rollers to chase the vehicle if it's stationary.
if ( flSpeed < 10 )
return 0.1;
return DotProduct( vecVelocity, vecToMine );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// &vecDir -
// *ptr -
//-----------------------------------------------------------------------------
void CNPC_RollerMine::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
if ( info.GetDamageType() & (DMG_BULLET | DMG_CLUB) )
{
CTakeDamageInfo newInfo( info );
// If we're stuck to the car, increase it even more
if ( GetVehicleStuckTo() )
{
newInfo.SetDamageForce( info.GetDamageForce() * 40 );
}
else
{
newInfo.SetDamageForce( info.GetDamageForce() * 20 );
}
BaseClass::TraceAttack( newInfo, vecDir, ptr, pAccumulator );
return;
}
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
}
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC( npc_rollermine, CNPC_RollerMine )
//Tasks
DECLARE_TASK( TASK_ROLLERMINE_CHARGE_ENEMY )
DECLARE_TASK( TASK_ROLLERMINE_BURIED_WAIT )
DECLARE_TASK( TASK_ROLLERMINE_UNBURROW )
DECLARE_TASK( TASK_ROLLERMINE_GET_PATH_TO_FLEE )
DECLARE_TASK( TASK_ROLLERMINE_NUDGE_TOWARDS_NODES )
DECLARE_TASK( TASK_ROLLERMINE_RETURN_TO_PLAYER )
DECLARE_TASK( TASK_ROLLERMINE_POWERDOWN )
//Schedules
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_BURIED_WAIT,
" Tasks"
" TASK_ROLLERMINE_BURIED_WAIT 0"
" "
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_BURIED_UNBURROW,
" Tasks"
" TASK_ROLLERMINE_UNBURROW 0"
" "
" Interrupts"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_RANGE_ATTACK1,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY"
" TASK_ROLLERMINE_CHARGE_ENEMY 0"
" "
" Interrupts"
" COND_ENEMY_DEAD"
" COND_NEW_ENEMY"
" COND_ENEMY_OCCLUDED"
" COND_ENEMY_TOO_FAR"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_CHASE_ENEMY,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ROLLERMINE_RANGE_ATTACK1"
" TASK_SET_TOLERANCE_DISTANCE 24"
" TASK_GET_PATH_TO_ENEMY 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" "
" Interrupts"
" COND_ENEMY_DEAD"
" COND_ENEMY_UNREACHABLE"
" COND_ENEMY_TOO_FAR"
" COND_CAN_RANGE_ATTACK1"
" COND_TASK_FAILED"
" COND_SEE_FEAR"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_FLEE,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_IDLE_STAND"
" TASK_ROLLERMINE_GET_PATH_TO_FLEE 300"
" TASK_RUN_PATH 0"
" TASK_STOP_MOVING 0"
" "
" Interrupts"
" COND_NEW_ENEMY"
" COND_TASK_FAILED"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_ALERT_STAND,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_REASONABLE 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_WAIT 2"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_SEE_ENEMY"
" COND_SEE_FEAR"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_PROVOKED"
" COND_SMELL"
" COND_HEAR_COMBAT" // sound flags
" COND_HEAR_WORLD"
" COND_HEAR_PLAYER"
" COND_HEAR_DANGER"
" COND_HEAR_BULLET_IMPACT"
" COND_IDLE_INTERRUPT"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_NUDGE_TOWARDS_NODES,
" Tasks"
" TASK_ROLLERMINE_NUDGE_TOWARDS_NODES 0"
" TASK_WAIT 1.5"
""
" Interrupts"
""
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_PATH_TO_PLAYER,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ROLLERMINE_ALERT_STAND"
" TASK_SET_TOLERANCE_DISTANCE 200"
" TASK_GET_PATH_TO_PLAYER 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_SEE_ENEMY"
" COND_SEE_FEAR"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_PROVOKED"
" COND_SMELL"
" COND_HEAR_COMBAT" // sound flags
" COND_HEAR_WORLD"
" COND_HEAR_PLAYER"
" COND_HEAR_DANGER"
" COND_HEAR_BULLET_IMPACT"
" COND_IDLE_INTERRUPT"
" COND_SEE_PLAYER"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_ROLL_TO_PLAYER,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ROLLERMINE_ALERT_STAND"
" TASK_SET_TOLERANCE_DISTANCE 200"
" TASK_ROLLERMINE_RETURN_TO_PLAYER 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_SEE_ENEMY"
" COND_SEE_FEAR"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_PROVOKED"
" COND_SMELL"
" COND_HEAR_COMBAT" // sound flags
" COND_HEAR_WORLD"
" COND_HEAR_PLAYER"
" COND_HEAR_DANGER"
" COND_HEAR_BULLET_IMPACT"
" COND_IDLE_INTERRUPT"
)
DEFINE_SCHEDULE
(
SCHED_ROLLERMINE_POWERDOWN,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_ROLLERMINE_POWERDOWN 0"
""
" Interrupts"
""
);
AI_END_CUSTOM_NPC()