mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-09-04 11:44:45 +03:00
2381 lines
67 KiB
C++
2381 lines
67 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "npc_turret_floor.h"
|
|
#include "ai_senses.h"
|
|
#include "ai_memory.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "ammodef.h"
|
|
#include "hl2/hl2_player.h"
|
|
#include "soundenvelope.h"
|
|
#include "physics_saverestore.h"
|
|
#include "IEffects.h"
|
|
#include "basehlcombatweapon_shared.h"
|
|
#include "phys_controller.h"
|
|
#include "ai_interactions.h"
|
|
#include "Sprite.h"
|
|
#include "beam_shared.h"
|
|
#include "props.h"
|
|
#include "particle_parse.h"
|
|
|
|
#ifdef PORTAL
|
|
#include "prop_portal_shared.h"
|
|
#include "portal_util_shared.h"
|
|
#endif
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
const char *GetMassEquivalent(float flMass);
|
|
|
|
#define DISABLE_SHOT 0
|
|
|
|
//Debug visualization
|
|
ConVar g_debug_turret("g_debug_turret", "0");
|
|
|
|
extern ConVar physcannon_tracelength;
|
|
|
|
// Interactions
|
|
int g_interactionTurretStillStanding = 0;
|
|
|
|
float CNPC_FloorTurret::fMaxTipControllerVelocity = 300.0f * 300.0f;
|
|
float CNPC_FloorTurret::fMaxTipControllerAngularVelocity = 90.0f * 90.0f;
|
|
|
|
#define LASER_BEAM_SPRITE "effects/laser1.vmt"
|
|
|
|
#define FLOOR_TURRET_MODEL "models/combine_turrets/floor_turret.mdl"
|
|
#define FLOOR_TURRET_MODEL_CITIZEN "models/combine_turrets/citizen_turret.mdl"
|
|
#define FLOOR_TURRET_GLOW_SPRITE "sprites/glow1.vmt"
|
|
// #define FLOOR_TURRET_BC_YAW "aim_yaw"
|
|
// #define FLOOR_TURRET_BC_PITCH "aim_pitch"
|
|
#define FLOOR_TURRET_RANGE 1200
|
|
#define FLOOR_TURRET_MAX_WAIT 5
|
|
#define FLOOR_TURRET_SHORT_WAIT 2.0 // Used for FAST_RETIRE spawnflag
|
|
#define FLOOR_TURRET_PING_TIME 1.0f //LPB!!
|
|
|
|
#define FLOOR_TURRET_VOICE_PITCH_LOW 45
|
|
#define FLOOR_TURRET_VOICE_PITCH_HIGH 100
|
|
|
|
//Aiming variables
|
|
#define FLOOR_TURRET_MAX_NOHARM_PERIOD 0.0f
|
|
#define FLOOR_TURRET_MAX_GRACE_PERIOD 3.0f
|
|
|
|
//Activities
|
|
int ACT_FLOOR_TURRET_OPEN;
|
|
int ACT_FLOOR_TURRET_CLOSE;
|
|
int ACT_FLOOR_TURRET_OPEN_IDLE;
|
|
int ACT_FLOOR_TURRET_CLOSED_IDLE;
|
|
int ACT_FLOOR_TURRET_FIRE;
|
|
|
|
//Datatable
|
|
BEGIN_DATADESC(CNPC_FloorTurret)
|
|
|
|
DEFINE_FIELD(m_iAmmoType, FIELD_INTEGER),
|
|
DEFINE_FIELD(m_bAutoStart, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bActive, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bBlinkState, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bEnabled, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bNoAlarmSounds, FIELD_BOOLEAN),
|
|
|
|
DEFINE_FIELD(m_flShotTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_flLastSight, FIELD_TIME),
|
|
DEFINE_FIELD(m_flThrashTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_flPingTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_flNextActivateSoundTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_bCarriedByPlayer, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_bUseCarryAngles, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_flPlayerDropTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_hLastNPCToKickMe, FIELD_EHANDLE),
|
|
DEFINE_FIELD(m_flKnockOverFailedTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_flDestructStartTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_hFizzleEffect, FIELD_EHANDLE),
|
|
|
|
DEFINE_FIELD(m_vecGoalAngles, FIELD_VECTOR),
|
|
DEFINE_FIELD(m_iEyeAttachment, FIELD_INTEGER),
|
|
DEFINE_FIELD(m_iMuzzleAttachment, FIELD_INTEGER),
|
|
DEFINE_FIELD(m_iEyeState, FIELD_INTEGER),
|
|
DEFINE_FIELD(m_hEyeGlow, FIELD_EHANDLE),
|
|
DEFINE_FIELD(m_pMotionController, FIELD_EHANDLE),
|
|
DEFINE_FIELD(m_vecEnemyLKP, FIELD_VECTOR),
|
|
DEFINE_FIELD(m_hLaser, FIELD_EHANDLE),
|
|
DEFINE_FIELD(m_bSelfDestructing, FIELD_BOOLEAN),
|
|
|
|
DEFINE_FIELD(m_hPhysicsAttacker, FIELD_EHANDLE),
|
|
DEFINE_FIELD(m_flLastPhysicsInfluenceTime, FIELD_TIME),
|
|
|
|
DEFINE_FIELD(m_bHackedByAlyx, FIELD_BOOLEAN),
|
|
|
|
DEFINE_KEYFIELD(m_iKeySkin, FIELD_INTEGER, "SkinNumber"),
|
|
|
|
DEFINE_THINKFUNC(Retire),
|
|
DEFINE_THINKFUNC(Deploy),
|
|
DEFINE_THINKFUNC(ActiveThink),
|
|
DEFINE_THINKFUNC(SearchThink),
|
|
DEFINE_THINKFUNC(AutoSearchThink),
|
|
DEFINE_THINKFUNC(TippedThink),
|
|
DEFINE_THINKFUNC(InactiveThink),
|
|
DEFINE_THINKFUNC(SuppressThink),
|
|
DEFINE_THINKFUNC(DisabledThink),
|
|
DEFINE_THINKFUNC(SelfDestructThink),
|
|
DEFINE_THINKFUNC(BreakThink),
|
|
|
|
DEFINE_USEFUNC(ToggleUse),
|
|
|
|
// Inputs
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable),
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable),
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "DepleteAmmo", InputDepleteAmmo),
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "RestoreAmmo", InputRestoreAmmo),
|
|
DEFINE_INPUTFUNC(FIELD_VOID, "SelfDestruct", InputSelfDestruct),
|
|
|
|
DEFINE_OUTPUT(m_OnDeploy, "OnDeploy"),
|
|
DEFINE_OUTPUT(m_OnRetire, "OnRetire"),
|
|
DEFINE_OUTPUT(m_OnTipped, "OnTipped"),
|
|
DEFINE_OUTPUT(m_OnPhysGunPickup, "OnPhysGunPickup"),
|
|
DEFINE_OUTPUT(m_OnPhysGunDrop, "OnPhysGunDrop"),
|
|
|
|
DEFINE_BASENPCINTERACTABLE_DATADESC(),
|
|
|
|
// DEFINE_FIELD( m_ShotSounds, FIELD_SHORT ),
|
|
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS(npc_turret_floor, CNPC_FloorTurret);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Constructor
|
|
//-----------------------------------------------------------------------------
|
|
CNPC_FloorTurret::CNPC_FloorTurret(void) :
|
|
m_bActive(false),
|
|
m_hEyeGlow(NULL),
|
|
m_hLaser(NULL),
|
|
m_iAmmoType(-1),
|
|
m_bAutoStart(false),
|
|
m_flPingTime(0.0f),
|
|
m_flNextActivateSoundTime(0.0f),
|
|
m_bCarriedByPlayer(false),
|
|
m_bUseCarryAngles(false),
|
|
m_flPlayerDropTime(0.0f),
|
|
m_flShotTime(0.0f),
|
|
m_flLastSight(0.0f),
|
|
m_bBlinkState(false),
|
|
m_flThrashTime(0.0f),
|
|
m_pMotionController(NULL),
|
|
m_bEnabled(false),
|
|
m_bSelfDestructing(false)
|
|
{
|
|
m_vecGoalAngles.Init();
|
|
|
|
m_vecEnemyLKP = vec3_invalid;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
Class_T CNPC_FloorTurret::Classify(void)
|
|
{
|
|
if (m_bEnabled)
|
|
{
|
|
// Hacked or friendly turrets don't attack players
|
|
if (m_bHackedByAlyx || IsCitizenTurret())
|
|
return CLASS_PLAYER_ALLY;
|
|
|
|
return CLASS_COMBINE;
|
|
}
|
|
|
|
return CLASS_NONE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::UpdateOnRemove(void)
|
|
{
|
|
if (m_pMotionController != NULL)
|
|
{
|
|
UTIL_Remove(m_pMotionController);
|
|
m_pMotionController = NULL;
|
|
}
|
|
|
|
if (m_hLaser != NULL)
|
|
{
|
|
UTIL_Remove(m_hLaser);
|
|
m_hLaser = NULL;
|
|
}
|
|
|
|
if (m_hEyeGlow != NULL)
|
|
{
|
|
UTIL_Remove(m_hEyeGlow);
|
|
m_hEyeGlow = NULL;
|
|
}
|
|
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Precache
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Precache(void)
|
|
{
|
|
const char *pModelName = STRING(GetModelName());
|
|
pModelName = (pModelName && pModelName[0] != '\0') ? pModelName : FLOOR_TURRET_MODEL;
|
|
PrecacheModel(pModelName);
|
|
PrecacheModel(FLOOR_TURRET_GLOW_SPRITE);
|
|
|
|
PropBreakablePrecacheAll(MAKE_STRING(pModelName));
|
|
|
|
if (IsCitizenTurret())
|
|
{
|
|
PrecacheModel(LASER_BEAM_SPRITE);
|
|
PrecacheScriptSound("NPC_FloorTurret.AlarmPing");
|
|
}
|
|
|
|
// Activities
|
|
ADD_CUSTOM_ACTIVITY(CNPC_FloorTurret, ACT_FLOOR_TURRET_OPEN);
|
|
ADD_CUSTOM_ACTIVITY(CNPC_FloorTurret, ACT_FLOOR_TURRET_CLOSE);
|
|
ADD_CUSTOM_ACTIVITY(CNPC_FloorTurret, ACT_FLOOR_TURRET_CLOSED_IDLE);
|
|
ADD_CUSTOM_ACTIVITY(CNPC_FloorTurret, ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
ADD_CUSTOM_ACTIVITY(CNPC_FloorTurret, ACT_FLOOR_TURRET_FIRE);
|
|
|
|
PrecacheScriptSound("NPC_FloorTurret.Retire");
|
|
PrecacheScriptSound("NPC_FloorTurret.Deploy");
|
|
PrecacheScriptSound("NPC_FloorTurret.Move");
|
|
PrecacheScriptSound("NPC_Combine.WeaponBash");
|
|
PrecacheScriptSound("NPC_FloorTurret.Activate");
|
|
PrecacheScriptSound("NPC_FloorTurret.Alert");
|
|
m_ShotSounds = PrecacheScriptSound("NPC_FloorTurret.ShotSounds");
|
|
PrecacheScriptSound("NPC_FloorTurret.Die");
|
|
PrecacheScriptSound("NPC_FloorTurret.Retract");
|
|
PrecacheScriptSound("NPC_FloorTurret.Alarm");
|
|
PrecacheScriptSound("NPC_FloorTurret.Ping");
|
|
PrecacheScriptSound("NPC_FloorTurret.DryFire");
|
|
PrecacheScriptSound("NPC_FloorTurret.Destruct");
|
|
PrecacheParticleSystem("ar2_normal");
|
|
#ifdef HL2_EPISODIC
|
|
PrecacheParticleSystem("explosion_turret_break");
|
|
#endif // HL2_EPISODIC
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Spawn the entity
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Spawn(void)
|
|
{
|
|
Precache();
|
|
|
|
const char *pModelName = STRING(GetModelName());
|
|
SetModel((pModelName && pModelName[0] != '\0') ? pModelName : FLOOR_TURRET_MODEL);
|
|
|
|
// If we're a citizen turret, we use a different skin
|
|
if (IsCitizenTurret())
|
|
{
|
|
if (m_iKeySkin == 0)
|
|
{ // select a "random" skin -- rather than being truly random, use a static variable
|
|
// to cycle through them evenly. The static won't be saved across save/load, but
|
|
// frankly I don't care so much about that.
|
|
// m_nSkin = random->RandomInt( 1, 4 );
|
|
static unsigned int nextSkin = 0;
|
|
m_nSkin = nextSkin + 1;
|
|
|
|
// add one mod 4
|
|
nextSkin = (nextSkin + 1) & 0x03;
|
|
}
|
|
else
|
|
{ // at least make sure that it's in the right range
|
|
m_nSkin = clamp(m_iKeySkin, 1, 4);
|
|
}
|
|
}
|
|
|
|
BaseClass::Spawn();
|
|
|
|
SetBlocksLOS(false);
|
|
|
|
m_HackedGunPos = Vector(0, 0, 12.75);
|
|
SetViewOffset(EyeOffset(ACT_IDLE));
|
|
m_flFieldOfView = 0.4f; // 60 degrees
|
|
m_takedamage = DAMAGE_EVENTS_ONLY;
|
|
m_iHealth = 100;
|
|
m_iMaxHealth = 100;
|
|
|
|
AddEFlags(EFL_NO_DISSOLVE);
|
|
|
|
m_bSpawnNoRagdoll = true;
|
|
|
|
SetPoseParameter(m_poseAim_Yaw, 0);
|
|
SetPoseParameter(m_poseAim_Pitch, 0);
|
|
|
|
m_iAmmoType = GetAmmoDef()->Index("PISTOL");
|
|
|
|
m_iMuzzleAttachment = LookupAttachment("eyes");
|
|
m_iEyeAttachment = LookupAttachment("light");
|
|
|
|
// FIXME: Do we ever need m_bAutoStart? (Sawyer)
|
|
m_spawnflags |= SF_FLOOR_TURRET_AUTOACTIVATE;
|
|
|
|
//Set our autostart state
|
|
m_bAutoStart = !!(m_spawnflags & SF_FLOOR_TURRET_AUTOACTIVATE);
|
|
m_bEnabled = ((m_spawnflags & SF_FLOOR_TURRET_STARTINACTIVE) == false);
|
|
|
|
//Do we start active?
|
|
if (m_bAutoStart && m_bEnabled)
|
|
{
|
|
SetThink(&CNPC_FloorTurret::AutoSearchThink);
|
|
SetEyeState(TURRET_EYE_DORMANT);
|
|
}
|
|
else
|
|
{
|
|
SetThink(&CNPC_FloorTurret::DisabledThink);
|
|
SetEyeState(TURRET_EYE_DISABLED);
|
|
}
|
|
|
|
// Start
|
|
if (OnSide())
|
|
{
|
|
SetThink(&CNPC_FloorTurret::DisabledThink);
|
|
SetEyeState(TURRET_EYE_DISABLED);
|
|
}
|
|
|
|
//Stagger our starting times
|
|
SetNextThink(gpGlobals->curtime + random->RandomFloat(0.1f, 0.3f));
|
|
|
|
SetUse(&CNPC_FloorTurret::ToggleUse);
|
|
|
|
// Don't allow us to skip animation setup because our attachments are critical to us!
|
|
SetBoneCacheFlags(BCF_NO_ANIMATION_SKIP);
|
|
|
|
CreateVPhysics();
|
|
|
|
SetState(NPC_STATE_IDLE);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Activate(void)
|
|
{
|
|
BaseClass::Activate();
|
|
|
|
// Force the eye state to the current state so that our glows are recreated after transitions
|
|
SetEyeState(m_iEyeState);
|
|
|
|
if (!m_pMotionController)
|
|
{
|
|
// Create the motion controller
|
|
m_pMotionController = CTurretTipController::CreateTipController(this);
|
|
|
|
// Enable the controller
|
|
if (m_pMotionController != NULL)
|
|
{
|
|
m_pMotionController->Enable();
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool CNPC_FloorTurret::CreateVPhysics(void)
|
|
{
|
|
//Spawn our physics hull
|
|
if (VPhysicsInitNormal(SOLID_VPHYSICS, 0, false) == NULL)
|
|
{
|
|
DevMsg("npc_turret_floor unable to spawn physics object!\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retract and stop attacking
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Retire(void)
|
|
{
|
|
if (PreThink(TURRET_RETIRING))
|
|
return;
|
|
|
|
//Level out the turret
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
|
|
//Set ourselves to close
|
|
if (GetActivity() != ACT_FLOOR_TURRET_CLOSE)
|
|
{
|
|
//Set our visible state to dormant
|
|
SetEyeState(TURRET_EYE_DORMANT);
|
|
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
|
|
//If we're done moving to our desired facing, close up
|
|
if (UpdateFacing() == false)
|
|
{
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_CLOSE);
|
|
EmitSound("NPC_FloorTurret.Retire");
|
|
|
|
//Notify of the retraction
|
|
m_OnRetire.FireOutput(NULL, this);
|
|
}
|
|
}
|
|
else if (IsActivityFinished())
|
|
{
|
|
m_bActive = false;
|
|
m_flLastSight = 0;
|
|
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_CLOSED_IDLE);
|
|
|
|
//Go back to auto searching
|
|
if (m_bAutoStart)
|
|
{
|
|
SetThink(&CNPC_FloorTurret::AutoSearchThink);
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
}
|
|
else
|
|
{
|
|
//Set our visible state to dormant
|
|
SetEyeState(TURRET_EYE_DISABLED);
|
|
SetThink(&CNPC_FloorTurret::DisabledThink);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Deploy and start attacking
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Deploy(void)
|
|
{
|
|
if (PreThink(TURRET_DEPLOYING))
|
|
return;
|
|
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
|
|
//Show we've seen a target
|
|
SetEyeState(TURRET_EYE_SEE_TARGET);
|
|
|
|
//Open if we're not already
|
|
if (GetActivity() != ACT_FLOOR_TURRET_OPEN)
|
|
{
|
|
m_bActive = true;
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN);
|
|
EmitSound("NPC_FloorTurret.Deploy");
|
|
|
|
//Notify we're deploying
|
|
m_OnDeploy.FireOutput(NULL, this);
|
|
}
|
|
|
|
//If we're done, then start searching
|
|
if (IsActivityFinished())
|
|
{
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
|
|
m_flShotTime = gpGlobals->curtime + 1.0f;
|
|
|
|
m_flPlaybackRate = 0;
|
|
SetThink(&CNPC_FloorTurret::SearchThink);
|
|
|
|
EmitSound("NPC_FloorTurret.Move");
|
|
}
|
|
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason)
|
|
{
|
|
m_hPhysicsAttacker = pPhysGunUser;
|
|
m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
|
|
|
|
// Drop our mass a lot so that we can be moved easily with +USE
|
|
if (reason != PUNTED_BY_CANNON)
|
|
{
|
|
Assert(VPhysicsGetObject());
|
|
|
|
m_bCarriedByPlayer = true;
|
|
m_OnPhysGunPickup.FireOutput(this, this);
|
|
|
|
// We want to use preferred carry angles if we're not nicely upright
|
|
Vector vecToTurret = pPhysGunUser->GetAbsOrigin() - GetAbsOrigin();
|
|
vecToTurret.z = 0;
|
|
VectorNormalize(vecToTurret);
|
|
|
|
// We want to use preferred carry angles if we're not nicely upright
|
|
Vector forward, up;
|
|
GetVectors(&forward, NULL, &up);
|
|
|
|
bool bUpright = DotProduct(up, Vector(0, 0, 1)) > 0.9f;
|
|
bool bBehind = DotProduct(vecToTurret, forward) < 0.85f;
|
|
|
|
// Correct our angles only if we're not upright or we're mostly behind the turret
|
|
if (hl2_episodic.GetBool())
|
|
{
|
|
m_bUseCarryAngles = (bUpright == false || bBehind);
|
|
}
|
|
else
|
|
{
|
|
m_bUseCarryAngles = (bUpright == false);
|
|
}
|
|
}
|
|
|
|
// Clear out our last NPC to kick me, because it makes no sense now
|
|
m_hLastNPCToKickMe = NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::OnPhysGunDrop(CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason)
|
|
{
|
|
m_hPhysicsAttacker = pPhysGunUser;
|
|
m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
|
|
|
|
m_bCarriedByPlayer = false;
|
|
m_bUseCarryAngles = false;
|
|
m_OnPhysGunDrop.FireOutput(this, this);
|
|
|
|
// If this is a friendly turret, remember that it was just dropped
|
|
if (IRelationType(pPhysGunUser) != D_HT)
|
|
{
|
|
m_flPlayerDropTime = gpGlobals->curtime + 2.0;
|
|
}
|
|
|
|
// Restore our mass to the original value
|
|
Assert(VPhysicsGetObject());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Whether this should return carry angles
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::HasPreferredCarryAnglesForPlayer(CBasePlayer *pPlayer)
|
|
{
|
|
// Don't use preferred angles on enemy turrets
|
|
if (IRelationType(pPlayer) == D_HT)
|
|
return false;
|
|
|
|
return m_bUseCarryAngles;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::OnAttemptPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason)
|
|
{
|
|
// Prevent players pulling enemy turrets from afar if they're in front of the turret
|
|
if (reason == PICKED_UP_BY_CANNON && IRelationType(pPhysGunUser) == D_HT)
|
|
{
|
|
Vector vecForward;
|
|
GetVectors(&vecForward, NULL, NULL);
|
|
Vector vecForce = (pPhysGunUser->GetAbsOrigin() - GetAbsOrigin());
|
|
float flDistance = VectorNormalize(vecForce);
|
|
|
|
// If it's over the physcannon tracelength, we're pulling it
|
|
if (flDistance > physcannon_tracelength.GetFloat())
|
|
{
|
|
float flDot = DotProduct(vecForward, vecForce);
|
|
if (flDot > 0.5)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter *sourceEnt)
|
|
{
|
|
if (interactionType == g_interactionCombineBash)
|
|
{
|
|
// We've been bashed by a combine soldier. Remember who it was, if we haven't got an active kicker
|
|
if (!m_hLastNPCToKickMe)
|
|
{
|
|
m_hLastNPCToKickMe = sourceEnt;
|
|
m_flKnockOverFailedTime = gpGlobals->curtime + 3.0;
|
|
}
|
|
|
|
// Get knocked away
|
|
Vector forward, up;
|
|
AngleVectors(sourceEnt->GetLocalAngles(), &forward, NULL, &up);
|
|
ApplyAbsVelocityImpulse(forward * 100 + up * 50);
|
|
CTakeDamageInfo info(sourceEnt, sourceEnt, 30, DMG_CLUB);
|
|
CalculateMeleeDamageForce(&info, forward, GetAbsOrigin());
|
|
TakeDamage(info);
|
|
|
|
EmitSound("NPC_Combine.WeaponBash");
|
|
return true;
|
|
}
|
|
|
|
return BaseClass::HandleInteraction(interactionType, data, sourceEnt);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns the speed at which the turret can face a target
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_FloorTurret::MaxYawSpeed(void)
|
|
{
|
|
//TODO: Scale by difficulty?
|
|
return 360.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Return true if this turret was recently dropped by a player
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::WasJustDroppedByPlayer(void)
|
|
{
|
|
if (m_flPlayerDropTime > gpGlobals->curtime)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Causes the turret to face its desired angles
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::UpdateFacing(void)
|
|
{
|
|
bool bMoved = false;
|
|
UpdateMuzzleMatrix();
|
|
|
|
Vector vecGoalDir;
|
|
AngleVectors(m_vecGoalAngles, &vecGoalDir);
|
|
|
|
Vector vecGoalLocalDir;
|
|
VectorIRotate(vecGoalDir, m_muzzleToWorld, vecGoalLocalDir);
|
|
|
|
if (g_debug_turret.GetBool())
|
|
{
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
|
|
MatrixGetColumn(m_muzzleToWorld, 3, vecMuzzle);
|
|
MatrixGetColumn(m_muzzleToWorld, 0, vecMuzzleDir);
|
|
|
|
NDebugOverlay::Cross3D(vecMuzzle, -Vector(2, 2, 2), Vector(2, 2, 2), 255, 255, 0, false, 0.05);
|
|
NDebugOverlay::Cross3D(vecMuzzle + (vecMuzzleDir * 256), -Vector(2, 2, 2), Vector(2, 2, 2), 255, 255, 0, false, 0.05);
|
|
NDebugOverlay::Line(vecMuzzle, vecMuzzle + (vecMuzzleDir * 256), 255, 255, 0, false, 0.05);
|
|
|
|
NDebugOverlay::Cross3D(vecMuzzle, -Vector(2, 2, 2), Vector(2, 2, 2), 255, 0, 0, false, 0.05);
|
|
NDebugOverlay::Cross3D(vecMuzzle + (vecGoalDir * 256), -Vector(2, 2, 2), Vector(2, 2, 2), 255, 0, 0, false, 0.05);
|
|
NDebugOverlay::Line(vecMuzzle, vecMuzzle + (vecGoalDir * 256), 255, 0, 0, false, 0.05);
|
|
}
|
|
|
|
QAngle vecGoalLocalAngles;
|
|
VectorAngles(vecGoalLocalDir, vecGoalLocalAngles);
|
|
|
|
// Update pitch
|
|
float flDiff = AngleNormalize(UTIL_ApproachAngle(vecGoalLocalAngles.x, 0.0, 0.05f * MaxYawSpeed()));
|
|
|
|
SetPoseParameter(m_poseAim_Pitch, GetPoseParameter(m_poseAim_Pitch) + (flDiff / 1.5f));
|
|
|
|
if (fabs(flDiff) > 0.1f)
|
|
{
|
|
bMoved = true;
|
|
}
|
|
|
|
// Update yaw
|
|
flDiff = AngleNormalize(UTIL_ApproachAngle(vecGoalLocalAngles.y, 0.0, 0.05f * MaxYawSpeed()));
|
|
|
|
SetPoseParameter(m_poseAim_Yaw, GetPoseParameter(m_poseAim_Yaw) + (flDiff / 1.5f));
|
|
|
|
if (fabs(flDiff) > 0.1f)
|
|
{
|
|
bMoved = true;
|
|
}
|
|
|
|
// You're going to make decisions based on this info. So bump the bone cache after you calculate everything
|
|
InvalidateBoneCache();
|
|
|
|
return bMoved;
|
|
}
|
|
|
|
void CNPC_FloorTurret::DryFire(void)
|
|
{
|
|
EmitSound("NPC_FloorTurret.DryFire");
|
|
EmitSound("NPC_FloorTurret.Activate");
|
|
|
|
if (RandomFloat(0, 1) > 0.5)
|
|
{
|
|
m_flShotTime = gpGlobals->curtime + random->RandomFloat(1, 2.5);
|
|
}
|
|
else
|
|
{
|
|
m_flShotTime = gpGlobals->curtime;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Turret will continue to fire on a target's position when it loses sight of it
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::SuppressThink(void)
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if (PreThink(TURRET_SUPPRESSING))
|
|
return;
|
|
|
|
//Update our think time
|
|
SetNextThink(gpGlobals->curtime + 0.1f);
|
|
|
|
// Look for a new enemy
|
|
HackFindEnemy();
|
|
|
|
//If we've acquired an enemy, start firing at it
|
|
if (!GetEnemy())
|
|
{
|
|
SetThink(&CNPC_FloorTurret::ActiveThink);
|
|
return;
|
|
}
|
|
|
|
//See if we're done suppressing
|
|
if (gpGlobals->curtime > m_flLastSight)
|
|
{
|
|
// Should we look for a new target?
|
|
ClearEnemyMemory();
|
|
SetEnemy(NULL);
|
|
SetThink(&CNPC_FloorTurret::SearchThink);
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
|
|
SpinDown();
|
|
|
|
if (m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE)
|
|
{
|
|
// Retire quickly in this case. (The case where we saw the player, but he hid again).
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
|
|
}
|
|
else
|
|
{
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//Get our shot positions
|
|
Vector vecMid = EyePosition();
|
|
Vector vecMidEnemy = m_vecEnemyLKP;
|
|
|
|
//Calculate dir and dist to enemy
|
|
Vector vecDirToEnemy = vecMidEnemy - vecMid;
|
|
|
|
//We want to look at the enemy's eyes so we don't jitter
|
|
Vector vecDirToEnemyEyes = vecMidEnemy - vecMid;
|
|
VectorNormalize(vecDirToEnemyEyes);
|
|
|
|
QAngle vecAnglesToEnemy;
|
|
VectorAngles(vecDirToEnemyEyes, vecAnglesToEnemy);
|
|
|
|
//Draw debug info
|
|
if (g_debug_turret.GetBool())
|
|
{
|
|
NDebugOverlay::Cross3D(vecMid, -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 0.05);
|
|
NDebugOverlay::Cross3D(vecMidEnemy, -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 0.05);
|
|
NDebugOverlay::Line(vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f);
|
|
}
|
|
|
|
if (m_flShotTime < gpGlobals->curtime && m_vecEnemyLKP != vec3_invalid)
|
|
{
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
UpdateMuzzleMatrix();
|
|
MatrixGetColumn(m_muzzleToWorld, 0, vecMuzzleDir);
|
|
MatrixGetColumn(m_muzzleToWorld, 3, vecMuzzle);
|
|
|
|
//Fire the gun
|
|
if (DotProduct(vecDirToEnemy, vecMuzzleDir) >= 0.9848) // 10 degree slop
|
|
{
|
|
if (m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO)
|
|
{
|
|
DryFire();
|
|
}
|
|
else
|
|
{
|
|
ResetActivity();
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_FIRE);
|
|
|
|
//Fire the weapon
|
|
#if !DISABLE_SHOT
|
|
Shoot(vecMuzzle, vecMuzzleDir);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
}
|
|
|
|
//If we can see our enemy, face it
|
|
m_vecGoalAngles.y = vecAnglesToEnemy.y;
|
|
m_vecGoalAngles.x = vecAnglesToEnemy.x;
|
|
|
|
//Turn to face
|
|
UpdateFacing();
|
|
}
|
|
void CNPC_FloorTurret::MakeTracer(const Vector &vecTracerSrc, const trace_t &tr, int iTracerType)
|
|
{
|
|
#if 1
|
|
//BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
|
|
if (cvar->FindVar("oc_particle_tracers")->GetInt())
|
|
UTIL_ParticleTracer("weapon_npc_tracer_ar2", vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, iTracerType == TRACER_LINE_AND_WHIZ);//OverCharged
|
|
else
|
|
UTIL_Tracer(vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 5000, true, "AR2Tracer");
|
|
#else
|
|
CBeam *pBeam;
|
|
int width = 2;
|
|
|
|
pBeam = CBeam::BeamCreate(GROUNDTURRET_BEAM_SPRITE, width);
|
|
if (!pBeam)
|
|
return;
|
|
|
|
pBeam->SetStartPos(vecTracerSrc);
|
|
pBeam->SetEndPos(tr.endpos);
|
|
pBeam->SetWidth(width);
|
|
pBeam->SetEndWidth(width / 4.0f);
|
|
|
|
pBeam->SetBrightness(100);
|
|
pBeam->SetColor(0, 145 + random->RandomInt(-16, 16), 255);
|
|
pBeam->RelinkBeam();
|
|
pBeam->LiveForTime(random->RandomFloat(0.2f, 0.5f));
|
|
#endif
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allows the turret to fire on targets if they're visible
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::ActiveThink(void)
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if (PreThink(TURRET_ACTIVE))
|
|
return;
|
|
|
|
HackFindEnemy();
|
|
|
|
//Update our think time
|
|
SetNextThink(gpGlobals->curtime + 0.1f);
|
|
|
|
//If we've become inactive, go back to searching
|
|
if ((m_bActive == false) || (GetEnemy() == NULL))
|
|
{
|
|
SetEnemy(NULL);
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
|
|
SetThink(&CNPC_FloorTurret::SearchThink);
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
return;
|
|
}
|
|
|
|
//Get our shot positions
|
|
Vector vecMid = EyePosition();
|
|
Vector vecMidEnemy = GetEnemy()->BodyTarget(vecMid);
|
|
|
|
// Store off our last seen location so we can suppress it later
|
|
m_vecEnemyLKP = vecMidEnemy;
|
|
|
|
//Look for our current enemy
|
|
bool bEnemyInFOV = FInViewCone(GetEnemy());
|
|
bool bEnemyVisible = FVisible(GetEnemy()) && GetEnemy()->IsAlive();
|
|
|
|
// Robin: This is a hack to get around the fact that the muzzle for the turret
|
|
// is outside it's vcollide. This means that if it leans against a thin wall,
|
|
// the muzzle can be on the other side of the wall, where it's then able to see
|
|
// and shoot at targets. This check ensures that nothing has come between the
|
|
// center of the turret and the muzzle.
|
|
if (bEnemyVisible)
|
|
{
|
|
trace_t tr;
|
|
Vector vecCenter;
|
|
CollisionProp()->CollisionToWorldSpace(Vector(0, 0, 52), &vecCenter);
|
|
UTIL_TraceLine(vecCenter, vecMid, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
bEnemyVisible = false;
|
|
}
|
|
}
|
|
|
|
//Calculate dir and dist to enemy
|
|
Vector vecDirToEnemy = vecMidEnemy - vecMid;
|
|
float flDistToEnemy = VectorNormalize(vecDirToEnemy);
|
|
|
|
//Draw debug info
|
|
if (g_debug_turret.GetBool())
|
|
{
|
|
NDebugOverlay::Cross3D(vecMid, -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 0.05);
|
|
NDebugOverlay::Cross3D(GetEnemy()->WorldSpaceCenter(), -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 0.05);
|
|
NDebugOverlay::Line(vecMid, GetEnemy()->WorldSpaceCenter(), 0, 255, 0, false, 0.05);
|
|
|
|
NDebugOverlay::Cross3D(vecMid, -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 0.05);
|
|
NDebugOverlay::Cross3D(vecMidEnemy, -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 0.05);
|
|
NDebugOverlay::Line(vecMid, vecMidEnemy, 0, 255, 0, false, 0.05f);
|
|
}
|
|
|
|
//See if they're past our FOV of attack
|
|
if (bEnemyInFOV == false)
|
|
{
|
|
// Should we look for a new target?
|
|
ClearEnemyMemory();
|
|
SetEnemy(NULL);
|
|
|
|
if (m_spawnflags & SF_FLOOR_TURRET_FASTRETIRE)
|
|
{
|
|
// Retire quickly in this case. (The case where we saw the player, but he hid again).
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_SHORT_WAIT;
|
|
}
|
|
else
|
|
{
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
|
|
}
|
|
|
|
SetThink(&CNPC_FloorTurret::SearchThink);
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
|
|
SpinDown();
|
|
|
|
return;
|
|
}
|
|
|
|
//Current enemy is not visible
|
|
if ((bEnemyVisible == false) || (flDistToEnemy > FLOOR_TURRET_RANGE))
|
|
{
|
|
m_flLastSight = gpGlobals->curtime + 2.0f;
|
|
|
|
ClearEnemyMemory();
|
|
SetEnemy(NULL);
|
|
SetThink(&CNPC_FloorTurret::SuppressThink);
|
|
|
|
return;
|
|
}
|
|
|
|
if (g_debug_turret.GetBool())
|
|
{
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
|
|
UpdateMuzzleMatrix();
|
|
MatrixGetColumn(m_muzzleToWorld, 0, vecMuzzleDir);
|
|
MatrixGetColumn(m_muzzleToWorld, 3, vecMuzzle);
|
|
|
|
// Visualize vertical firing ranges
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
QAngle angMaxDownPitch = GetAbsAngles();
|
|
|
|
switch (i)
|
|
{
|
|
case 0: angMaxDownPitch.x -= 15; break;
|
|
case 1: angMaxDownPitch.x += 15; break;
|
|
case 2: angMaxDownPitch.x -= 25; break;
|
|
case 3: angMaxDownPitch.x += 25; break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Vector vecMaxDownPitch;
|
|
AngleVectors(angMaxDownPitch, &vecMaxDownPitch);
|
|
NDebugOverlay::Line(vecMuzzle, vecMuzzle + (vecMaxDownPitch * 256), 255, 255, 255, false, 0.1);
|
|
}
|
|
}
|
|
|
|
if (m_flShotTime < gpGlobals->curtime)
|
|
{
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
|
|
UpdateMuzzleMatrix();
|
|
MatrixGetColumn(m_muzzleToWorld, 0, vecMuzzleDir);
|
|
MatrixGetColumn(m_muzzleToWorld, 3, vecMuzzle);
|
|
|
|
Vector2D vecDirToEnemy2D = vecDirToEnemy.AsVector2D();
|
|
Vector2D vecMuzzleDir2D = vecMuzzleDir.AsVector2D();
|
|
|
|
bool bCanShoot = true;
|
|
float minCos3d = DOT_10DEGREE; // 10 degrees slop
|
|
|
|
if (flDistToEnemy < 60.0)
|
|
{
|
|
vecDirToEnemy2D.NormalizeInPlace();
|
|
vecMuzzleDir2D.NormalizeInPlace();
|
|
|
|
bCanShoot = (vecDirToEnemy2D.Dot(vecMuzzleDir2D) >= DOT_10DEGREE);
|
|
minCos3d = 0.7071; // 45 degrees
|
|
}
|
|
|
|
//Fire the gun
|
|
if (bCanShoot) // 10 degree slop XY
|
|
{
|
|
float dot3d = DotProduct(vecDirToEnemy, vecMuzzleDir);
|
|
|
|
if (m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO)
|
|
{
|
|
DryFire();
|
|
}
|
|
else
|
|
{
|
|
if (dot3d >= minCos3d)
|
|
{
|
|
ResetActivity();
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_FIRE);
|
|
|
|
//Fire the weapon
|
|
#if !DISABLE_SHOT
|
|
Shoot(vecMuzzle, vecMuzzleDir, (dot3d < DOT_10DEGREE));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
}
|
|
|
|
//If we can see our enemy, face it
|
|
if (bEnemyVisible)
|
|
{
|
|
//We want to look at the enemy's eyes so we don't jitter
|
|
Vector vecDirToEnemyEyes = GetEnemy()->WorldSpaceCenter() - vecMid;
|
|
VectorNormalize(vecDirToEnemyEyes);
|
|
|
|
QAngle vecAnglesToEnemy;
|
|
VectorAngles(vecDirToEnemyEyes, vecAnglesToEnemy);
|
|
|
|
m_vecGoalAngles.y = vecAnglesToEnemy.y;
|
|
m_vecGoalAngles.x = vecAnglesToEnemy.x;
|
|
}
|
|
|
|
//Turn to face
|
|
UpdateFacing();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Target doesn't exist or has eluded us, so search for one
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::SearchThink(void)
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if (PreThink(TURRET_SEARCHING))
|
|
return;
|
|
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
|
|
//If our enemy has died, pick a new enemy
|
|
if ((GetEnemy() != NULL) && (GetEnemy()->IsAlive() == false))
|
|
{
|
|
SetEnemy(NULL);
|
|
}
|
|
|
|
//Acquire the target
|
|
if (GetEnemy() == NULL)
|
|
{
|
|
HackFindEnemy();
|
|
}
|
|
|
|
//If we've found a target, spin up the barrel and start to attack
|
|
if (GetEnemy() != NULL)
|
|
{
|
|
//Give players a grace period
|
|
if (GetEnemy()->IsPlayer())
|
|
{
|
|
m_flShotTime = gpGlobals->curtime + 0.5f;
|
|
}
|
|
else
|
|
{
|
|
m_flShotTime = gpGlobals->curtime + 0.1f;
|
|
}
|
|
|
|
m_flLastSight = 0;
|
|
SetThink(&CNPC_FloorTurret::ActiveThink);
|
|
SetEyeState(TURRET_EYE_SEE_TARGET);
|
|
|
|
SpinUp();
|
|
|
|
if (gpGlobals->curtime > m_flNextActivateSoundTime)
|
|
{
|
|
EmitSound("NPC_FloorTurret.Activate");
|
|
m_flNextActivateSoundTime = gpGlobals->curtime + 3.0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Are we out of time and need to retract?
|
|
if (gpGlobals->curtime > m_flLastSight)
|
|
{
|
|
//Before we retrace, make sure that we are spun down.
|
|
m_flLastSight = 0;
|
|
SetThink(&CNPC_FloorTurret::Retire);
|
|
return;
|
|
}
|
|
|
|
//Display that we're scanning
|
|
m_vecGoalAngles.x = GetAbsAngles().x + (sin(gpGlobals->curtime * 1.0f) * 15.0f);
|
|
m_vecGoalAngles.y = GetAbsAngles().y + (sin(gpGlobals->curtime * 2.0f) * 60.0f);
|
|
|
|
//Turn and ping
|
|
UpdateFacing();
|
|
Ping();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Watch for a target to wander into our view
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::AutoSearchThink(void)
|
|
{
|
|
//Allow descended classes a chance to do something before the think function
|
|
if (PreThink(TURRET_AUTO_SEARCHING))
|
|
return;
|
|
|
|
//Spread out our thinking
|
|
SetNextThink(gpGlobals->curtime + random->RandomFloat(0.2f, 0.4f));
|
|
|
|
//If the enemy is dead, find a new one
|
|
if ((GetEnemy() != NULL) && (GetEnemy()->IsAlive() == false))
|
|
{
|
|
SetEnemy(NULL);
|
|
}
|
|
|
|
//Acquire Target
|
|
if (GetEnemy() == NULL)
|
|
{
|
|
HackFindEnemy();
|
|
}
|
|
|
|
//Deploy if we've got an active target
|
|
if (GetEnemy() != NULL)
|
|
{
|
|
SetThink(&CNPC_FloorTurret::Deploy);
|
|
if (!m_bNoAlarmSounds)
|
|
{
|
|
EmitSound("NPC_FloorTurret.Alert");
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fire!
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Shoot(const Vector &vecSrc, const Vector &vecDirToEnemy, bool bStrict)
|
|
{
|
|
FireBulletsInfo_t info;
|
|
|
|
if (!bStrict && GetEnemy() != NULL)
|
|
{
|
|
Vector vecDir = GetActualShootTrajectory(vecSrc);
|
|
|
|
info.m_vecSrc = vecSrc;
|
|
info.m_vecDirShooting = vecDir;
|
|
info.m_iTracerFreq = 1;
|
|
info.m_iShots = 1;
|
|
info.m_pAttacker = this;
|
|
info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
|
|
info.m_flDistance = MAX_COORD_RANGE;
|
|
info.m_iAmmoType = m_iAmmoType;
|
|
}
|
|
else
|
|
{
|
|
info.m_vecSrc = vecSrc;
|
|
info.m_vecDirShooting = vecDirToEnemy;
|
|
info.m_iTracerFreq = 1;
|
|
info.m_iShots = 1;
|
|
info.m_pAttacker = this;
|
|
info.m_vecSpread = GetAttackSpread(NULL, GetEnemy());
|
|
info.m_flDistance = MAX_COORD_RANGE;
|
|
info.m_iAmmoType = m_iAmmoType;
|
|
}
|
|
|
|
FireBullets(info);
|
|
EmitSound("NPC_FloorTurret.ShotSounds", m_ShotSounds);
|
|
DoMuzzleFlash();
|
|
//DoMuzzleFlashOVR(1, 6, 64, 128, 255, "ar2_normal");
|
|
}
|
|
|
|
void CNPC_FloorTurret::DoMuzzleFlash(void)
|
|
{
|
|
BaseClass::DoMuzzleFlash();
|
|
|
|
if (!cvar->FindVar("oc_particle_muzzleflashes")->GetInt())
|
|
{
|
|
CEffectData data;
|
|
data.m_nAttachmentIndex = 1;
|
|
data.m_nEntIndex = this->entindex();
|
|
DispatchEffect("Ar2MuzzleFlash", data);
|
|
}
|
|
else
|
|
{
|
|
CEffectData data;
|
|
data.m_nAttachmentIndex = 1;
|
|
data.m_nEntIndex = this->entindex();
|
|
DispatchEffect("Ar2MuzzleFlashParticle", data);
|
|
}
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEnemy -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::IsValidEnemy(CBaseEntity *pEnemy)
|
|
{
|
|
if (m_NPCState == NPC_STATE_DEAD)
|
|
return false;
|
|
|
|
// Don't shoot at other turrets.
|
|
if (pEnemy->m_iClassname == m_iClassname)
|
|
return false;
|
|
|
|
// If our eye is stuck in something, don't shoot
|
|
if (UTIL_PointContents(EyePosition()) & MASK_SHOT)
|
|
return false;
|
|
|
|
// Turrets have limited vertical aim capability
|
|
// - Can only aim +-15 degrees, + the 10 degree slop they're allowed.
|
|
Vector vEnemyPos = pEnemy->EyePosition();
|
|
|
|
#ifdef PORTAL
|
|
if (!FInViewCone(pEnemy) || !FVisible(pEnemy))
|
|
{
|
|
CProp_Portal *pPortal = FInViewConeThroughPortal(pEnemy);
|
|
|
|
if (pPortal)
|
|
{
|
|
// Translate our target across the portal
|
|
UTIL_Portal_PointTransform(pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyPos, vEnemyPos);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Vector los = (vEnemyPos - EyePosition());
|
|
|
|
QAngle angleToTarget;
|
|
VectorAngles(los, angleToTarget);
|
|
float flZDiff = fabs(AngleNormalize(angleToTarget.x - GetAbsAngles().x));
|
|
if (flZDiff > 28.0f && los.LengthSqr() > 4096.0f)
|
|
return false;
|
|
|
|
return BaseClass::IsValidEnemy(pEnemy);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pEnemy -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::CanBeAnEnemyOf(CBaseEntity *pEnemy)
|
|
{
|
|
// If we're out of ammo, make friendly companions ignore us
|
|
if (m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO)
|
|
{
|
|
if (pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL)
|
|
return false;
|
|
}
|
|
|
|
// If we're on the side, we're never anyone's enemy
|
|
if (OnSide())
|
|
return false;
|
|
|
|
return BaseClass::CanBeAnEnemyOf(pEnemy);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: The turret has been tipped over and will thrash for awhile
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::TippedThink(void)
|
|
{
|
|
// Update our PVS state
|
|
CheckPVSCondition();
|
|
|
|
//Animate
|
|
StudioFrameAdvance();
|
|
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
SetEnemy(NULL);
|
|
|
|
// If we're not on side anymore, stop thrashing
|
|
if (!OnSide())
|
|
{
|
|
ReturnToLife();
|
|
return;
|
|
}
|
|
|
|
//See if we should continue to thrash
|
|
if (gpGlobals->curtime < m_flThrashTime)
|
|
{
|
|
if (m_flShotTime < gpGlobals->curtime)
|
|
{
|
|
if (m_spawnflags & SF_FLOOR_TURRET_OUT_OF_AMMO)
|
|
{
|
|
DryFire();
|
|
}
|
|
else if (IsCitizenTurret() == false) // Citizen turrets don't wildly fire
|
|
{
|
|
Vector vecMuzzle, vecMuzzleDir;
|
|
UpdateMuzzleMatrix();
|
|
MatrixGetColumn(m_muzzleToWorld, 0, vecMuzzleDir);
|
|
MatrixGetColumn(m_muzzleToWorld, 3, vecMuzzle);
|
|
|
|
ResetActivity();
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_FIRE);
|
|
|
|
#if !DISABLE_SHOT
|
|
Shoot(vecMuzzle, vecMuzzleDir);
|
|
#endif
|
|
}
|
|
|
|
m_flShotTime = gpGlobals->curtime + 0.05f;
|
|
}
|
|
|
|
m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat(-60, 60);
|
|
m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat(-60, 60);
|
|
|
|
UpdateFacing();
|
|
}
|
|
else
|
|
{
|
|
//Face forward
|
|
m_vecGoalAngles = GetAbsAngles();
|
|
|
|
//Set ourselves to close
|
|
if (GetActivity() != ACT_FLOOR_TURRET_CLOSE)
|
|
{
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_OPEN_IDLE);
|
|
|
|
//If we're done moving to our desired facing, close up
|
|
if (UpdateFacing() == false)
|
|
{
|
|
//Make any last death noises and anims
|
|
EmitSound("NPC_FloorTurret.Die");
|
|
SpinDown();
|
|
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_CLOSE);
|
|
EmitSound("NPC_FloorTurret.Retract");
|
|
|
|
CTakeDamageInfo info;
|
|
info.SetDamage(1);
|
|
info.SetDamageType(DMG_CRUSH);
|
|
Event_Killed(info);
|
|
}
|
|
}
|
|
else if (IsActivityFinished())
|
|
{
|
|
m_bActive = false;
|
|
m_flLastSight = 0;
|
|
|
|
SetActivity((Activity)ACT_FLOOR_TURRET_CLOSED_IDLE);
|
|
|
|
// Don't need to store last NPC anymore, because I've been knocked over
|
|
if (m_hLastNPCToKickMe)
|
|
{
|
|
m_hLastNPCToKickMe = NULL;
|
|
m_flKnockOverFailedTime = 0;
|
|
}
|
|
|
|
//Try to look straight
|
|
if (UpdateFacing() == false)
|
|
{
|
|
m_OnTipped.FireOutput(this, this);
|
|
SetEyeState(TURRET_EYE_DEAD);
|
|
SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER);
|
|
|
|
// Start thinking slowly to see if we're ever set upright somehow
|
|
SetThink(&CNPC_FloorTurret::InactiveThink);
|
|
SetNextThink(gpGlobals->curtime + 1.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: This turret is dead. See if it ever becomes upright again, and if
|
|
// so, become active again.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InactiveThink(void)
|
|
{
|
|
// Update our PVS state
|
|
CheckPVSCondition();
|
|
|
|
// Wake up if we're not on our side
|
|
if (!OnSide() && m_bEnabled)
|
|
{
|
|
ReturnToLife();
|
|
return;
|
|
}
|
|
|
|
if (IsCitizenTurret())
|
|
{
|
|
// Blink if we have ammo or our current blink is "on" and we need to turn it off again
|
|
if (HasSpawnFlags(SF_FLOOR_TURRET_OUT_OF_AMMO) == false || m_bBlinkState)
|
|
{
|
|
// If we're on our side, ping and complain to the player
|
|
if (m_bBlinkState == false)
|
|
{
|
|
// Ping when the light is going to come back on
|
|
EmitSound("NPC_FloorTurret.AlarmPing");
|
|
}
|
|
|
|
SetEyeState(TURRET_EYE_ALARM);
|
|
SetNextThink(gpGlobals->curtime + 0.25f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetNextThink(gpGlobals->curtime + 1.0f);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::ReturnToLife(void)
|
|
{
|
|
m_flThrashTime = 0;
|
|
|
|
// Enable the tip controller
|
|
if (m_pMotionController)
|
|
m_pMotionController->Enable(true);
|
|
|
|
// Return to life
|
|
SetState(NPC_STATE_IDLE);
|
|
m_lifeState = LIFE_ALIVE;
|
|
SetCollisionGroup(COLLISION_GROUP_NONE);
|
|
|
|
// Become active again
|
|
Enable();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: The turret is not doing anything at all
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::DisabledThink(void)
|
|
{
|
|
SetNextThink(gpGlobals->curtime + 0.5);
|
|
if (OnSide())
|
|
{
|
|
m_OnTipped.FireOutput(this, this);
|
|
SetEyeState(TURRET_EYE_DEAD);
|
|
SetCollisionGroup(COLLISION_GROUP_DEBRIS);
|
|
SetThink(NULL);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: The turret doesn't run base AI properly, which is a bad decision.
|
|
// As a result, it has to manually find enemies.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::HackFindEnemy(void)
|
|
{
|
|
// We have to refresh our memories before finding enemies, so
|
|
// dead enemies are cleared out before new ones are added.
|
|
GetEnemies()->RefreshMemories();
|
|
|
|
GetSenses()->Look(FLOOR_TURRET_RANGE);
|
|
SetEnemy(BestEnemy());
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Determines whether the turret is upright enough to function
|
|
// Output : Returns true if the turret is tipped over
|
|
//-----------------------------------------------------------------------------
|
|
inline bool CNPC_FloorTurret::OnSide(void)
|
|
{
|
|
Vector up;
|
|
GetVectors(NULL, NULL, &up);
|
|
|
|
return (DotProduct(up, Vector(0, 0, 1)) < 0.5f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allows a generic think function before the others are called
|
|
// Input : state - which state the turret is currently in
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_FloorTurret::PreThink(turretState_e state)
|
|
{
|
|
// Hack to disable turrets when ai is disabled
|
|
if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
|
|
{
|
|
// Push our think out into the future
|
|
SetNextThink(gpGlobals->curtime + 0.1f);
|
|
return true;
|
|
}
|
|
|
|
CheckPVSCondition();
|
|
|
|
//Animate
|
|
StudioFrameAdvance();
|
|
|
|
// We're gonna blow up, so don't interrupt us
|
|
if (state == TURRET_SELF_DESTRUCTING)
|
|
return false;
|
|
|
|
//See if we've tipped, but only do this if we're not being carried
|
|
if (!IsBeingCarriedByPlayer())
|
|
{
|
|
if (OnSide() == false)
|
|
{
|
|
// If I still haven't fallen over after an NPC has tried to knock me down, let them know
|
|
if (m_hLastNPCToKickMe && m_flKnockOverFailedTime < gpGlobals->curtime)
|
|
{
|
|
m_hLastNPCToKickMe->DispatchInteraction(g_interactionTurretStillStanding, NULL, this);
|
|
m_hLastNPCToKickMe = NULL;
|
|
}
|
|
|
|
//Debug visualization
|
|
if (g_debug_turret.GetBool())
|
|
{
|
|
Vector up;
|
|
GetVectors(NULL, NULL, &up);
|
|
|
|
NDebugOverlay::Line(GetAbsOrigin() + (up * 32), GetAbsOrigin() + (up * 128), 0, 255, 0, false, 2.0f);
|
|
NDebugOverlay::Cross3D(GetAbsOrigin() + (up * 32), -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 2.0f);
|
|
NDebugOverlay::Cross3D(GetAbsOrigin() + (up * 128), -Vector(2, 2, 2), Vector(2, 2, 2), 0, 255, 0, false, 2.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (HasSpawnFlags(SF_FLOOR_TURRET_OUT_OF_AMMO) == false)
|
|
{
|
|
//Thrash around for a bit
|
|
m_flThrashTime = gpGlobals->curtime + random->RandomFloat(2.0f, 2.5f);
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
|
|
SetThink(&CNPC_FloorTurret::TippedThink);
|
|
SetEyeState(TURRET_EYE_SEE_TARGET);
|
|
|
|
SpinUp();
|
|
if (!m_bNoAlarmSounds)
|
|
{
|
|
EmitSound("NPC_FloorTurret.Alarm");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Take away the laser
|
|
UTIL_Remove(m_hLaser);
|
|
m_hLaser = NULL;
|
|
|
|
// Become inactive
|
|
SetThink(&CNPC_FloorTurret::InactiveThink);
|
|
SetEyeState(TURRET_EYE_DEAD);
|
|
}
|
|
|
|
//Stop being targetted
|
|
SetState(NPC_STATE_DEAD);
|
|
m_lifeState = LIFE_DEAD;
|
|
|
|
//Disable the tip controller
|
|
if (m_pMotionController)
|
|
m_pMotionController->Enable(false);
|
|
|
|
//Debug visualization
|
|
if (g_debug_turret.GetBool())
|
|
{
|
|
Vector up;
|
|
GetVectors(NULL, NULL, &up);
|
|
|
|
NDebugOverlay::Line(GetAbsOrigin() + (up * 32), GetAbsOrigin() + (up * 128), 255, 0, 0, false, 2.0f);
|
|
NDebugOverlay::Cross3D(GetAbsOrigin() + (up * 32), -Vector(2, 2, 2), Vector(2, 2, 2), 255, 0, 0, false, 2.0f);
|
|
NDebugOverlay::Cross3D(GetAbsOrigin() + (up * 128), -Vector(2, 2, 2), Vector(2, 2, 2), 255, 0, 0, false, 2.0f);
|
|
}
|
|
|
|
//Interrupt current think function
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//Do not interrupt current think function
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the state of the glowing eye attached to the turret
|
|
// Input : state - state the eye should be in
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::SetEyeState(eyeState_t state)
|
|
{
|
|
// Must have a valid eye to affect
|
|
if (!m_hEyeGlow)
|
|
{
|
|
// Create our eye sprite
|
|
m_hEyeGlow = CSprite::SpriteCreate(FLOOR_TURRET_GLOW_SPRITE, GetLocalOrigin(), false);
|
|
if (!m_hEyeGlow)
|
|
return;
|
|
|
|
m_hEyeGlow->SetTransparency(kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation);
|
|
m_hEyeGlow->SetAttachment(this, m_iEyeAttachment);
|
|
}
|
|
|
|
// Add the laser if it doesn't already exist
|
|
if (IsCitizenTurret() && HasSpawnFlags(SF_FLOOR_TURRET_OUT_OF_AMMO) == false && m_hLaser == NULL)
|
|
{
|
|
m_hLaser = CBeam::BeamCreate(LASER_BEAM_SPRITE, 1.0f);
|
|
if (m_hLaser == NULL)
|
|
return;
|
|
|
|
m_hLaser->EntsInit(this, this);
|
|
m_hLaser->FollowEntity(this);
|
|
m_hLaser->SetStartAttachment(LookupAttachment("laser_start"));
|
|
m_hLaser->SetEndAttachment(LookupAttachment("laser_end"));
|
|
m_hLaser->SetNoise(0);
|
|
m_hLaser->SetColor(255, 0, 0);
|
|
m_hLaser->SetScrollRate(0);
|
|
m_hLaser->SetWidth(1.0f);
|
|
m_hLaser->SetEndWidth(1.0f);
|
|
m_hLaser->SetBrightness(160);
|
|
m_hLaser->SetBeamFlags(SF_BEAM_SHADEIN);
|
|
}
|
|
|
|
m_iEyeState = state;
|
|
|
|
//Set the state
|
|
switch (state)
|
|
{
|
|
default:
|
|
case TURRET_EYE_SEE_TARGET: //Fade in and scale up
|
|
m_hEyeGlow->SetColor(255, 0, 0);
|
|
m_hEyeGlow->SetBrightness(164, 0.1f);
|
|
m_hEyeGlow->SetScale(0.4f, 0.1f);
|
|
break;
|
|
|
|
case TURRET_EYE_SEEKING_TARGET: //Ping-pongs
|
|
|
|
//Toggle our state
|
|
m_bBlinkState = !m_bBlinkState;
|
|
m_hEyeGlow->SetColor(255, 128, 0);
|
|
|
|
if (m_bBlinkState)
|
|
{
|
|
//Fade up and scale up
|
|
m_hEyeGlow->SetScale(0.25f, 0.1f);
|
|
m_hEyeGlow->SetBrightness(164, 0.1f);
|
|
}
|
|
else
|
|
{
|
|
//Fade down and scale down
|
|
m_hEyeGlow->SetScale(0.2f, 0.1f);
|
|
m_hEyeGlow->SetBrightness(64, 0.1f);
|
|
}
|
|
|
|
break;
|
|
|
|
case TURRET_EYE_DORMANT: //Fade out and scale down
|
|
m_hEyeGlow->SetColor(0, 255, 0);
|
|
m_hEyeGlow->SetScale(0.1f, 0.5f);
|
|
m_hEyeGlow->SetBrightness(64, 0.5f);
|
|
break;
|
|
|
|
case TURRET_EYE_DEAD: //Fade out slowly
|
|
m_hEyeGlow->SetColor(255, 0, 0);
|
|
m_hEyeGlow->SetScale(0.1f, 3.0f);
|
|
m_hEyeGlow->SetBrightness(0, 3.0f);
|
|
break;
|
|
|
|
case TURRET_EYE_DISABLED:
|
|
m_hEyeGlow->SetColor(0, 255, 0);
|
|
m_hEyeGlow->SetScale(0.1f, 1.0f);
|
|
m_hEyeGlow->SetBrightness(0, 1.0f);
|
|
break;
|
|
|
|
case TURRET_EYE_ALARM:
|
|
{
|
|
//Toggle our state
|
|
m_bBlinkState = !m_bBlinkState;
|
|
m_hEyeGlow->SetColor(255, 0, 0);
|
|
|
|
if (m_bBlinkState)
|
|
{
|
|
//Fade up and scale up
|
|
m_hEyeGlow->SetScale(0.75f, 0.05f);
|
|
m_hEyeGlow->SetBrightness(192, 0.05f);
|
|
}
|
|
else
|
|
{
|
|
//Fade down and scale down
|
|
m_hEyeGlow->SetScale(0.25f, 0.25f);
|
|
m_hEyeGlow->SetBrightness(64, 0.25f);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Make a pinging noise so the player knows where we are
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Ping(void)
|
|
{
|
|
//See if it's time to ping again
|
|
if (m_flPingTime > gpGlobals->curtime)
|
|
return;
|
|
|
|
//Ping!
|
|
EmitSound("NPC_FloorTurret.Ping");
|
|
|
|
SetEyeState(TURRET_EYE_SEEKING_TARGET);
|
|
|
|
m_flPingTime = gpGlobals->curtime + FLOOR_TURRET_PING_TIME;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Toggle the turret's state
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Toggle(void)
|
|
{
|
|
//This turret is on its side, it can't function
|
|
if (OnSide() || (IsAlive() == false))
|
|
return;
|
|
|
|
//Toggle the state
|
|
if (m_bEnabled)
|
|
{
|
|
Disable();
|
|
}
|
|
else
|
|
{
|
|
Enable();
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Enable the turret and deploy
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Enable(void)
|
|
{
|
|
// Don't interrupt blowing up!
|
|
if (m_bSelfDestructing)
|
|
return;
|
|
|
|
// Always allow us to come back to life, even if not right now
|
|
m_bEnabled = true;
|
|
|
|
//This turret is on its side, it can't function
|
|
if (OnSide() || (IsAlive() == false))
|
|
return;
|
|
|
|
// if the turret is flagged as an autoactivate turret, re-enable its ability open self.
|
|
if (m_spawnflags & SF_FLOOR_TURRET_AUTOACTIVATE)
|
|
{
|
|
m_bAutoStart = true;
|
|
}
|
|
|
|
SetThink(&CNPC_FloorTurret::Deploy);
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Retire the turret until enabled again
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::Disable(void)
|
|
{
|
|
//This turret is on its side, it can't function
|
|
if (OnSide() || (IsAlive() == false) || m_bSelfDestructing)
|
|
return;
|
|
|
|
if (m_bEnabled)
|
|
{
|
|
m_bEnabled = false;
|
|
m_bAutoStart = false;
|
|
|
|
SetEnemy(NULL);
|
|
SetThink(&CNPC_FloorTurret::Retire);
|
|
SetNextThink(gpGlobals->curtime + 0.1f);
|
|
}
|
|
else
|
|
SetThink(&CNPC_FloorTurret::DisabledThink);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Toggle the turret's state via input function
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InputToggle(inputdata_t &inputdata)
|
|
{
|
|
Toggle();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InputEnable(inputdata_t &inputdata)
|
|
{
|
|
Enable();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InputDisable(inputdata_t &inputdata)
|
|
{
|
|
Disable();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Stops the turret from firing live rounds (still attempts to though)
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InputDepleteAmmo(inputdata_t &inputdata)
|
|
{
|
|
AddSpawnFlags(SF_FLOOR_TURRET_OUT_OF_AMMO);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allows the turret to fire live rounds again
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InputRestoreAmmo(inputdata_t &inputdata)
|
|
{
|
|
RemoveSpawnFlags(SF_FLOOR_TURRET_OUT_OF_AMMO);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Allow players and npc's to turn the turret on and off
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::ToggleUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
|
|
{
|
|
switch (useType)
|
|
{
|
|
case USE_OFF:
|
|
Disable();
|
|
break;
|
|
case USE_ON:
|
|
Enable();
|
|
break;
|
|
case USE_SET:
|
|
break;
|
|
case USE_TOGGLE:
|
|
Toggle();
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Reduce physics forces from the front
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_FloorTurret::VPhysicsTakeDamage(const CTakeDamageInfo &info)
|
|
{
|
|
bool bShouldIgnoreFromFront = false;
|
|
|
|
// Ignore crossbow bolts hitting us from the front
|
|
bShouldIgnoreFromFront = (info.GetDamageType() & DMG_BULLET) != 0;
|
|
|
|
// Ignore bullets from the front
|
|
if (!bShouldIgnoreFromFront)
|
|
{
|
|
bShouldIgnoreFromFront = FClassnameIs(info.GetInflictor(), "crossbow_bolt");
|
|
}
|
|
|
|
// Did it hit us on the front?
|
|
if (bShouldIgnoreFromFront)
|
|
{
|
|
Vector vecForward;
|
|
GetVectors(&vecForward, NULL, NULL);
|
|
Vector vecForce = info.GetDamageForce();
|
|
VectorNormalize(vecForce);
|
|
float flDot = DotProduct(vecForward, vecForce);
|
|
if (flDot < -0.85)
|
|
return 0;
|
|
}
|
|
|
|
return BaseClass::VPhysicsTakeDamage(info);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &info -
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_FloorTurret::OnTakeDamage(const CTakeDamageInfo &info)
|
|
{
|
|
CTakeDamageInfo newInfo = info;
|
|
|
|
if (info.GetDamageType() & (DMG_SLASH | DMG_CLUB))
|
|
{
|
|
// Take extra force from melee hits
|
|
newInfo.ScaleDamageForce(2.0f);
|
|
|
|
// Disable our upright controller for some time
|
|
if (m_pMotionController != NULL)
|
|
{
|
|
m_pMotionController->Suspend(2.0f);
|
|
}
|
|
}
|
|
else if (info.GetDamageType() & DMG_BLAST)
|
|
{
|
|
newInfo.ScaleDamageForce(2.0f);
|
|
}
|
|
else if ((info.GetDamageType() & DMG_BULLET) && !(info.GetDamageType() & DMG_BUCKSHOT))
|
|
{
|
|
// Bullets, but not buckshot, do extra push
|
|
newInfo.ScaleDamageForce(2.5f);
|
|
}
|
|
|
|
// Manually apply vphysics because AI_BaseNPC takedamage doesn't call back to CBaseEntity OnTakeDamage
|
|
VPhysicsTakeDamage(newInfo);
|
|
|
|
// Bump up our search time
|
|
m_flLastSight = gpGlobals->curtime + FLOOR_TURRET_MAX_WAIT;
|
|
|
|
// Start looking around in anger if we were idle
|
|
if (IsAlive() && m_bEnabled && m_bAutoStart && GetActivity() == ACT_FLOOR_TURRET_CLOSED_IDLE && m_bSelfDestructing == false)
|
|
{
|
|
SetThink(&CNPC_FloorTurret::Deploy);
|
|
}
|
|
|
|
return BaseClass::OnTakeDamage(newInfo);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::SpinUp(void)
|
|
{
|
|
}
|
|
|
|
#define FLOOR_TURRET_MIN_SPIN_DOWN 1.0f
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : const QAngle
|
|
//-----------------------------------------------------------------------------
|
|
QAngle CNPC_FloorTurret::PreferredCarryAngles(void)
|
|
{
|
|
// FIXME: Embed this into the class
|
|
static QAngle g_prefAngles;
|
|
|
|
Vector vecUserForward;
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer();
|
|
pPlayer->EyeVectors(&vecUserForward);
|
|
|
|
// If we're looking up, then face directly forward
|
|
if (vecUserForward.z >= 0.0f)
|
|
return vec3_angle;
|
|
|
|
// Otherwise, stay "upright"
|
|
g_prefAngles.Init();
|
|
g_prefAngles.x = -pPlayer->EyeAngles().x;
|
|
|
|
return g_prefAngles;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::SpinDown(void)
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pVictim -
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_FloorTurret::GetAttackDamageScale(CBaseEntity *pVictim)
|
|
{
|
|
CBaseCombatCharacter *pBCC = pVictim->MyCombatCharacterPointer();
|
|
|
|
// Do extra damage to antlions & combine
|
|
if (pBCC)
|
|
{
|
|
if (pBCC->Classify() == CLASS_ANTLION)
|
|
return 2.0;
|
|
|
|
if (pBCC->Classify() == CLASS_COMBINE)
|
|
return 2.0;
|
|
}
|
|
|
|
return BaseClass::GetAttackDamageScale(pVictim);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
Vector CNPC_FloorTurret::GetAttackSpread(CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget)
|
|
{
|
|
WeaponProficiency_t weaponProficiency = WEAPON_PROFICIENCY_AVERAGE;
|
|
|
|
// Switch our weapon proficiency based upon our target
|
|
if (pTarget)
|
|
{
|
|
if (pTarget->Classify() == CLASS_PLAYER || pTarget->Classify() == CLASS_ANTLION || pTarget->Classify() == CLASS_ZOMBIE)
|
|
{
|
|
// Make me much more accurate
|
|
weaponProficiency = WEAPON_PROFICIENCY_PERFECT;
|
|
}
|
|
else if (pTarget->Classify() == CLASS_COMBINE)
|
|
{
|
|
// Make me more accurate
|
|
weaponProficiency = WEAPON_PROFICIENCY_VERY_GOOD;
|
|
}
|
|
}
|
|
|
|
return VECTOR_CONE_10DEGREES * ((CBaseHLCombatWeapon::GetDefaultProficiencyValues())[weaponProficiency].spreadscale);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Do we have a physics attacker?
|
|
//------------------------------------------------------------------------------
|
|
CBasePlayer *CNPC_FloorTurret::HasPhysicsAttacker(float dt)
|
|
{
|
|
// If the player is holding me now, or I've been recently thrown
|
|
// then return a pointer to that player
|
|
if (IsHeldByPhyscannon() || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime))
|
|
{
|
|
return m_hPhysicsAttacker;
|
|
}
|
|
return NULL;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw any debug text overlays
|
|
// Output : Current text offset from the top
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_FloorTurret::DrawDebugTextOverlays(void)
|
|
{
|
|
int text_offset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
if (VPhysicsGetObject())
|
|
{
|
|
char tempstr[512];
|
|
Q_snprintf(tempstr, sizeof(tempstr), "Mass: %.2f kg / %.2f lb (%s)", VPhysicsGetObject()->GetMass(), kg2lbs(VPhysicsGetObject()->GetMass()), GetMassEquivalent(VPhysicsGetObject()->GetMass()));
|
|
EntityText(text_offset, tempstr, 0);
|
|
text_offset++;
|
|
}
|
|
}
|
|
|
|
return text_offset;
|
|
}
|
|
|
|
void CNPC_FloorTurret::UpdateMuzzleMatrix()
|
|
{
|
|
if (gpGlobals->tickcount != m_muzzleToWorldTick)
|
|
{
|
|
m_muzzleToWorldTick = gpGlobals->tickcount;
|
|
GetAttachment(m_iMuzzleAttachment, m_muzzleToWorld);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: We override this code because otherwise we start to move into the
|
|
// tricky realm of player avoidance. Since we don't go through the
|
|
// normal NPC thinking but we ARE an NPC (...) we miss a bunch of
|
|
// book keeping. This means we can become invisible and then never
|
|
// reappear.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::PlayerPenetratingVPhysics(void)
|
|
{
|
|
// We don't care!
|
|
}
|
|
|
|
#define SELF_DESTRUCT_DURATION 4.0f
|
|
#define SELF_DESTRUCT_BEEP_MIN_DELAY 0.1f
|
|
#define SELF_DESTRUCT_BEEP_MAX_DELAY 0.75f
|
|
#define SELF_DESTRUCT_BEEP_MIN_PITCH 100.0f
|
|
#define SELF_DESTRUCT_BEEP_MAX_PITCH 225.0f
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::BreakThink(void)
|
|
{
|
|
Vector vecUp;
|
|
GetVectors(NULL, NULL, &vecUp);
|
|
Vector vecOrigin = WorldSpaceCenter() + (vecUp * 12.0f);
|
|
|
|
// Our effect
|
|
#ifdef HL2_EPISODIC
|
|
DispatchParticleEffect("explosion_turret_break", vecOrigin, GetAbsAngles());
|
|
#endif // HL2_EPISODIC
|
|
|
|
// K-boom
|
|
RadiusDamage(CTakeDamageInfo(this, this, 15.0f, DMG_BLAST), vecOrigin, (10 * 12), CLASS_NONE, this);
|
|
|
|
EmitSound("NPC_FloorTurret.Destruct");
|
|
|
|
breakablepropparams_t params(GetAbsOrigin(), GetAbsAngles(), vec3_origin, RandomAngularImpulse(-800.0f, 800.0f));
|
|
params.impactEnergyScale = 1.0f;
|
|
params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
|
|
|
|
// no damage/damage force? set a burst of 100 for some movement
|
|
params.defBurstScale = 100;
|
|
PropBreakableCreateAll(GetModelIndex(), VPhysicsGetObject(), params, this, -1, true);
|
|
|
|
// Throw out some small chunks too obscure the explosion even more
|
|
CPVSFilter filter(vecOrigin);
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
Vector gibVelocity = RandomVector(-100, 100);
|
|
int iModelIndex = modelinfo->GetModelIndex(g_PropDataSystem.GetRandomChunkModel("MetalChunks"));
|
|
te->BreakModel(filter, 0.0, vecOrigin, GetAbsAngles(), Vector(40, 40, 40), gibVelocity, iModelIndex, 150, 4, 2.5, BREAK_METAL);
|
|
}
|
|
|
|
// We're done!
|
|
UTIL_Remove(this);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: The countdown to destruction!
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::SelfDestructThink(void)
|
|
{
|
|
// Continue to animate
|
|
PreThink(TURRET_SELF_DESTRUCTING);
|
|
|
|
// If we're done, explode
|
|
if ((gpGlobals->curtime - m_flDestructStartTime) >= SELF_DESTRUCT_DURATION)
|
|
{
|
|
SetThink(&CNPC_FloorTurret::BreakThink);
|
|
SetNextThink(gpGlobals->curtime + 0.1f);
|
|
UTIL_Remove(m_hFizzleEffect);
|
|
m_hFizzleEffect = NULL;
|
|
return;
|
|
}
|
|
|
|
// Find out where we are in the cycle of our destruction
|
|
float flDestructPerc = clamp((gpGlobals->curtime - m_flDestructStartTime) / SELF_DESTRUCT_DURATION, 0.0f, 1.0f);
|
|
|
|
// Figure out when our next beep should occur
|
|
float flBeepTime = SELF_DESTRUCT_BEEP_MAX_DELAY + ((SELF_DESTRUCT_BEEP_MIN_DELAY - SELF_DESTRUCT_BEEP_MAX_DELAY) * flDestructPerc);
|
|
|
|
// If it's time to beep again, do so
|
|
if (gpGlobals->curtime > (m_flPingTime + flBeepTime))
|
|
{
|
|
// Figure out what our beep pitch will be
|
|
float flBeepPitch = SELF_DESTRUCT_BEEP_MIN_PITCH + ((SELF_DESTRUCT_BEEP_MAX_PITCH - SELF_DESTRUCT_BEEP_MIN_PITCH) * flDestructPerc);
|
|
|
|
StopSound("NPC_FloorTurret.AlarmPing");
|
|
|
|
// Play the beep
|
|
CPASAttenuationFilter filter(this, "NPC_FloorTurret.AlarmPing");
|
|
EmitSound_t params;
|
|
params.m_pSoundName = "NPC_FloorTurret.AlarmPing";
|
|
params.m_nPitch = floor(flBeepPitch);
|
|
params.m_nFlags = SND_CHANGE_PITCH;
|
|
EmitSound(filter, entindex(), params);
|
|
|
|
// Flash our eye
|
|
SetEyeState(TURRET_EYE_ALARM);
|
|
|
|
// Save this as the last time we pinged
|
|
m_flPingTime = gpGlobals->curtime;
|
|
|
|
// Randomly twitch
|
|
m_vecGoalAngles.x = GetAbsAngles().x + random->RandomFloat(-60 * flDestructPerc, 60 * flDestructPerc);
|
|
m_vecGoalAngles.y = GetAbsAngles().y + random->RandomFloat(-60 * flDestructPerc, 60 * flDestructPerc);
|
|
}
|
|
|
|
UpdateFacing();
|
|
|
|
// Think again!
|
|
SetNextThink(gpGlobals->curtime + 0.05f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Make us explode
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_FloorTurret::InputSelfDestruct(inputdata_t &inputdata)
|
|
{
|
|
// Ka-boom!
|
|
m_flDestructStartTime = gpGlobals->curtime;
|
|
m_flPingTime = gpGlobals->curtime;
|
|
m_bSelfDestructing = true;
|
|
|
|
SetThink(&CNPC_FloorTurret::SelfDestructThink);
|
|
SetNextThink(gpGlobals->curtime + 0.1f);
|
|
|
|
// Create the dust effect in place
|
|
m_hFizzleEffect = (CParticleSystem *)CreateEntityByName("info_particle_system");
|
|
if (m_hFizzleEffect != NULL)
|
|
{
|
|
Vector vecUp;
|
|
GetVectors(NULL, NULL, &vecUp);
|
|
|
|
// Setup our basic parameters
|
|
m_hFizzleEffect->KeyValue("start_active", "1");
|
|
m_hFizzleEffect->KeyValue("effect_name", "explosion_turret_fizzle");
|
|
m_hFizzleEffect->SetParent(this);
|
|
m_hFizzleEffect->SetAbsOrigin(WorldSpaceCenter() + (vecUp * 12.0f));
|
|
DispatchSpawn(m_hFizzleEffect);
|
|
m_hFizzleEffect->Activate();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Tip controller
|
|
//
|
|
|
|
LINK_ENTITY_TO_CLASS(floorturret_tipcontroller, CTurretTipController);
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// Save/Restore
|
|
//---------------------------------------------------------
|
|
BEGIN_DATADESC(CTurretTipController)
|
|
|
|
DEFINE_FIELD(m_bEnabled, FIELD_BOOLEAN),
|
|
DEFINE_FIELD(m_flSuspendTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_worldGoalAxis, FIELD_VECTOR),
|
|
DEFINE_FIELD(m_localTestAxis, FIELD_VECTOR),
|
|
DEFINE_PHYSPTR(m_pController),
|
|
DEFINE_FIELD(m_angularLimit, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_pParentTurret, FIELD_CLASSPTR),
|
|
|
|
END_DATADESC()
|
|
|
|
|
|
|
|
CTurretTipController::~CTurretTipController()
|
|
{
|
|
if (m_pController)
|
|
{
|
|
physenv->DestroyMotionController(m_pController);
|
|
m_pController = NULL;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTurretTipController::Spawn(void)
|
|
{
|
|
m_bEnabled = true;
|
|
|
|
// align the object's local Z axis
|
|
m_localTestAxis.Init(0, 0, 1);
|
|
// with the world's Z axis
|
|
m_worldGoalAxis.Init(0, 0, 1);
|
|
|
|
// recover from up to 25 degrees / sec angular velocity
|
|
m_angularLimit = 25;
|
|
m_flSuspendTime = 0;
|
|
|
|
SetMoveType(MOVETYPE_NONE);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTurretTipController::Activate(void)
|
|
{
|
|
BaseClass::Activate();
|
|
|
|
if (m_pParentTurret == NULL)
|
|
{
|
|
UTIL_Remove(this);
|
|
return;
|
|
}
|
|
|
|
IPhysicsObject *pPhys = m_pParentTurret->VPhysicsGetObject();
|
|
|
|
if (pPhys == NULL)
|
|
{
|
|
UTIL_Remove(this);
|
|
return;
|
|
}
|
|
|
|
//Setup the motion controller
|
|
if (!m_pController)
|
|
{
|
|
m_pController = physenv->CreateMotionController((IMotionEvent *)this);
|
|
m_pController->AttachObject(pPhys, true);
|
|
}
|
|
else
|
|
{
|
|
m_pController->SetEventHandler(this);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Actual simulation for tip controller
|
|
//-----------------------------------------------------------------------------
|
|
IMotionEvent::simresult_e CTurretTipController::Simulate(IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular)
|
|
{
|
|
if (Enabled() == false)
|
|
return SIM_NOTHING;
|
|
|
|
// Don't simulate if we're being carried by the player
|
|
if (m_pParentTurret->IsBeingCarriedByPlayer())
|
|
return SIM_NOTHING;
|
|
|
|
float flAngularLimit = m_angularLimit;
|
|
|
|
// If we were just dropped by a friendly player, stabilise better
|
|
if (m_pParentTurret->WasJustDroppedByPlayer())
|
|
{
|
|
// Increase the controller strength a little
|
|
flAngularLimit += 20;
|
|
}
|
|
else
|
|
{
|
|
// If the turret has some vertical velocity, don't simulate
|
|
Vector vecVelocity;
|
|
AngularImpulse angImpulse;
|
|
pObject->GetVelocity(&vecVelocity, &angImpulse);
|
|
if ((vecVelocity.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerVelocity) || (angImpulse.LengthSqr() > CNPC_FloorTurret::fMaxTipControllerAngularVelocity))
|
|
return SIM_NOTHING;
|
|
}
|
|
|
|
linear.Init();
|
|
|
|
AngularImpulse angVel;
|
|
pObject->GetVelocity(NULL, &angVel);
|
|
|
|
matrix3x4_t matrix;
|
|
// get the object's local to world transform
|
|
pObject->GetPositionMatrix(&matrix);
|
|
|
|
// Get the alignment axis in object space
|
|
Vector currentLocalTargetAxis;
|
|
VectorIRotate(m_worldGoalAxis, matrix, currentLocalTargetAxis);
|
|
|
|
float invDeltaTime = (1 / deltaTime);
|
|
angular = ComputeRotSpeedToAlignAxes(m_localTestAxis, currentLocalTargetAxis, angVel, 1.0, invDeltaTime * invDeltaTime, flAngularLimit * invDeltaTime);
|
|
|
|
return SIM_LOCAL_ACCELERATION;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CTurretTipController::Enable(bool state)
|
|
{
|
|
m_bEnabled = state;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : time -
|
|
//-----------------------------------------------------------------------------
|
|
void CTurretTipController::Suspend(float time)
|
|
{
|
|
m_flSuspendTime = gpGlobals->curtime + time;
|
|
}
|
|
|
|
|
|
float CTurretTipController::SuspendedTill(void)
|
|
{
|
|
return m_flSuspendTime;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CTurretTipController::Enabled(void)
|
|
{
|
|
if (m_flSuspendTime > gpGlobals->curtime)
|
|
return false;
|
|
|
|
return m_bEnabled;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Schedules
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
AI_BEGIN_CUSTOM_NPC(npc_turret_floor, CNPC_FloorTurret)
|
|
|
|
DECLARE_INTERACTION(g_interactionTurretStillStanding);
|
|
|
|
AI_END_CUSTOM_NPC()
|