Files
HL2Overcharged/game/server/overcharged/npc_mortarsynth.cpp
2025-05-21 21:20:08 +03:00

3501 lines
99 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Combine Alien Synth enemy
// 9 sept 2021 BJ
//=============================================================================//
#include "cbase.h"
#include "npcevent.h"
#include "soundenvelope.h"
#include "ai_hint.h"
#include "ai_moveprobe.h"
#include "ai_squad.h"
#include "beam_shared.h"
#include "globalstate.h"
#include "soundent.h"
#include "npc_citizen17.h"
#include "gib.h"
#include "spotlightend.h"
#include "IEffects.h"
#include "items.h"
#include "ai_route.h"
#include "player_pickup.h"
#include "weapon_physcannon.h"
#include "hl2_player.h"
#include "npc_mortarsynth.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Singleton interfaces
//-----------------------------------------------------------------------------
extern IMaterialSystemHardwareConfig *g_pMaterialSystemHardwareConfig;
//-----------------------------------------------------------------------------
// Parameters for how the scanner relates to citizens.
//-----------------------------------------------------------------------------
#define MSYNTH_CIT_INSPECT_DELAY 10 // Check for citizens this often
#define MSYNTH_CIT_INSPECT_GROUND_DIST 500 // How far to look for citizens to inspect
#define MSYNTH_CIT_INSPECT_FLY_DIST 1500 // How far to look for citizens to inspect
#define MSYNTH_CIT_INSPECT_LENGTH 5 // How long does the inspection last
#define MSYNTH_HINT_INSPECT_LENGTH 5 // How long does the inspection last
#define MSYNTH_SOUND_INSPECT_LENGTH 5 // How long does the inspection last
#define MSYNTH_HINT_INSPECT_DELAY 15 // Check for hint nodes this often
#define SPOTLIGHT_WIDTH 32
#define MSYNTH_SPOTLIGHT_NEAR_DIST 256
#define MSYNTH_SPOTLIGHT_FAR_DIST 356
#define MSYNTH_SPOTLIGHT_FLY_HEIGHT 72
#define MSYNTH_NOSPOTLIGHT_FLY_HEIGHT 72
#define MSYNTH_FLASH_MIN_DIST 900 // How far does flash effect enemy
#define MSYNTH_FLASH_MAX_DIST 1200 // How far does flash effect enemy
#define MSYNTH_FLASH_MAX_VALUE 240 // How bright is maximum flash
#define MSYNTH_PHOTO_NEAR_DIST 256
#define MSYNTH_PHOTO_FAR_DIST 356
#define MSYNTH_FOLLOW_DIST 356
#define MSYNTH_NUM_GIBS 6 // Number of gibs in gib file
// Strider Scout Scanners
#define MSYNTH_SCOUT_MAX_SPEED 200
ConVar sk_mortarsynth_beam_damage("sk_mortarsynth_beam_damage", "0");
ConVar sk_mortarsynth_projectile_damage("sk_mortarsynth_projectile_damage", "0");
ConVar sk_mortarsynth_projectile_damage_calc_difficulty("sk_mortarsynth_projectile_damage_calc_difficulty", "0");
ConVar sk_mortarsynth_health("sk_mortarsynth_health", "0");
ConVar g_debug_mortarsynth("g_debug_mortarsynth", "0");
//added
ConVar sk_mortarsynth_explode_damage("sk_mortarsynth_explode_damage", "0");
//-----------------------------------------------------------------------------
// Private activities.
//-----------------------------------------------------------------------------
static int ACT_SCANNER_SMALL_FLINCH_ALERT = 0;
static int ACT_SCANNER_SMALL_FLINCH_COMBAT = 0;
static int ACT_SCANNER_INSPECT = 0;
static int ACT_SCANNER_WALK_ALERT = 0;
static int ACT_SCANNER_WALK_COMBAT = 0;
static int ACT_SCANNER_FLARE = 0;
static int ACT_SCANNER_RETRACT = 0;
static int ACT_SCANNER_FLARE_PRONGS = 0;
static int ACT_SCANNER_RETRACT_PRONGS = 0;
static int ACT_SCANNER_FLARE_START = 0;
//-----------------------------------------------------------------------------
// Interactions
//-----------------------------------------------------------------------------
int g_interactionScannerInspect = 0;
int g_interactionScannerInspectBegin = 0;
int g_interactionScannerInspectHandsUp = 0;
int g_interactionScannerInspectShowArmband = 0;//<<TEMP>>still to be completed
int g_interactionScannerInspectDone = 0;
int g_interactionScannerSupportEntity = 0;
int g_interactionScannerSupportPosition = 0;
//-----------------------------------------------------------------------------
// Animation events
//------------------------------------------------------------------------
int AE_MSYNTH_CLOSED;
//-----------------------------------------------------------------------------
// Attachment points
//-----------------------------------------------------------------------------
#define MSYNTH_ATTACHMENT_LIGHT "light"
#define MSYNTH_ATTACHMENT_FLASH 1
#define MSYNTH_ATTACHMENT_LPRONG 2
#define MSYNTH_ATTACHMENT_RPRONG 3
//-----------------------------------------------------------------------------
// Other defines.
//-----------------------------------------------------------------------------
#define SCANNER_MAX_BEAMS 4
BEGIN_DATADESC(CNPC_MSynth)
DEFINE_SOUNDPATCH(m_pEngineSound),
DEFINE_KEYFIELD(m_BeamArmed, FIELD_BOOLEAN, "HasBeamWeapon"),
DEFINE_FIELD(m_pEnergyBeam, FIELD_CLASSPTR),
DEFINE_ARRAY(m_pEnergyGlow, FIELD_CLASSPTR, 2),
DEFINE_EMBEDDED(m_KilledInfo),
DEFINE_FIELD(m_bGibbing, FIELD_INTEGER),
DEFINE_FIELD(m_flGoalOverrideDistance, FIELD_FLOAT),
DEFINE_FIELD(m_bPhotoTaken, FIELD_BOOLEAN),
DEFINE_FIELD(m_vInspectPos, FIELD_VECTOR),
DEFINE_FIELD(m_fInspectEndTime, FIELD_TIME),
DEFINE_FIELD(m_fCheckCitizenTime, FIELD_TIME),
DEFINE_FIELD(m_fCheckHintTime, FIELD_TIME),
DEFINE_KEYFIELD(m_bShouldInspect, FIELD_BOOLEAN, "ShouldInspect"),
DEFINE_KEYFIELD(m_bOnlyInspectPlayers, FIELD_BOOLEAN, "OnlyInspectPlayers"),
DEFINE_KEYFIELD(m_bNeverInspectPlayers, FIELD_BOOLEAN, "NeverInspectPlayers"),
DEFINE_FIELD(m_fNextPhotographTime, FIELD_TIME),
// DEFINE_FIELD( m_pEyeFlash, FIELD_CLASSPTR ),
DEFINE_FIELD(m_vSpotlightTargetPos, FIELD_POSITION_VECTOR),
DEFINE_FIELD(m_vSpotlightCurrentPos, FIELD_POSITION_VECTOR),
// don't save (recreated after restore/transition)
// DEFINE_FIELD( m_hSpotlight, FIELD_EHANDLE ),
// DEFINE_FIELD( m_hSpotlightTarget, FIELD_EHANDLE ),
DEFINE_FIELD(m_vSpotlightDir, FIELD_VECTOR),
DEFINE_FIELD(m_vSpotlightAngVelocity, FIELD_VECTOR),
DEFINE_FIELD(m_flSpotlightCurLength, FIELD_FLOAT),
DEFINE_FIELD(m_fNextSpotlightTime, FIELD_TIME),
DEFINE_FIELD(m_nHaloSprite, FIELD_INTEGER),
DEFINE_FIELD(m_fNextFlySoundTime, FIELD_TIME),
DEFINE_FIELD(m_nFlyMode, FIELD_INTEGER),
DEFINE_FIELD(m_nPoseTail, FIELD_INTEGER),
DEFINE_FIELD(m_nPoseDynamo, FIELD_INTEGER),
DEFINE_FIELD(m_nPoseFlare, FIELD_INTEGER),
DEFINE_FIELD(m_nPoseFaceVert, FIELD_INTEGER),
DEFINE_FIELD(m_nPoseFaceHoriz, FIELD_INTEGER),
DEFINE_FIELD(m_bIsClawScanner, FIELD_BOOLEAN),
DEFINE_FIELD(m_bIsOpen, FIELD_BOOLEAN),
// DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ),
DEFINE_FIELD(m_pSmokeTrail, FIELD_CLASSPTR),
DEFINE_FIELD(m_flFlyNoiseBase, FIELD_FLOAT),
DEFINE_FIELD(m_flEngineStallTime, FIELD_TIME),
DEFINE_FIELD(m_vecDiveBombDirection, FIELD_VECTOR),
DEFINE_FIELD(m_flDiveBombRollForce, FIELD_FLOAT),
DEFINE_KEYFIELD(m_flSpotlightMaxLength, FIELD_FLOAT, "SpotlightLength"),
DEFINE_KEYFIELD(m_flSpotlightGoalWidth, FIELD_FLOAT, "SpotlightWidth"),
// Physics Influence
DEFINE_FIELD(m_hPhysicsAttacker, FIELD_EHANDLE),
DEFINE_FIELD(m_flLastPhysicsInfluenceTime, FIELD_TIME),
DEFINE_KEYFIELD(m_bNoLight, FIELD_BOOLEAN, "SpotlightDisabled"),
DEFINE_INPUTFUNC(FIELD_VOID, "DisableSpotlight", InputDisableSpotlight),
DEFINE_INPUTFUNC(FIELD_STRING, "InspectTargetPhoto", InputInspectTargetPhoto),
DEFINE_INPUTFUNC(FIELD_STRING, "InspectTargetSpotlight", InputInspectTargetSpotlight),
DEFINE_INPUTFUNC(FIELD_INTEGER, "InputShouldInspect", InputShouldInspect),
DEFINE_INPUTFUNC(FIELD_STRING, "SetFollowTarget", InputSetFollowTarget),
DEFINE_INPUTFUNC(FIELD_VOID, "ClearFollowTarget", InputClearFollowTarget),
DEFINE_INPUTFUNC(FIELD_STRING, "DeployMine", InputDeployMine),
DEFINE_INPUTFUNC(FIELD_STRING, "EquipMine", InputEquipMine),
DEFINE_OUTPUT(m_OnPhotographPlayer, "OnPhotographPlayer"),
DEFINE_OUTPUT(m_OnPhotographNPC, "OnPhotographNPC"),
END_DATADESC()
LINK_ENTITY_TO_CLASS(npc_mortarsynth, CNPC_MSynth);
#ifndef CLIENT_DLL
void TE_StickyBolt(IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin);
//-----------------------------------------------------------------------------
// ShockRifle
//-----------------------------------------------------------------------------
class CMSynthProjectile : public CBaseCombatCharacter
{
DECLARE_CLASS(CMSynthProjectile, CBaseCombatCharacter);
public:
CMSynthProjectile() { };
~CMSynthProjectile();
Class_T Classify(void) { return CLASS_COMBINE; }
public:
void Spawn(void);
void Precache(void);
void BubbleThink(void); // changed
void BoltTouch(CBaseEntity *pOther);
bool CreateVPhysics(void);
unsigned int PhysicsSolidMaskForEntity() const;
static CMSynthProjectile *BoltCreate(const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CNPC_MSynth *pentOwner = NULL);
protected:
bool CreateSprites(void);
CHandle<CSprite> m_pGlowSprite;
int m_iDamage;
private:
CNPC_MSynth *pParent = NULL;
const char *Particle1;
const char *Particle2;
DECLARE_DATADESC();
//DECLARE_SERVERCLASS();
};
LINK_ENTITY_TO_CLASS(msynth_projectile, CMSynthProjectile);
BEGIN_DATADESC(CMSynthProjectile)
DEFINE_FUNCTION(BubbleThink),
DEFINE_FUNCTION(BoltTouch),
DEFINE_FIELD(m_pGlowSprite, FIELD_EHANDLE),
END_DATADESC()
//IMPLEMENT_SERVERCLASS_ST(CMSynthProjectile, DT_MSynthProjectile)
//END_SEND_TABLE()
#define BOLT_MODEL_NPC cvar->FindVar("oc_weapon_ShockRifle_model")->GetString() //"models/ShockRifle.mdl"
#define BOLT_SKIN_GLOW_NPC 1
CMSynthProjectile *CMSynthProjectile::BoltCreate(const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CNPC_MSynth *pentOwner)
{
CMSynthProjectile *pProjectile = (CMSynthProjectile *)CreateEntityByName("msynth_projectile");
UTIL_SetOrigin(pProjectile, vecOrigin);
pProjectile->SetAbsAngles(angAngles);
pProjectile->Spawn();
pProjectile->SetOwnerEntity(pentOwner);
pProjectile->pParent = pentOwner;
pProjectile->Particle1 = pentOwner->MSYNTH_PARTICLE_TRAIL;
pProjectile->Particle1 = pentOwner->MSYNTH_PARTICLE_GLOW;
DispatchParticleEffect(pentOwner->MSYNTH_PARTICLE_TRAIL, PATTACH_ABSORIGIN_FOLLOW, pProjectile);
DispatchParticleEffect(pentOwner->MSYNTH_PARTICLE_GLOW, PATTACH_ABSORIGIN_FOLLOW, pProjectile);
pProjectile->m_iDamage = iDamage;
return pProjectile;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CMSynthProjectile::~CMSynthProjectile(void)
{
if (m_pGlowSprite)
{
UTIL_Remove(m_pGlowSprite);
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CMSynthProjectile::CreateVPhysics(void)
{
VPhysicsInitNormal(SOLID_BBOX, FSOLID_NOT_STANDABLE, false);
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
unsigned int CMSynthProjectile::PhysicsSolidMaskForEntity() const
{
return (BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX) & ~CONTENTS_GRATE;
}
bool CMSynthProjectile::CreateSprites(void)
{
//DevMsg("%s \n", pParent->MSYNTH_PARTICLE_TRAIL);
//DevMsg("%s \n", pParent->MSYNTH_PARTICLE_GLOW);
//if (pParent)
//{
//DispatchParticleEffect(Particle1, PATTACH_ABSORIGIN_FOLLOW, this);
//DispatchParticleEffect(Particle2, PATTACH_ABSORIGIN_FOLLOW, this);
//}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMSynthProjectile::Spawn(void)
{
Precache();
SetModel(BOLT_MODEL_NPC);
SetMoveType(MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM);
UTIL_SetSize(this, -Vector(10, 10, 10), Vector(10, 10, 10));
SetSolid(SOLID_BBOX);
SetModelScale(0.3f, 0.0f);
CollisionProp()->SetSurroundingBoundsType(USE_HITBOXES);
UpdateWaterState();
SetTouch(&CMSynthProjectile::BoltTouch);
SetThink(&CMSynthProjectile::BubbleThink);
SetNextThink(gpGlobals->curtime + 0.02f);
AddEFlags(EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION);
CreateSprites();
m_nSkin = BOLT_SKIN_GLOW_NPC;
}
void CMSynthProjectile::Precache(void)
{
PrecacheModel(BOLT_MODEL_NPC);
/*PrecacheParticleSystem(GetOwnerEntity()->MSYNTH_PARTICLE_TRAIL);
PrecacheParticleSystem(MSYNTH_PARTICLE_GLOW);
PrecacheParticleSystem(MSYNTH_PARTICLE_EXPLOSION);*/
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CMSynthProjectile::BoltTouch(CBaseEntity *pOther)
{
if (!pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS))
return;
trace_t tr2;
tr2 = BaseClass::GetTouchTrace();
RadiusDamage(CTakeDamageInfo(this, NULL, cvar->FindVar("sk_mortarsynth_projectile_damage")->GetFloat(), DMG_BLAST), GetAbsOrigin(), 130, CLASS_NONE, NULL);
if (pParent)
{
//DevMsg("%s \n", pParent->MSYNTH_PARTICLE_EXPLOSION);
DispatchParticleEffect(pParent->MSYNTH_PARTICLE_EXPLOSION, tr2.endpos, GetAbsAngles());
}
if (pOther->m_takedamage != DAMAGE_NO)
{
trace_t tr;
tr = BaseClass::GetTouchTrace();
Vector vecNormalizedVel = GetAbsVelocity();
ClearMultiDamage();
VectorNormalize(vecNormalizedVel);
if (GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC())
{
//CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_NEVERGIB );
CTakeDamageInfo dmgInfo(this, GetOwnerEntity(), cvar->FindVar("sk_mortarsynth_projectile_damage_calc_difficulty")->GetFloat(), DMG_SHOCK);
dmgInfo.AdjustPlayerDamageInflictedForSkillLevel();
CalculateMeleeDamageForce(&dmgInfo, vecNormalizedVel, tr.endpos, 0.7f);
dmgInfo.SetDamagePosition(tr.endpos);
pOther->DispatchTraceAttack(dmgInfo, vecNormalizedVel, &tr);
}
else
{
//CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_BULLET | DMG_NEVERGIB );
CTakeDamageInfo dmgInfo(this, GetOwnerEntity(), cvar->FindVar("sk_mortarsynth_projectile_damage_calc_difficulty")->GetFloat(), DMG_NEVERGIB | DMG_SHOCK);
CalculateMeleeDamageForce(&dmgInfo, vecNormalizedVel, tr.endpos, 0.7f);
dmgInfo.SetDamagePosition(tr.endpos);
pOther->DispatchTraceAttack(dmgInfo, vecNormalizedVel, &tr);
}
ApplyMultiDamage();
//Adrian: keep going through the glass.
if (pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS)
return;
SetAbsVelocity(Vector(0, 0, 0));
EmitSound(MSYNTH_SHOCK_IMPACT_SOUND);
CEffectData data;
data.m_vOrigin = tr.endpos + (tr.plane.normal * 1.0f);
data.m_vNormal = tr.plane.normal;
g_pEffects->Sparks(data.m_vOrigin);
SetTouch(NULL);
SetThink(NULL);
UTIL_Remove(this);
}
else
{
trace_t tr;
Vector vForward;
AngleVectors(GetAbsAngles(), &vForward);
VectorNormalize(vForward);
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_OPAQUE, pOther, COLLISION_GROUP_NONE, &tr);
EmitSound(MSYNTH_SHOCK_IMPACT_SOUND);
CEffectData data;
data.m_vOrigin = tr.endpos + (tr.plane.normal * 1.0f);
data.m_vNormal = tr.plane.normal;
g_pEffects->Sparks(data.m_vOrigin);
UTIL_DecalTrace(&tr, "SmallScorch"); //"Scorch" );
UTIL_Remove(this);
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMSynthProjectile::BubbleThink(void)
{
QAngle angNewAngles;
VectorAngles(GetAbsVelocity(), angNewAngles);
SetAbsAngles(angNewAngles);
SetNextThink(gpGlobals->curtime + 0.02f);
if (GetWaterLevel() == 0)
return;
//DispatchParticleEffect("Shockrifle_sparks", PATTACH_ABSORIGIN, NULL);
UTIL_Remove(this); // BriJee OVR : Little trick to get entity killed in water
//UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 );
}
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNPC_MSynth::CNPC_MSynth()
{
#ifdef _DEBUG
m_vInspectPos.Init();
m_vSpotlightTargetPos.Init();
m_vSpotlightCurrentPos.Init();
m_vSpotlightDir.Init();
m_vSpotlightAngVelocity.Init();
#endif
m_bShouldInspect = true;
m_bOnlyInspectPlayers = false;
m_bNeverInspectPlayers = false;
m_flAttackNearDist = MSYNTH_ATTACK_NEAR_DIST;
m_flAttackFarDist = MSYNTH_ATTACK_FAR_DIST;
m_flAttackRange = MSYNTH_ATTACK_RANGE;
//m_bGibbing = 0;
char szMapName[256];
Q_strncpy(szMapName, STRING(gpGlobals->mapname), sizeof(szMapName));
Q_strlower(szMapName);
/*if (!Q_strnicmp(szMapName, "d3_c17", 6))
{
// Streetwar scanners are claw scanners
m_bIsClawScanner = true;
}
else*/
//{
m_bIsClawScanner = false;
//}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::Spawn(void)
{
// Check for user error
if (m_flSpotlightMaxLength <= 0)
{
DevMsg("CNPC_MSynth::Spawn: Invalid spotlight length <= 0, setting to 500\n");
m_flSpotlightMaxLength = 500;
}
if (m_flSpotlightGoalWidth <= 0)
{
DevMsg("CNPC_MSynth::Spawn: Invalid spotlight width <= 0, setting to 100\n");
m_flSpotlightGoalWidth = 100;
}
if (m_flSpotlightGoalWidth > MAX_BEAM_WIDTH)
{
DevMsg("CNPC_MSynth::Spawn: Invalid spotlight width %.1f (max %.1f).\n", m_flSpotlightGoalWidth, MAX_BEAM_WIDTH);
m_flSpotlightGoalWidth = MAX_BEAM_WIDTH;
}
Precache();
SetModel("models/mortarsynth_over.mdl");
//m_bGibbing = random->RandomInt(0, 1);
m_bGibbing = 1; // BJ For now ALWAYS explode
/*if (m_bIsClawScanner)
{
SetModel("models/shield_scanner.mdl");
}
else
{
SetModel("models/combine_scanner.mdl");
}*/
m_pEnergyBeam = NULL;
m_iHealth = sk_mortarsynth_health.GetFloat();
m_iMaxHealth = m_iHealth;
// ------------------------------------
// Init all class vars
// ------------------------------------
m_vInspectPos = vec3_origin;
m_fInspectEndTime = 0;
m_fCheckCitizenTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_DELAY;
m_fCheckHintTime = gpGlobals->curtime + MSYNTH_HINT_INSPECT_DELAY;
m_fNextPhotographTime = 0;
m_vSpotlightTargetPos = vec3_origin;
m_vSpotlightCurrentPos = vec3_origin;
m_hSpotlight = NULL;
m_hSpotlightTarget = NULL;
AngleVectors(GetLocalAngles(), &m_vSpotlightDir);
m_vSpotlightAngVelocity = vec3_origin;
m_pEyeFlash = 0;
m_fNextSpotlightTime = 0;
m_nFlyMode = MSYNTH_FLY_PATROL;
m_vCurrentBanking = m_vSpotlightDir;
m_flSpotlightCurLength = m_flSpotlightMaxLength;
m_nPoseTail = LookupPoseParameter("tail_control");
m_nPoseDynamo = LookupPoseParameter("dynamo_wheel");
m_nPoseFlare = LookupPoseParameter("alert_control");
m_nPoseFaceVert = LookupPoseParameter("flex_vert");
m_nPoseFaceHoriz = LookupPoseParameter("flex_horz");
// --------------------------------------------
CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1);
m_bPhotoTaken = false;
// BJ: Check model collision
Vector vecSurroundingMins(-80, -80, 0);
Vector vecSurroundingMaxs(80, 80, 214);
CollisionProp()->SetSurroundingBoundsType(USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs);
SetHullType(HULL_SMALL_CENTERED); // BJ: Perfect HULL type
SetHullSizeNormal();
AddSolidFlags(FSOLID_NOT_STANDABLE); // player stand upper?
SetSolid(SOLID_BBOX); //bj test
//SetCollisionGroup(HL2COLLISION_GROUP_HEADCRAB);
if (m_bIsClawScanner)
DevMsg("Mortar - Claw type check \n");
BaseClass::Spawn();
// sethull was here
// Watch for this error state
if (m_bOnlyInspectPlayers && m_bNeverInspectPlayers)
{
Assert(0);
Warning("ERROR: Mortarsynth set to never and always inspect players!\n");
}
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CNPC_MSynth::Activate()
{
BaseClass::Activate();
// Have to do this here because sprites do not go across level transitions
m_pEyeFlash = CSprite::SpriteCreate("sprites/blueflare1.vmt", GetLocalOrigin(), FALSE);
m_pEyeFlash->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
m_pEyeFlash->SetAttachment(this, LookupAttachment(MSYNTH_ATTACHMENT_LIGHT));
m_pEyeFlash->SetBrightness(0);
m_pEyeFlash->SetScale(1.4);
}
//------------------------------------------------------------------------------
// Purpose: Override to split in two when attacked
//------------------------------------------------------------------------------
int CNPC_MSynth::OnTakeDamage_Alive(const CTakeDamageInfo &info)
{
// Turn off my spotlight when shot
SpotlightDestroy();
m_fNextSpotlightTime = gpGlobals->curtime + 2.0f;
return (BaseClass::OnTakeDamage_Alive(info));
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::Gib(void)
{
if (IsMarkedForDeletion())
return;
//DevMsg("Msynth GIB after_mark \n");
// BJ: ONLY RIGHT GIBS NO LESS
/*CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib1.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib4.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib5.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib6.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/scanner_gib04.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib01.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib02.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib03.mdl");
CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib04.mdl");*/
// Add a random chance of spawning a battery...
if (!HasSpawnFlags(SF_NPC_NO_WEAPON_DROP) && random->RandomFloat(0.0f, 1.0f) < 0.3f)
{
CItem *pBattery = (CItem*)CreateEntityByName("item_battery");
if (pBattery)
{
pBattery->SetAbsOrigin(GetAbsOrigin());
pBattery->SetAbsVelocity(GetAbsVelocity());
pBattery->SetLocalAngularVelocity(GetLocalAngularVelocity());
pBattery->ActivateWhenAtRest();
pBattery->Spawn();
}
}
//DeployMine();
RadiusDamage(CTakeDamageInfo(this, this, sk_mortarsynth_explode_damage.GetFloat(), DMG_BLAST), GetAbsOrigin(), 256, CLASS_COMBINE, NULL); // BJ Self Explode damage
BaseClass::Gib();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pInflictor -
// pAttacker -
// flDamage -
// bitsDamageType -
//-----------------------------------------------------------------------------
void CNPC_MSynth::Event_Killed(const CTakeDamageInfo &info)
{
// Copy off the takedamage info that killed me, since we're not going to call
// up into the base class's Event_Killed() until we gib. (gibbing is ultimate death)
m_KilledInfo = info;
//MikeD: We don't have mines
//DeployMine();
if (m_pEnergyBeam)
{
UTIL_Remove(m_pEnergyBeam);
m_pEnergyBeam = NULL;
}
for (int i = 0; i < 2; i++)
{
if (m_pEnergyGlow[i])
{
UTIL_Remove(m_pEnergyGlow[i]);
m_pEnergyGlow[i] = NULL;
}
}
ClearInspectTarget();
// Interrupt whatever schedule I'm on
SetCondition(COND_SCHEDULE_DONE);
// Remove spotlight
SpotlightDestroy();
// Remove sprite
UTIL_Remove(m_pEyeFlash);
m_pEyeFlash = NULL;
// If I have an enemy and I'm up high, do a dive bomb (unless dissolved)
if (!m_bIsClawScanner && GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false)
{
DevMsg("Mortarsynth PRE bomb mode attack \n");
Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin();
if ((vecDelta.z > 120) && (vecDelta.Length() > 360))
{
DevMsg("Mortarsynth bomb mode attack \n");
// If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again.
// This is especially bad if someone machineguns the divebombing scanner.
AttackDivebomb();
return;
}
}
if (m_bGibbing == 1)
Gib();
else
BecomeRagdoll(m_KilledInfo, m_KilledInfo.GetDamageForce());
}
bool CNPC_MSynth::BecomeRagdoll(const CTakeDamageInfo &info, const Vector &forceVector)
{
if (m_bGibbing == 1)
return false;
else
return BaseClass::BecomeRagdoll(info, forceVector);
}
//-----------------------------------------------------------------------------
// Purpose: Tells use whether or not the NPC cares about a given type of hint node.
// Input : sHint -
// Output : TRUE if the NPC is interested in this hint type, FALSE if not.
//-----------------------------------------------------------------------------
bool CNPC_MSynth::FValidateHintType(CAI_Hint *pHint)
{
return(pHint->HintType() == HINT_WORLD_WINDOW);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : Type -
//-----------------------------------------------------------------------------
int CNPC_MSynth::TranslateSchedule(int scheduleType)
{
switch (scheduleType)
{
case SCHED_IDLE_STAND:
{
return SCHED_SCANNER_PATROL;
}
case SCHED_SCANNER_PATROL:
return SCHED_CSCANNER_PATROL;
}
return BaseClass::TranslateSchedule(scheduleType);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : idealActivity -
// *pIdealWeaponActivity -
// Output : int
//-----------------------------------------------------------------------------
Activity CNPC_MSynth::NPC_TranslateActivity(Activity eNewActivity)
{
if (!m_bIsClawScanner || GetActivity() == ACT_RANGE_ATTACK1)
{
return BaseClass::NPC_TranslateActivity(eNewActivity);
}
// The claw scanner came along a little late and doesn't have the activities
// of the city scanner. So Just pick between these three
if (eNewActivity == ACT_DISARM)
{
// Closing up.
return eNewActivity;
}
if (m_bIsOpen)
{
return ACT_IDLE_ANGRY;
}
else
{
return ACT_IDLE;
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_MSynth::HandleAnimEvent(animevent_t *pEvent)
{
if (pEvent->event == AE_MSYNTH_CLOSED)
{
m_bIsOpen = false;
SetActivity(ACT_IDLE);
return;
}
BaseClass::HandleAnimEvent(pEvent);
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
char *CNPC_MSynth::GetEngineSound(void)
{
//if (m_bIsClawScanner)
//return MSYNTH_ENGINE_SOUND;
return MSYNTH_ENGINE_SOUND;
}
//-----------------------------------------------------------------------------
// Purpose: Plays the engine sound.
//-----------------------------------------------------------------------------
void CNPC_MSynth::NPCThink(void)
{
if (!IsAlive())
{
SetActivity((Activity)ACT_SCANNER_RETRACT_PRONGS);
StudioFrameAdvance();
SetNextThink(gpGlobals->curtime + 0.1f);
}
else
{
BaseClass::NPCThink();
SpotlightUpdate();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::Precache(void)
{
PrecacheModel("models/mortarsynth_over.mdl");
PrecacheParticleSystem("CombineGuard_Attack_Laser");
PrecacheParticleSystem("CombineGuard_Muzzle_Flash");
PrecacheParticleSystem("weapon_muzzle_flash_shock_npc");
PrecacheScriptSound(MSYNTH_IDLE_SOUND);
PrecacheScriptSound(MSYNTH_IDLE2_SOUND);
PrecacheScriptSound(MSYNTH_ATTACK_SOUND);
PrecacheScriptSound(MSYNTH_SHOCK_IMPACT_SOUND);
PrecacheScriptSound(MSYNTH_DEPLOY_SOUND);
PrecacheScriptSound(MSYNTH_PREATTACK_SOUND);
PrecacheScriptSound(MSYNTH_PAIN_SOUND);
PrecacheScriptSound(MSYNTH_ALERT_SOUND);
PrecacheScriptSound(MSYNTH_DIE_SOUND);
PrecacheScriptSound(MSYNTH_ENGINE_SOUND);
PrecacheScriptSound(MSYNTH_SPEECH1_SOUND);
PrecacheScriptSound(MSYNTH_SPEECH2_SOUND);
PrecacheScriptSound(MSYNTH_SPEECH3_SOUND);
PrecacheScriptSound(MSYNTH_SPEECH4_SOUND);
// BJ: ONLY RIGHT GIBS NO LESS
// Model
//if (m_bIsClawScanner)
//{
//PrecacheModel("models/shield_scanner.mdl");
PrecacheModel("models/gibs/Shield_Scanner_Gib1.mdl");
//PrecacheModel("models/gibs/Shield_Scanner_Gib2.mdl");
//PrecacheModel("models/gibs/Shield_Scanner_Gib3.mdl");
PrecacheModel("models/gibs/Shield_Scanner_Gib4.mdl");
PrecacheModel("models/gibs/Shield_Scanner_Gib5.mdl");
PrecacheModel("models/gibs/Shield_Scanner_Gib6.mdl");
PrecacheModel("models/gibs/scanner_gib04.mdl");
PrecacheModel("models/gibs/manhack_gib01.mdl");
PrecacheModel("models/gibs/manhack_gib02.mdl");
PrecacheModel("models/gibs/manhack_gib03.mdl");
PrecacheModel("models/gibs/manhack_gib04.mdl");
/*
PrecacheScriptSound("NPC_SScanner.Shoot");
PrecacheScriptSound("NPC_SScanner.Alert");
PrecacheScriptSound("NPC_SScanner.Die");
PrecacheScriptSound("NPC_SScanner.Combat");
PrecacheScriptSound("NPC_SScanner.Idle");
PrecacheScriptSound("NPC_SScanner.Pain");
PrecacheScriptSound("NPC_SScanner.TakePhoto");
PrecacheScriptSound("NPC_SScanner.AttackFlash");
PrecacheScriptSound("NPC_SScanner.DiveBombFlyby");
PrecacheScriptSound("NPC_SScanner.DiveBomb");
PrecacheScriptSound("NPC_SScanner.DeployMine");
*/
//PrecacheScriptSound("NPC_SScanner.FlyLoop");
UTIL_PrecacheOther("combine_mine");
//}
/*else
{
PrecacheModel("models/combine_scanner.mdl");
PrecacheModel("models/gibs/scanner_gib01.mdl");
PrecacheModel("models/gibs/scanner_gib02.mdl");
PrecacheModel("models/gibs/scanner_gib02.mdl");
PrecacheModel("models/gibs/scanner_gib04.mdl");
PrecacheModel("models/gibs/scanner_gib05.mdl");
PrecacheScriptSound("NPC_CScanner.Shoot");
PrecacheScriptSound("NPC_CScanner.Alert");
PrecacheScriptSound("NPC_CScanner.Die");
PrecacheScriptSound("NPC_CScanner.Combat");
PrecacheScriptSound("NPC_CScanner.Idle");
PrecacheScriptSound("NPC_CScanner.Pain");
PrecacheScriptSound("NPC_CScanner.TakePhoto");
PrecacheScriptSound("NPC_CScanner.AttackFlash");
PrecacheScriptSound("NPC_CScanner.DiveBombFlyby");
PrecacheScriptSound("NPC_CScanner.DiveBomb");
PrecacheScriptSound("NPC_CScanner.DeployMine");
PrecacheScriptSound("NPC_CScanner.FlyLoop");
}*/
// Sprites
m_nHaloSprite = PrecacheModel("sprites/light_glow03.vmt");
PrecacheModel("sprites/glow_test02.vmt");
BaseClass::Precache();
}
//------------------------------------------------------------------------------
// Purpose: Request help inspecting from other squad members
//------------------------------------------------------------------------------
void CNPC_MSynth::RequestInspectSupport(void)
{
if (m_pSquad)
{
AISquadIter_t iter;
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember(&iter); pSquadMember; pSquadMember = m_pSquad->GetNextMember(&iter))
{
if (pSquadMember != this)
{
if (GetTarget())
{
pSquadMember->DispatchInteraction(g_interactionScannerSupportEntity, ((void *)((CBaseEntity*)GetTarget())), this);
}
else
{
pSquadMember->DispatchInteraction(g_interactionScannerSupportPosition, ((void *)m_vInspectPos.Base()), this);
}
}
}
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
bool CNPC_MSynth::IsValidInspectTarget(CBaseEntity *pEntity)
{
// If a citizen, make sure he can be inspected again
if (pEntity->Classify() == CLASS_CITIZEN_PASSIVE)
{
if (((CNPC_Citizen*)pEntity)->GetNextScannerInspectTime() > gpGlobals->curtime)
{
return false;
}
}
// Make sure no other squad member has already chosen to
// inspect this entity
if (m_pSquad)
{
AISquadIter_t iter;
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember(&iter); pSquadMember; pSquadMember = m_pSquad->GetNextMember(&iter))
{
if (pSquadMember->GetTarget() == pEntity)
{
return false;
}
}
}
// Do not inspect friendly targets
if (IRelationType(pEntity) == D_LI)
return false;
return true;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
CBaseEntity* CNPC_MSynth::BestInspectTarget(void)
{
if (!m_bShouldInspect)
return NULL;
CBaseEntity* pBestEntity = NULL;
float fBestDist = MAX_COORD_RANGE;
float fTestDist;
CBaseEntity *pEntity = NULL;
// If I have a spotlight, search from the spotlight position
// otherwise search from my position
Vector vSearchOrigin;
float fSearchDist;
if (m_hSpotlightTarget != NULL)
{
vSearchOrigin = m_hSpotlightTarget->GetAbsOrigin();
fSearchDist = MSYNTH_CIT_INSPECT_GROUND_DIST;
}
else
{
vSearchOrigin = WorldSpaceCenter();
fSearchDist = MSYNTH_CIT_INSPECT_FLY_DIST;
}
if (m_bOnlyInspectPlayers)
{
CBasePlayer *pPlayer = AI_GetSinglePlayer();
if (!pPlayer)
return NULL;
if (!pPlayer->IsAlive() || (pPlayer->GetFlags() & FL_NOTARGET))
return NULL;
return WorldSpaceCenter().DistToSqr(pPlayer->EyePosition()) <= (fSearchDist * fSearchDist) ? pPlayer : NULL;
}
CUtlVector<CBaseEntity *> candidates;
float fSearchDistSq = fSearchDist * fSearchDist;
int i;
// Inspect players unless told otherwise
if (m_bNeverInspectPlayers == false)
{
// Players
for (i = 1; i <= gpGlobals->maxClients; i++)
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex(i);
if (pPlayer)
{
if (vSearchOrigin.DistToSqr(pPlayer->GetAbsOrigin()) < fSearchDistSq)
{
candidates.AddToTail(pPlayer);
}
}
}
}
// NPCs
CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
for (i = 0; i < g_AI_Manager.NumAIs(); i++)
{
if (ppAIs[i] != this && vSearchOrigin.DistToSqr(ppAIs[i]->GetAbsOrigin()) < fSearchDistSq)
{
candidates.AddToTail(ppAIs[i]);
}
}
for (i = 0; i < candidates.Count(); i++)
{
pEntity = candidates[i];
Assert(pEntity != this && (pEntity->MyNPCPointer() || pEntity->IsPlayer()));
CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
if ((pNPC && pNPC->Classify() == CLASS_CITIZEN_PASSIVE) || pEntity->IsPlayer())
{
if (pEntity->GetFlags() & FL_NOTARGET)
continue;
if (pEntity->IsAlive() == false)
continue;
// Ensure it's within line of sight
if (!FVisible(pEntity))
continue;
fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length();
if (fTestDist < fBestDist)
{
if (IsValidInspectTarget(pEntity))
{
fBestDist = fTestDist;
pBestEntity = pEntity;
}
}
}
}
return pBestEntity;
}
//------------------------------------------------------------------------------
// Purpose: Clears any previous inspect target and set inspect target to
// the given entity and set the durection of the inspection
//------------------------------------------------------------------------------
void CNPC_MSynth::SetInspectTargetToEnt(CBaseEntity *pEntity, float fInspectDuration)
{
ClearInspectTarget();
SetTarget(pEntity);
m_fInspectEndTime = gpGlobals->curtime + fInspectDuration;
}
//------------------------------------------------------------------------------
// Purpose: Clears any previous inspect target and set inspect target to
// the given hint node and set the durection of the inspection
//------------------------------------------------------------------------------
void CNPC_MSynth::SetInspectTargetToHint(CAI_Hint *pHint, float fInspectDuration)
{
ClearInspectTarget();
float yaw = pHint->Yaw();
// --------------------------------------------
// Figure out the location that the hint hits
// --------------------------------------------
Vector vHintDir = UTIL_YawToVector(yaw);
Vector vHintOrigin;
pHint->GetPosition(this, &vHintOrigin);
Vector vHintEnd = vHintOrigin + (vHintDir * 512);
trace_t tr;
AI_TraceLine(vHintOrigin, vHintEnd, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr);
if (g_debug_mortarsynth.GetBool())
{
NDebugOverlay::Line(vHintOrigin, tr.endpos, 255, 0, 0, true, 4.0f);
NDebugOverlay::Cross3D(tr.endpos, -Vector(8, 8, 8), Vector(8, 8, 8), 255, 0, 0, true, 4.0f);
}
if (tr.fraction == 1.0f)
{
DevMsg("ERROR: Mortarsynth hint node not facing a surface!\n");
}
else
{
SetHintNode(pHint);
m_vInspectPos = tr.endpos;
pHint->Lock(this);
m_fInspectEndTime = gpGlobals->curtime + fInspectDuration;
}
}
//------------------------------------------------------------------------------
// Purpose: Clears any previous inspect target and set inspect target to
// the given position and set the durection of the inspection
// Input :
// Output :
//------------------------------------------------------------------------------
void CNPC_MSynth::SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration)
{
ClearInspectTarget();
m_vInspectPos = vInspectPos;
m_fInspectEndTime = gpGlobals->curtime + fInspectDuration;
}
//------------------------------------------------------------------------------
// Purpose: Clears out any previous inspection targets
//------------------------------------------------------------------------------
void CNPC_MSynth::ClearInspectTarget(void)
{
if (GetIdealState() != NPC_STATE_SCRIPT)
{
SetTarget(NULL);
}
ClearHintNode(MSYNTH_HINT_INSPECT_LENGTH);
m_vInspectPos = vec3_origin;
}
//------------------------------------------------------------------------------
// Purpose: Returns true if there is a position to be inspected.
//------------------------------------------------------------------------------
bool CNPC_MSynth::HaveInspectTarget(void)
{
if (GetTarget() != NULL)
return true;
if (m_vInspectPos != vec3_origin)
return true;
return false;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
Vector CNPC_MSynth::InspectTargetPosition(void)
{
// If we have a target, return an adjust position
if (GetTarget() != NULL)
{
Vector vEyePos = GetTarget()->EyePosition();
// If in spotlight mode, aim for ground below target unless is client
if (m_nFlyMode == MSYNTH_FLY_SPOT && !(GetTarget()->GetFlags() & FL_CLIENT))
{
Vector vInspectPos;
vInspectPos.x = vEyePos.x;
vInspectPos.y = vEyePos.y;
vInspectPos.z = GetFloorZ(vEyePos);
// Let's take three-quarters between eyes and ground
vInspectPos.z += (vEyePos.z - vInspectPos.z) * 0.75f;
return vInspectPos;
}
else
{
// Otherwise aim for eyes
return vEyePos;
}
}
else if (m_vInspectPos != vec3_origin)
{
return m_vInspectPos;
}
else
{
DevMsg("InspectTargetPosition called with no target!\n");
return m_vInspectPos;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputShouldInspect(inputdata_t &inputdata)
{
m_bShouldInspect = (inputdata.value.Int() != 0);
if (!m_bShouldInspect)
{
if (GetEnemy() == GetTarget())
SetEnemy(NULL);
ClearInspectTarget();
SetTarget(NULL);
SpotlightDestroy();
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_MSynth::DeployMine()
{
CBaseEntity *child;
// iterate through all children
for (child = FirstMoveChild(); child != NULL; child = child->NextMovePeer())
{
if (FClassnameIs(child, "combine_mine"))
{
child->SetParent(NULL);
child->SetAbsVelocity(GetAbsVelocity());
child->SetOwnerEntity(this);
ScannerEmitSound("DeployMine");
IPhysicsObject *pPhysObj = child->VPhysicsGetObject();
if (pPhysObj)
{
// Make sure the mine's awake
pPhysObj->Wake();
}
if (m_bIsClawScanner)
{
// Fold up.
SetActivity(ACT_DISARM);
}
return;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CNPC_MSynth::GetMaxSpeed()
{
if (IsStriderScout())
{
return MSYNTH_SCOUT_MAX_SPEED;
}
return BaseClass::GetMaxSpeed();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputDeployMine(inputdata_t &inputdata)
{
DeployMine();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputEquipMine(inputdata_t &inputdata)
{
CBaseEntity *child;
// iterate through all children
for (child = FirstMoveChild(); child != NULL; child = child->NextMovePeer())
{
if (FClassnameIs(child, "combine_mine"))
{
// Already have a mine!
return;
}
}
CBaseEntity *pEnt;
pEnt = CreateEntityByName("combine_mine");
bool bPlacedMine = false;
if (m_bIsClawScanner)
{
Vector vecOrigin;
QAngle angles;
int attachment;
attachment = LookupAttachment("claw");
if (attachment > -1)
{
GetAttachment(attachment, vecOrigin, angles);
pEnt->SetAbsOrigin(vecOrigin);
pEnt->SetAbsAngles(angles);
pEnt->SetOwnerEntity(this);
pEnt->SetParent(this, attachment);
m_bIsOpen = true;
SetActivity(ACT_IDLE_ANGRY);
bPlacedMine = true;
}
}
if (!bPlacedMine)
{
Vector vecMineLocation = GetAbsOrigin();
vecMineLocation.z -= 32.0;
pEnt->SetAbsOrigin(vecMineLocation);
pEnt->SetAbsAngles(GetAbsAngles());
pEnt->SetOwnerEntity(this);
pEnt->SetParent(this);
}
pEnt->Spawn();
}
//-----------------------------------------------------------------------------
// Purpose: Tells the scanner to go photograph an entity.
// Input : String name or classname of the entity to inspect.
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputInspectTargetPhoto(inputdata_t &inputdata)
{
m_vLastPatrolDir = vec3_origin;
m_bPhotoTaken = false;
InspectTarget(inputdata, MSYNTH_FLY_PHOTO);
}
//-----------------------------------------------------------------------------
// Purpose: Tells the scanner to go spotlight an entity.
// Input : String name or classname of the entity to inspect.
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputInspectTargetSpotlight(inputdata_t &inputdata)
{
InspectTarget(inputdata, MSYNTH_FLY_SPOT);
}
//-----------------------------------------------------------------------------
// Purpose: Tells the scanner to go photo or spotlight an entity.
// Input : String name or classname of the entity to inspect.
//-----------------------------------------------------------------------------
void CNPC_MSynth::InspectTarget(inputdata_t &inputdata, MSynthFlyMode_t eFlyMode)
{
CBaseEntity *pEnt = gEntList.FindEntityGeneric(NULL, inputdata.value.String(), this, inputdata.pActivator);
if (pEnt != NULL)
{
// Set and begin to inspect our target
SetInspectTargetToEnt(pEnt, MSYNTH_CIT_INSPECT_LENGTH);
m_nFlyMode = eFlyMode;
SetCondition(COND_CSCANNER_HAVE_INSPECT_TARGET);
// Stop us from any other navigation we were doing
GetNavigator()->ClearGoal();
}
else
{
DevMsg("InspectTarget: target %s not found!\n", inputdata.value.String());
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_MSynth::MovingToInspectTarget(void)
{
// If we're flying to a photograph target and the photo isn't yet taken, we're still moving to it
if (m_nFlyMode == MSYNTH_FLY_PHOTO && m_bPhotoTaken == false)
return true;
// If we're still on a path, then we're still moving
if (HaveInspectTarget() && GetNavigator()->IsGoalActive())
return true;
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::GatherConditions(void)
{
BaseClass::GatherConditions();
// Clear out our old conditions
ClearCondition(COND_CSCANNER_INSPECT_DONE);
ClearCondition(COND_CSCANNER_HAVE_INSPECT_TARGET);
ClearCondition(COND_CSCANNER_SPOT_ON_TARGET);
ClearCondition(COND_CSCANNER_CAN_PHOTOGRAPH);
// We don't do any of these checks if we have an enemy
if (GetEnemy())
return;
// --------------------------------------
// COND_CSCANNER_INSPECT_DONE
//
// If my inspection over
// ---------------------------------------------------------
// Refresh our timing if we're still moving to our inspection target
if (MovingToInspectTarget())
{
m_fInspectEndTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_LENGTH;
}
// Update our follow times
if (HaveInspectTarget() && gpGlobals->curtime > m_fInspectEndTime && m_nFlyMode != MSYNTH_FLY_FOLLOW)
{
SetCondition(COND_CSCANNER_INSPECT_DONE);
m_fCheckCitizenTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_DELAY;
m_fCheckHintTime = gpGlobals->curtime + MSYNTH_HINT_INSPECT_DELAY;
ClearInspectTarget();
}
// ----------------------------------------------------------
// If I heard a sound and I don't have an enemy, inspect it
// ----------------------------------------------------------
if ((HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER)) && m_nFlyMode != MSYNTH_FLY_FOLLOW)
{
CSound *pSound = GetBestSound();
if (pSound)
{
// Chase an owner if we can
if (pSound->m_hOwner != NULL)
{
// Don't inspect sounds of things we like
if (IRelationType(pSound->m_hOwner) != D_LI)
{
// Only bother if we can see it
if (FVisible(pSound->m_hOwner))
{
SetInspectTargetToEnt(pSound->m_hOwner, MSYNTH_SOUND_INSPECT_LENGTH);
}
}
}
else
{
// Otherwise chase the specific sound
Vector vSoundPos = pSound->GetSoundOrigin();
SetInspectTargetToPos(vSoundPos, MSYNTH_SOUND_INSPECT_LENGTH);
}
m_nFlyMode = (random->RandomInt(0, 2) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO;
}
}
// --------------------------------------
// COND_CSCANNER_HAVE_INSPECT_TARGET
//
// Look for a nearby citizen or player to hassle.
// ---------------------------------------------------------
// Check for citizens to inspect
if (gpGlobals->curtime > m_fCheckCitizenTime && HaveInspectTarget() == false)
{
CBaseEntity *pBestEntity = BestInspectTarget();
if (pBestEntity != NULL)
{
SetInspectTargetToEnt(pBestEntity, MSYNTH_CIT_INSPECT_LENGTH);
m_nFlyMode = (random->RandomInt(0, 3) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO;
SetCondition(COND_CSCANNER_HAVE_INSPECT_TARGET);
}
}
// Check for hints to inspect
if (gpGlobals->curtime > m_fCheckHintTime && HaveInspectTarget() == false)
{
SetHintNode(CAI_HintManager::FindHint(this, HINT_WORLD_WINDOW, 0, MSYNTH_CIT_INSPECT_FLY_DIST));
if (GetHintNode())
{
m_fCheckHintTime = gpGlobals->curtime + MSYNTH_HINT_INSPECT_DELAY;
m_nFlyMode = (random->RandomInt(0, 2) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO;
SetInspectTargetToHint(GetHintNode(), MSYNTH_HINT_INSPECT_LENGTH);
SetCondition(COND_CSCANNER_HAVE_INSPECT_TARGET);
}
}
// --------------------------------------
// COND_CSCANNER_SPOT_ON_TARGET
//
// True when spotlight is on target ent
// --------------------------------------
if (m_hSpotlightTarget != NULL && HaveInspectTarget() && m_hSpotlightTarget->GetSmoothedVelocity().Length() < 25)
{
// If I have a target entity, check my spotlight against the
// actual position of the entity
if (GetTarget())
{
float fInspectDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length();
if (fInspectDist < 100)
{
SetCondition(COND_CSCANNER_SPOT_ON_TARGET);
}
}
// Otherwise just check by beam direction
else
{
Vector vTargetDir = SpotlightTargetPos() - GetLocalOrigin();
VectorNormalize(vTargetDir);
float dotpr = DotProduct(vTargetDir, m_vSpotlightDir);
if (dotpr > 0.95)
{
SetCondition(COND_CSCANNER_SPOT_ON_TARGET);
}
}
}
// --------------------------------------------
// COND_CSCANNER_CAN_PHOTOGRAPH
//
// True when can photograph target ent
// --------------------------------------------
ClearCondition(COND_CSCANNER_CAN_PHOTOGRAPH);
if (m_nFlyMode == MSYNTH_FLY_PHOTO)
{
// Make sure I have something to photograph and I'm ready to photograph and I'm not moving to fast
if (gpGlobals->curtime > m_fNextPhotographTime && HaveInspectTarget() && GetCurrentVelocity().LengthSqr() < (64 * 64))
{
// Check that I'm in the right distance range
float fInspectDist = (InspectTargetPosition() - GetAbsOrigin()).Length2D();
// See if we're within range
if (fInspectDist > MSYNTH_PHOTO_NEAR_DIST && fInspectDist < MSYNTH_PHOTO_FAR_DIST)
{
// Make sure we're looking at the target
if (UTIL_AngleDiff(GetAbsAngles().y, VecToYaw(InspectTargetPosition() - GetAbsOrigin())) < 4.0f)
{
trace_t tr;
AI_TraceLine(GetAbsOrigin(), InspectTargetPosition(), MASK_BLOCKLOS, GetTarget(), COLLISION_GROUP_NONE, &tr);
if (tr.fraction == 1.0f)
{
SetCondition(COND_CSCANNER_CAN_PHOTOGRAPH);
}
}
}
}
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::PrescheduleThink(void)
{
BaseClass::PrescheduleThink();
// Go back to idling if we're done
if (GetIdealActivity() == ACT_SCANNER_FLARE_START)
{
if (IsSequenceFinished() && GetActivity() != ACT_RANGE_ATTACK1)
{
SetIdealActivity((Activity)ACT_IDLE);
}
}
}
Class_T CNPC_MSynth::Classify(void)
{
// return(CLASS_MORTAR_SYNTH);
return CLASS_COMBINE;
}
//-----------------------------------------------------------------------------
// Purpose: Overridden because if the player is a criminal, we hate them.
// Input : pTarget - Entity with which to determine relationship.
// Output : Returns relationship value.
//-----------------------------------------------------------------------------
Disposition_t CNPC_MSynth::IRelationType(CBaseEntity *pTarget)
{
// If it's the player and they are a criminal, we hates them
if (pTarget && pTarget->Classify() == CLASS_PLAYER)
{
if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
//DevMsg("Msynth Gordon pre criminal? \n");
return D_NU;
}
return BaseClass::IRelationType(pTarget);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTask -
//-----------------------------------------------------------------------------
void CNPC_MSynth::RunTask(const Task_t *pTask)
{
switch (pTask->iTask)
{
case TASK_CSCANNER_PHOTOGRAPH:
{
if (IsWaitFinished())
{
// If light was on turn it off
if (m_pEyeFlash->GetBrightness() > 0)
{
m_pEyeFlash->SetBrightness(0);
// I'm done with this target
if (gpGlobals->curtime > m_fInspectEndTime)
{
ClearInspectTarget();
TaskComplete();
}
// Otherwise take another picture
else
{
SetWait(5.0f, 10.0f);
}
}
// If light was off, take another picture
else
{
TakePhoto();
SetWait(0.1f);
}
}
break;
}
case TASK_CSCANNER_ATTACK_PRE_FLASH:
{
AttackPreFlash();
if (IsWaitFinished())
{
TaskComplete();
}
break;
}
case TASK_CSCANNER_ATTACK_FLASH:
{
if (IsWaitFinished())
{
AttackFlashBlind();
TaskComplete();
}
break;
}
default:
{
BaseClass::RunTask(pTask);
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Gets the appropriate next schedule based on current condition
// bits.
//-----------------------------------------------------------------------------
int CNPC_MSynth::SelectSchedule(void)
{
// Turn our flash off in case we were interrupted while it was on.
if (m_pEyeFlash)
{
m_pEyeFlash->SetBrightness(0);
}
// ----------------------------------------------------
// If I'm dead, go into a dive bomb
// ----------------------------------------------------
if (m_iHealth <= 0)
{
m_flSpeed = MSYNTH_MAX_DIVE_BOMB_SPEED;
return SCHED_SCANNER_ATTACK_DIVEBOMB;
}
// -------------------------------
// If I'm in a script sequence
// -------------------------------
if (m_NPCState == NPC_STATE_SCRIPT)
return(BaseClass::SelectSchedule());
// -------------------------------
// Flinch
// -------------------------------
if (HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE))
{
if (IsHeldByPhyscannon())
return SCHED_SMALL_FLINCH;
if (m_NPCState == NPC_STATE_IDLE)
return SCHED_SMALL_FLINCH;
if (m_NPCState == NPC_STATE_ALERT)
{
if (m_iHealth < (3 * sk_mortarsynth_health.GetFloat() / 4))
return SCHED_TAKE_COVER_FROM_ORIGIN;
if (SelectWeightedSequence(ACT_SMALL_FLINCH) != -1)
return SCHED_SMALL_FLINCH;
}
else
{
if (random->RandomInt(0, 10) < 4)
return SCHED_SMALL_FLINCH;
}
}
// I'm being held by the physcannon... struggle!
if (IsHeldByPhyscannon())
return SCHED_SCANNER_HELD_BY_PHYSCANNON;
// ----------------------------------------------------------
// If I have an enemy
// ----------------------------------------------------------
if (GetEnemy() != NULL && GetEnemy()->IsAlive() && m_bShouldInspect)
{
// Always chase the enemy
SetInspectTargetToEnt(GetEnemy(), 9999);
// Patrol if the enemy has vanished
if (HasCondition(COND_LOST_ENEMY))
return SCHED_SCANNER_PATROL;
// Chase via route if we're directly blocked
if (HasCondition(COND_SCANNER_FLY_BLOCKED))
return SCHED_SCANNER_CHASE_ENEMY;
// Attack if it's time
if (gpGlobals->curtime < m_flNextAttack)
return SCHED_CSCANNER_SPOTLIGHT_HOVER;
// Melee attack if possible
if (HasCondition(COND_CAN_MELEE_ATTACK1))
{
if (random->RandomInt(0, 1))
return SCHED_CSCANNER_ATTACK_FLASH;
// TODO: a schedule where he makes an alarm sound?
return SCHED_SCANNER_CHASE_ENEMY;
}
// If I'm far from the enemy, stay up high and approach in spotlight mode
float fAttack2DDist = (GetEnemyLKP() - GetAbsOrigin()).Length2D();
if (fAttack2DDist > MSYNTH_ATTACK_FAR_DIST)
return SCHED_CSCANNER_SPOTLIGHT_HOVER;
// Otherwise fly in low for attack
return SCHED_SCANNER_ATTACK_HOVER;
}
// ----------------------------------------------------------
// If I have something to inspect
// ----------------------------------------------------------
if (HaveInspectTarget())
{
// Pathfind to our goal
if (HasCondition(COND_SCANNER_FLY_BLOCKED))
return SCHED_CSCANNER_MOVE_TO_INSPECT;
// If I was chasing, pick with photographing or spotlighting
if (m_nFlyMode == MSYNTH_FLY_CHASE)
{
m_nFlyMode = (random->RandomInt(0, 1) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO;
}
// Handle spotlight
if (m_nFlyMode == MSYNTH_FLY_SPOT)
{
if (HasCondition(COND_CSCANNER_SPOT_ON_TARGET))
{
if (GetTarget())
{
RequestInspectSupport();
CAI_BaseNPC *pNPC = GetTarget()->MyNPCPointer();
// If I'm leading the inspection, so verbal inspection
if (pNPC && pNPC->GetTarget() == this)
{
return SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT;
}
return SCHED_CSCANNER_SPOTLIGHT_HOVER;
}
return SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS;
}
return SCHED_CSCANNER_SPOTLIGHT_HOVER;
}
// Handle photographing
if (m_nFlyMode == MSYNTH_FLY_PHOTO)
{
if (HasCondition(COND_CSCANNER_CAN_PHOTOGRAPH))
return SCHED_CSCANNER_PHOTOGRAPH;
return SCHED_CSCANNER_PHOTOGRAPH_HOVER;
}
// Handle following after a target
if (m_nFlyMode == MSYNTH_FLY_FOLLOW)
{
//TODO: Randomly make noise, photograph, etc
return SCHED_SCANNER_FOLLOW_HOVER;
}
// Handle patrolling
if ((m_nFlyMode == MSYNTH_FLY_PATROL) || (m_nFlyMode == MSYNTH_FLY_FAST))
return SCHED_SCANNER_PATROL;
}
// Default to patrolling around
return SCHED_SCANNER_PATROL;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::SpotlightDestroy(void)
{
if (m_hSpotlight)
{
UTIL_Remove(m_hSpotlight);
m_hSpotlight = NULL;
UTIL_Remove(m_hSpotlightTarget);
m_hSpotlightTarget = NULL;
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::SpotlightCreate(void)
{
// Make sure we don't already have one
if (m_hSpotlight != NULL)
return;
// Can we create a spotlight yet?
if (gpGlobals->curtime < m_fNextSpotlightTime)
return;
// If I have an enemy, start spotlight on my enemy
if (GetEnemy() != NULL)
{
Vector vEnemyPos = GetEnemyLKP();
Vector vTargetPos = vEnemyPos;
vTargetPos.z = GetFloorZ(vEnemyPos);
m_vSpotlightDir = vTargetPos - GetLocalOrigin();
VectorNormalize(m_vSpotlightDir);
}
// If I have an target, start spotlight on my target
else if (GetTarget() != NULL)
{
Vector vTargetPos = GetTarget()->GetLocalOrigin();
vTargetPos.z = GetFloorZ(GetTarget()->GetLocalOrigin());
m_vSpotlightDir = vTargetPos - GetLocalOrigin();
VectorNormalize(m_vSpotlightDir);
}
// Other wise just start looking downpa
else
{
m_vSpotlightDir = Vector(0, 0, -1);
}
trace_t tr;
AI_TraceLine(GetAbsOrigin(), GetAbsOrigin() + m_vSpotlightDir * 2024, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
/*m_hSpotlightTarget = (CSpotlightEnd*)CreateEntityByName("spotlight_end");
m_hSpotlightTarget->Spawn();
m_hSpotlightTarget->SetLocalOrigin(tr.endpos);
m_hSpotlightTarget->SetOwnerEntity(this);
// YWB: Because the scanner only moves the target during think, make sure we interpolate over 0.1 sec instead of every tick!!!
m_hSpotlightTarget->SetSimulatedEveryTick(false);
// Using the same color as the beam...
m_hSpotlightTarget->SetRenderColor(255, 255, 255);
m_hSpotlightTarget->m_Radius = m_flSpotlightMaxLength;
m_hSpotlight = CBeam::BeamCreate("sprites/glow_test02.vmt", SPOTLIGHT_WIDTH);
// Set the temporary spawnflag on the beam so it doesn't save (we'll recreate it on restore)
m_hSpotlight->AddSpawnFlags(SF_BEAM_TEMPORARY);
m_hSpotlight->SetColor(255, 255, 255);
m_hSpotlight->SetHaloTexture(m_nHaloSprite);
m_hSpotlight->SetHaloScale(32);
m_hSpotlight->SetEndWidth(m_hSpotlight->GetWidth());
m_hSpotlight->SetBeamFlags((FBEAM_SHADEOUT | FBEAM_NOTILE));
m_hSpotlight->SetBrightness(32);
m_hSpotlight->SetNoise(0);
m_hSpotlight->EntsInit(this, m_hSpotlightTarget);
m_hSpotlight->SetHDRColorScale(0.75f); // Scale this back a bit on HDR maps
// attach to light
m_hSpotlight->SetStartAttachment(LookupAttachment(MSYNTH_ATTACHMENT_LIGHT));*/
m_vSpotlightAngVelocity = vec3_origin;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
Vector CNPC_MSynth::SpotlightTargetPos(void)
{
// ----------------------------------------------
// If I have an enemy
// ----------------------------------------------
if (GetEnemy() != NULL)
{
// If I can see my enemy aim for him
if (HasCondition(COND_SEE_ENEMY))
{
// If its client aim for his eyes
if (GetEnemy()->GetFlags() & FL_CLIENT)
{
m_vSpotlightTargetPos = GetEnemy()->EyePosition();
}
// Otherwise same for his feet
else
{
m_vSpotlightTargetPos = GetEnemy()->GetLocalOrigin();
m_vSpotlightTargetPos.z = GetFloorZ(GetEnemy()->GetLocalOrigin());
}
}
// Otherwise aim for last known position if I can see LKP
else
{
Vector vLKP = GetEnemyLKP();
m_vSpotlightTargetPos.x = vLKP.x;
m_vSpotlightTargetPos.y = vLKP.y;
m_vSpotlightTargetPos.z = GetFloorZ(vLKP);
}
}
// ----------------------------------------------
// If I have an inspect target
// ----------------------------------------------
else if (HaveInspectTarget())
{
m_vSpotlightTargetPos = InspectTargetPosition();
}
else
{
// This creates a nice patrol spotlight sweep
// in the direction that I'm travelling
m_vSpotlightTargetPos = GetCurrentVelocity();
m_vSpotlightTargetPos.z = 0;
VectorNormalize(m_vSpotlightTargetPos);
m_vSpotlightTargetPos *= 5;
float noiseScale = 2.5;
const Vector &noiseMod = GetNoiseMod();
m_vSpotlightTargetPos.x += noiseScale*sin(noiseMod.x * gpGlobals->curtime + noiseMod.x);
m_vSpotlightTargetPos.y += noiseScale*cos(noiseMod.y* gpGlobals->curtime + noiseMod.y);
m_vSpotlightTargetPos.z -= fabs(noiseScale*cos(noiseMod.z* gpGlobals->curtime + noiseMod.z));
m_vSpotlightTargetPos = GetLocalOrigin() + m_vSpotlightTargetPos * 2024;
}
return m_vSpotlightTargetPos;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
Vector CNPC_MSynth::SpotlightCurrentPos(void)
{
Vector vTargetDir = SpotlightTargetPos() - GetLocalOrigin();
VectorNormalize(vTargetDir);
if (!m_hSpotlight)
{
DevMsg("Spotlight pos. called w/o spotlight!\n");
return vec3_origin;
}
// -------------------------------------------------
// Beam has momentum relative to it's ground speed
// so sclae the turn rate based on its distance
// from the beam source
// -------------------------------------------------
float fBeamDist = (m_hSpotlightTarget->GetLocalOrigin() - GetLocalOrigin()).Length();
float fBeamTurnRate = atan(50 / fBeamDist);
Vector vNewAngVelocity = fBeamTurnRate * (vTargetDir - m_vSpotlightDir);
float myDecay = 0.4;
m_vSpotlightAngVelocity = (myDecay * m_vSpotlightAngVelocity + (1 - myDecay) * vNewAngVelocity);
// ------------------------------
// Limit overall angular speed
// -----------------------------
if (m_vSpotlightAngVelocity.Length() > 1)
{
Vector velDir = m_vSpotlightAngVelocity;
VectorNormalize(velDir);
m_vSpotlightAngVelocity = velDir * 1;
}
// ------------------------------
// Calculate new beam direction
// ------------------------------
m_vSpotlightDir = m_vSpotlightDir + m_vSpotlightAngVelocity;
m_vSpotlightDir = m_vSpotlightDir;
VectorNormalize(m_vSpotlightDir);
// ---------------------------------------------
// Get beam end point. Only collide with
// solid objects, not npcs
// ---------------------------------------------
trace_t tr;
Vector vTraceEnd = GetAbsOrigin() + (m_vSpotlightDir * 2 * m_flSpotlightMaxLength);
AI_TraceLine(GetAbsOrigin(), vTraceEnd, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
return (tr.endpos);
}
//------------------------------------------------------------------------------
// Purpose: Update the direction and position of my spotlight
//------------------------------------------------------------------------------
void CNPC_MSynth::SpotlightUpdate(void)
{
//FIXME: JDW - E3 Hack
if (m_bNoLight)
{
if (m_hSpotlight)
{
SpotlightDestroy();
}
return;
}
if ((m_nFlyMode != MSYNTH_FLY_SPOT) &&
(m_nFlyMode != MSYNTH_FLY_PATROL) &&
(m_nFlyMode != MSYNTH_FLY_FAST))
{
if (m_hSpotlight)
{
SpotlightDestroy();
}
return;
}
// If I don't have a spotlight attempt to create one
if (m_hSpotlight == NULL)
{
SpotlightCreate();
if (m_hSpotlight == NULL)
return;
}
// Calculate the new homing target position
m_vSpotlightCurrentPos = SpotlightCurrentPos();
// ------------------------------------------------------------------
// If I'm not facing the spotlight turn it off
// ------------------------------------------------------------------
Vector vSpotDir = m_vSpotlightCurrentPos - GetAbsOrigin();
VectorNormalize(vSpotDir);
Vector vForward;
AngleVectors(GetAbsAngles(), &vForward);
float dotpr = DotProduct(vForward, vSpotDir);
if (dotpr < 0.0)
{
// Leave spotlight off for a while
m_fNextSpotlightTime = gpGlobals->curtime + 3.0f;
SpotlightDestroy();
return;
}
// --------------------------------------------------------------
// Update spotlight target velocity
// --------------------------------------------------------------
Vector vTargetDir = (m_vSpotlightCurrentPos - m_hSpotlightTarget->GetLocalOrigin());
float vTargetDist = vTargetDir.Length();
Vector vecNewVelocity = vTargetDir;
VectorNormalize(vecNewVelocity);
vecNewVelocity *= (10 * vTargetDist);
// If a large move is requested, just jump to final spot as we
// probably hit a discontinuity
if (vecNewVelocity.Length() > 200)
{
VectorNormalize(vecNewVelocity);
vecNewVelocity *= 200;
m_hSpotlightTarget->SetLocalOrigin(m_vSpotlightCurrentPos);
}
m_hSpotlightTarget->SetAbsVelocity(vecNewVelocity);
m_hSpotlightTarget->m_vSpotlightOrg = GetAbsOrigin();
// Avoid sudden change in where beam fades out when cross disconinuities
m_hSpotlightTarget->m_vSpotlightDir = m_hSpotlightTarget->GetLocalOrigin() - m_hSpotlightTarget->m_vSpotlightOrg;
float flBeamLength = VectorNormalize(m_hSpotlightTarget->m_vSpotlightDir);
m_flSpotlightCurLength = (0.80*m_flSpotlightCurLength) + (0.2*flBeamLength);
// Fade out spotlight end if past max length.
if (m_flSpotlightCurLength > 2 * m_flSpotlightMaxLength)
{
m_hSpotlightTarget->SetRenderColorA(0);
m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength);
}
else if (m_flSpotlightCurLength > m_flSpotlightMaxLength)
{
m_hSpotlightTarget->SetRenderColorA((1 - ((m_flSpotlightCurLength - m_flSpotlightMaxLength) / m_flSpotlightMaxLength)));
m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength);
}
else
{
m_hSpotlightTarget->SetRenderColorA(1.0);
m_hSpotlight->SetFadeLength(m_flSpotlightCurLength);
}
// Adjust end width to keep beam width constant
float flNewWidth = SPOTLIGHT_WIDTH * (flBeamLength / m_flSpotlightMaxLength);
m_hSpotlight->SetWidth(flNewWidth);
m_hSpotlight->SetEndWidth(flNewWidth);
m_hSpotlightTarget->m_flLightScale = 0.0;
}
//-----------------------------------------------------------------------------
// Purpose: Called just before we are deleted.
//-----------------------------------------------------------------------------
void CNPC_MSynth::UpdateOnRemove(void)
{
SpotlightDestroy();
BaseClass::UpdateOnRemove();
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::TakePhoto(void)
{
/*ScannerEmitSound("TakePhoto");
m_pEyeFlash->SetScale(1.4);
m_pEyeFlash->SetBrightness(255);
m_pEyeFlash->SetColor(255, 255, 255);
Vector vRawPos = InspectTargetPosition();
Vector vLightPos = vRawPos;
// If taking picture of entity, aim at feet
if (GetTarget())
{
if (GetTarget()->IsPlayer())
{
m_OnPhotographPlayer.FireOutput(GetTarget(), this);
//BlindFlashTarget(GetTarget());
}
if (GetTarget()->MyNPCPointer() != NULL)
{
m_OnPhotographNPC.FireOutput(GetTarget(), this);
GetTarget()->MyNPCPointer()->DispatchInteraction(g_interactionScannerInspectBegin, NULL, this);
}
}
SetIdealActivity((Activity)ACT_SCANNER_FLARE_START);*/
m_bPhotoTaken = true;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::AttackPreFlash(void)
{
//ScannerEmitSound(MSYNTH_PREATTACK_SOUND);
SetActivity(ACT_RANGE_ATTACK1);
// -------------------
// Beam beams
// -------------------
if (m_pEnergyBeam == NULL)
{
ScannerEmitSound(MSYNTH_PREATTACK_SOUND);
m_pEnergyBeam = CBeam::BeamCreate("sprites/physbeam.vmt", 2.0);
m_pEnergyBeam->SetColor(0, 100, 255);
m_pEnergyBeam->SetBrightness(100);
m_pEnergyBeam->SetNoise(8);
m_pEnergyBeam->EntsInit(this, this);
m_pEnergyBeam->SetStartAttachment(LookupAttachment("0"));
m_pEnergyBeam->SetEndAttachment(LookupAttachment("1"));
}
// -------------
// Glow
// -------------
if (m_pEnergyGlow[0] == NULL)
{
m_pEnergyGlow[0] = CSprite::SpriteCreate("sprites/glow01.vmt", GetLocalOrigin(), FALSE);
m_pEnergyGlow[0]->SetAttachment(this, LookupAttachment("0"));
m_pEnergyGlow[0]->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
m_pEnergyGlow[0]->SetBrightness(100);
m_pEnergyGlow[0]->SetScale(0.3);
}
if (m_pEnergyGlow[1] == NULL)
{
m_pEnergyGlow[1] = CSprite::SpriteCreate("sprites/glow01.vmt", GetLocalOrigin(), FALSE);
m_pEnergyGlow[1]->SetAttachment(this, LookupAttachment("1"));
m_pEnergyGlow[1]->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
m_pEnergyGlow[1]->SetBrightness(100);
m_pEnergyGlow[1]->SetScale(0.3);
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::AttackFlash(void)
{
ScannerEmitSound(MSYNTH_ATTACK_SOUND);
/*m_pEyeFlash->SetScale(1.8);
m_pEyeFlash->SetBrightness(255);
m_pEyeFlash->SetColor(255, 255, 255);
if (GetEnemy() != NULL)
{
Vector pos = GetEnemyLKP();
CBroadcastRecipientFilter filter;
te->DynamicLight(filter, 0.0, &pos, 200, 200, 255, 0, 300, 0.2, 50);
if (GetEnemy()->IsPlayer())
{
m_OnPhotographPlayer.FireOutput(GetTarget(), this);
}
else if (GetEnemy()->MyNPCPointer())
{
m_OnPhotographNPC.FireOutput(GetTarget(), this);
}
}*/
if (m_BeamArmed)
{
Vector vecSrc, vecAim, vecAimCorpse;
trace_t tr;
Vector forward, right, up;
AngleVectors(GetAbsAngles(), &forward, &right, &up);
vecSrc = GetAbsOrigin() + up * 36;
vecAim = GetShootEnemyDir(vecSrc);
Vector vAttachPos;
QAngle ang = GetAbsAngles();
GetAttachment(1, vAttachPos, ang);
Vector End = vAttachPos + (up + forward * MAX_TRACE_LENGTH);
vecAim = GetShootEnemyDir(vAttachPos, false);
AI_TraceLine(vAttachPos, vecSrc + vecAim * MAX_TRACE_LENGTH, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
DevMsg("Msynth BeamArmed CGattacklaser \n");
DispatchParticleEffect("CombineGuard_Attack_Laser", vAttachPos, tr.endpos, ang, this);
if (tr.m_pEnt != NULL && m_takedamage)
{
ClearMultiDamage();
CTakeDamageInfo firedamage(this, this, sk_mortarsynth_beam_damage.GetFloat(), DMG_DISSOLVE);
CalculateMeleeDamageForce(&firedamage, vecAim, tr.endpos);
if (!cvar->FindVar("sk_mortarsynth_beam_damage")->GetFloat() == 0) // BJ Little Fixup for 0 value
RadiusDamage(CTakeDamageInfo(this, this, 2, DMG_DISSOLVE), tr.endpos, 64.0f, CLASS_NONE, NULL);
tr.m_pEnt->DispatchTraceAttack((firedamage), vecAim, &tr);
}
}
else
{
Vector vecShootOrigin;// = Weapon_ShootPosition();
GetAttachment("light", vecShootOrigin, NULL);
Vector vecShootDir = GetShootEnemyDir(vecShootOrigin);
Vector forward, right, up;
AngleVectors(GetAbsAngles(), &forward, &right, &up);
Vector vecShellVelocity = right * random->RandomFloat(40, 90) + up * random->RandomFloat(75, 200) + forward * random->RandomFloat(-40, 40);
//EjectShell( vecShootOrigin - vecShootDir * 24, vecShellVelocity, GetAbsAngles().y, 0 );
//FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, m_iAmmoType ); // shoot +-5 degrees
Vector src;
GetAttachment("light", src, NULL);
QAngle angAiming;
VectorAngles(vecShootDir, angAiming);
CMSynthProjectile *pProjectile = CMSynthProjectile::BoltCreate(src, angAiming, 0, this);
if (this->GetWaterLevel() == 3)
{
//pShockRifleProjectile->Remove(); //->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY );
CTakeDamageInfo hitself(this, this, 210, DMG_SHOCK);
TakeDamage(hitself);
}
else
{
pProjectile->SetAbsVelocity(vecShootDir * 1500);
}
DevMsg("Msynth Beam NOT armed SHKmuzzleflash \n");
DispatchParticleEffect("weapon_muzzle_flash_shock_npc", PATTACH_POINT_FOLLOW, this, "light", false);
}
if (m_pEnergyBeam)
{
// Let beam kill itself
m_pEnergyBeam->LiveForTime(0.2);
m_pEnergyBeam = NULL;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTarget -
//-----------------------------------------------------------------------------
void CNPC_MSynth::BlindFlashTarget(CBaseEntity *pTarget)
{
// Tell all the striders this person is here!
CAI_BaseNPC ** ppAIs = g_AI_Manager.AccessAIs();
int nAIs = g_AI_Manager.NumAIs();
if (IsStriderScout())
{
for (int i = 0; i < nAIs; i++)
{
if (FClassnameIs(ppAIs[i], "npc_strider"))
{
ppAIs[i]->UpdateEnemyMemory(pTarget, pTarget->GetAbsOrigin(), this);
}
}
}
// Only bother with player
if (pTarget->IsPlayer() == false)
return;
// Scale the flash value by how closely the player is looking at me
Vector vFlashDir = GetAbsOrigin() - pTarget->EyePosition();
VectorNormalize(vFlashDir);
Vector vFacing;
AngleVectors(pTarget->EyeAngles(), &vFacing);
float dotPr = DotProduct(vFlashDir, vFacing);
// Not if behind us
if (dotPr > 0.5f)
{
// Make sure nothing in the way
trace_t tr;
AI_TraceLine(GetAbsOrigin(), pTarget->EyePosition(), MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
if (tr.startsolid == false && tr.fraction == 1.0)
{
color32 white = { 255, 255, 255, MSYNTH_FLASH_MAX_VALUE * dotPr };
if ((g_pMaterialSystemHardwareConfig != NULL) && (g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE))
{
white.a = (byte)((float)white.a * 0.9f);
}
float flFadeTime = (IsX360()) ? 0.5f : 3.0f;
UTIL_ScreenFade(pTarget, white, flFadeTime, 0.5, FFADE_IN);
}
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::AttackFlashBlind(void)
{
/*if (GetEnemy())
{
BlindFlashTarget(GetEnemy());
}*/
m_pEyeFlash->SetBrightness(0);
float fAttackDelay = random->RandomFloat(MSYNTH_ATTACK_MIN_DELAY, MSYNTH_ATTACK_MAX_DELAY);
if (IsStriderScout())
{
// Make strider scouts more snappy.
fAttackDelay *= 0.5;
}
m_flNextAttack = gpGlobals->curtime + fAttackDelay;
m_fNextSpotlightTime = gpGlobals->curtime + 1.0f;
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CNPC_MSynth::AttackDivebomb(void)
{
if (m_hSpotlight)
{
SpotlightDestroy();
}
BaseClass::AttackDivebomb();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pTask -
//-----------------------------------------------------------------------------
void CNPC_MSynth::StartTask(const Task_t *pTask)
{
switch (pTask->iTask)
{
case TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET:
{
// Must have somewhere to fly to
if (HaveInspectTarget() == false)
{
TaskFail("No inspection target to fly to!\n");
return;
}
if (GetTarget())
{
//FIXME: Tweak
//Vector idealPos = IdealGoalForMovement( InspectTargetPosition(), GetAbsOrigin(), 128.0f, 128.0f );
AI_NavGoal_t goal(GOALTYPE_TARGETENT, vec3_origin);
if (GetNavigator()->SetGoal(goal))
{
TaskComplete();
return;
}
}
else
{
AI_NavGoal_t goal(GOALTYPE_LOCATION, InspectTargetPosition());
if (GetNavigator()->SetGoal(goal))
{
TaskComplete();
return;
}
}
// Don't try and inspect this target again for a few seconds
CNPC_Citizen *pCitizen = dynamic_cast<CNPC_Citizen *>(GetTarget());
if (pCitizen)
{
pCitizen->SetNextScannerInspectTime(gpGlobals->curtime + 5.0);
}
TaskFail("No route to inspection target!\n");
}
break;
case TASK_CSCANNER_SPOT_INSPECT_ON:
{
if (GetTarget() == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer();
if (!pNPC)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
pNPC->DispatchInteraction(g_interactionScannerInspectBegin, NULL, this);
// Now we need some time to inspect
m_fInspectEndTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_LENGTH;
TaskComplete();
}
}
break;
}
case TASK_CSCANNER_SPOT_INSPECT_WAIT:
{
if (GetTarget() == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer();
if (!pNPC)
{
SetTarget(NULL);
TaskFail(FAIL_NO_TARGET);
}
else
{
//<<TEMP>>//<<TEMP>> armband too!
pNPC->DispatchInteraction(g_interactionScannerInspectHandsUp, NULL, this);
}
TaskComplete();
}
break;
}
case TASK_CSCANNER_SPOT_INSPECT_OFF:
{
if (GetTarget() == NULL)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer();
if (!pNPC)
{
TaskFail(FAIL_NO_TARGET);
}
else
{
pNPC->DispatchInteraction(g_interactionScannerInspectDone, NULL, this);
// Clear target entity and don't inspect again for a while
SetTarget(NULL);
m_fCheckCitizenTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_DELAY;
TaskComplete();
}
}
break;
}
case TASK_CSCANNER_CLEAR_INSPECT_TARGET:
{
ClearInspectTarget();
TaskComplete();
break;
}
case TASK_CSCANNER_SET_FLY_SPOT:
{
m_nFlyMode = MSYNTH_FLY_SPOT;
TaskComplete();
break;
}
case TASK_CSCANNER_SET_FLY_PHOTO:
{
m_nFlyMode = MSYNTH_FLY_PHOTO;
m_bPhotoTaken = false;
// Leave spotlight off for a while
m_fNextSpotlightTime = gpGlobals->curtime + 2.0;
TaskComplete();
break;
}
case TASK_CSCANNER_PHOTOGRAPH:
{
TakePhoto();
SetWait(0.1);
break;
}
case TASK_CSCANNER_ATTACK_PRE_FLASH:
{
if (IsStriderScout())
{
Vector vecScare = GetEnemy()->EarPosition();
Vector vecDir = WorldSpaceCenter() - vecScare;
VectorNormalize(vecDir);
vecScare += vecDir * 64.0f;
CSoundEnt::InsertSound(SOUND_DANGER, vecScare, 256, 1.0, this);
}
DevMsg("Msynth prebeam CGmuzzleflash \n");
DispatchParticleEffect("CombineGuard_Muzzle_Flash", PATTACH_POINT_FOLLOW, this, "light", false);
if (m_pEyeFlash)
{
// If off turn on, if on turn off
if (m_pEyeFlash->GetBrightness() == 0)
{
m_pEyeFlash->SetScale(0.5);
m_pEyeFlash->SetBrightness(255);
m_pEyeFlash->SetColor(255, 0, 0);
}
else
{
m_pEyeFlash->SetBrightness(0);
}
}
else
{
TaskFail("No Flash TASK_CSCANNER_ATTACK_PRE_FLASH");
}
AttackPreFlash();
// Flash red for a while
SetWait(1.0f);
break;
}
case TASK_CSCANNER_ATTACK_FLASH:
{
AttackFlash();
// Blinding occurs slightly later
SetWait(0.05);
break;
}
// Override to go to inspect target position whether or not is an entity
case TASK_GET_PATH_TO_TARGET:
{
if (!HaveInspectTarget())
{
TaskFail(FAIL_NO_TARGET);
}
else if (GetHintNode())
{
Vector vNodePos;
GetHintNode()->GetPosition(this, &vNodePos);
GetNavigator()->SetGoal(vNodePos);
}
else
{
AI_NavGoal_t goal((const Vector &)InspectTargetPosition());
goal.pTarget = GetTarget();
GetNavigator()->SetGoal(goal);
}
break;
}
default:
BaseClass::StartTask(pTask);
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
/*
char *CNPC_MSynth::GetScannerSoundPrefix(void)
{
if (m_bIsClawScanner)
return "NPC_SScanner";
return "NPC_CScanner";
}
*/
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
float CNPC_MSynth::MinGroundDist(void)
{
if (m_nFlyMode == MSYNTH_FLY_SPOT && !GetHintNode())
{
return MSYNTH_SPOTLIGHT_FLY_HEIGHT;
}
return MSYNTH_NOSPOTLIGHT_FLY_HEIGHT;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::AdjustScannerVelocity(void)
{
if (m_bIsClawScanner)
{
m_vCurrentVelocity *= (1 + sin((gpGlobals->curtime + m_flFlyNoiseBase) * 2.5f) * .1);
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : flInterval -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_MSynth::OverrideMove(float flInterval)
{
// ----------------------------------------------
// If dive bombing
// ----------------------------------------------
if (m_nFlyMode == MSYNTH_FLY_DIVE)
{
MoveToDivebomb(flInterval);
}
else
{
Vector vMoveTargetPos(0, 0, 0);
CBaseEntity *pMoveTarget = NULL;
// The original line of code was, due to the accidental use of '|' instead of
// '&', always true. Replacing with 'true' to suppress the warning without changing
// the (long-standing) behavior.
if (true) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) )
{
// Select move target
if (GetTarget() != NULL)
{
pMoveTarget = GetTarget();
}
else if (GetEnemy() != NULL)
{
pMoveTarget = GetEnemy();
}
// Select move target position
if (HaveInspectTarget())
{
vMoveTargetPos = InspectTargetPosition();
}
else if (GetEnemy() != NULL)
{
vMoveTargetPos = GetEnemy()->GetAbsOrigin();
}
}
else
{
vMoveTargetPos = GetNavigator()->GetCurWaypointPos();
}
ClearCondition(COND_SCANNER_FLY_CLEAR);
ClearCondition(COND_SCANNER_FLY_BLOCKED);
// See if we can fly there directly
if (pMoveTarget || HaveInspectTarget())
{
trace_t tr;
AI_TraceHull(GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
//AI_TraceHull(GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr); // bj test
float fTargetDist = (1.0f - tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
if ((tr.m_pEnt == pMoveTarget) || (fTargetDist < 50))
{
if (g_debug_mortarsynth.GetBool())
{
NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0, 255, 0, true, 0);
NDebugOverlay::Cross3D(tr.endpos, Vector(-5, -5, -5), Vector(5, 5, 5), 0, 255, 0, true, 0.1);
}
SetCondition(COND_SCANNER_FLY_CLEAR);
}
else
{
//HANDY DEBUG TOOL
if (g_debug_mortarsynth.GetBool())
{
NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255, 0, 0, true, 0);
NDebugOverlay::Cross3D(tr.endpos, Vector(-5, -5, -5), Vector(5, 5, 5), 255, 0, 0, true, 0.1);
}
SetCondition(COND_SCANNER_FLY_BLOCKED);
}
}
// If I have a route, keep it updated and move toward target
if (GetNavigator()->IsGoalActive())
{
if (OverridePathMove(pMoveTarget, flInterval))
{
BlendPhyscannonLaunchSpeed();
return true;
}
}
else if (m_nFlyMode == MSYNTH_FLY_SPOT)
{
MoveToSpotlight(flInterval);
}
// If photographing
else if (m_nFlyMode == MSYNTH_FLY_PHOTO)
{
MoveToPhotograph(flInterval);
}
else if (m_nFlyMode == MSYNTH_FLY_FOLLOW)
{
MoveToSpotlight(flInterval);
}
// ----------------------------------------------
// If attacking
// ----------------------------------------------
else if (m_nFlyMode == MSYNTH_FLY_ATTACK)
{
if (m_hSpotlight)
{
SpotlightDestroy();
}
MoveToAttack(flInterval);
}
// -----------------------------------------------------------------
// If I don't have a route, just decelerate
// -----------------------------------------------------------------
else if (!GetNavigator()->IsGoalActive())
{
float myDecay = 9.5;
Decelerate(flInterval, myDecay);
}
}
MoveExecute_Alive(flInterval);
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Accelerates toward a given position.
// Input : flInterval - Time interval over which to move.
// vecMoveTarget - Position to move toward.
//-----------------------------------------------------------------------------
void CNPC_MSynth::MoveToTarget(float flInterval, const Vector &vecMoveTarget)
{
// Don't move if stalling
if (m_flEngineStallTime > gpGlobals->curtime)
return;
// Look at our inspection target if we have one
if (GetEnemy() != NULL)
{
// Otherwise at our enemy
TurnHeadToTarget(flInterval, GetEnemy()->EyePosition());
}
else if (HaveInspectTarget())
{
TurnHeadToTarget(flInterval, InspectTargetPosition());
}
else
{
// Otherwise face our motion direction
TurnHeadToTarget(flInterval, vecMoveTarget);
}
// -------------------------------------
// Move towards our target
// -------------------------------------
float myAccel;
float myZAccel = 400.0f;
float myDecay = 0.15f;
Vector vecCurrentDir;
// Get the relationship between my current velocity and the way I want to be going.
vecCurrentDir = GetCurrentVelocity();
VectorNormalize(vecCurrentDir);
Vector targetDir = vecMoveTarget - GetAbsOrigin();
float flDist = VectorNormalize(targetDir);
float flDot;
flDot = DotProduct(targetDir, vecCurrentDir);
if (flDot > 0.25)
{
// If my target is in front of me, my flight model is a bit more accurate.
myAccel = 250;
}
else
{
// Have a harder time correcting my course if I'm currently flying away from my target.
myAccel = 128;
}
if (myAccel > flDist / flInterval)
{
myAccel = flDist / flInterval;
}
if (myZAccel > flDist / flInterval)
{
myZAccel = flDist / flInterval;
}
MoveInDirection(flInterval, targetDir, myAccel, myZAccel, myDecay);
// calc relative banking targets
Vector forward, right, up;
GetVectors(&forward, &right, &up);
m_vCurrentBanking.x = targetDir.x;
m_vCurrentBanking.z = 120.0f * DotProduct(right, targetDir);
m_vCurrentBanking.y = 0;
float speedPerc = SimpleSplineRemapVal(GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f);
speedPerc = clamp(speedPerc, 0.0f, 1.0f);
m_vCurrentBanking *= speedPerc;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : flInterval -
//-----------------------------------------------------------------------------
void CNPC_MSynth::MoveToSpotlight(float flInterval)
{
if (flInterval <= 0)
return;
Vector vTargetPos;
if (HaveInspectTarget())
{
vTargetPos = InspectTargetPosition();
}
else if (GetEnemy() != NULL)
{
vTargetPos = GetEnemyLKP();
}
else
{
return;
}
//float flDesiredDist = SCANNER_SPOTLIGHT_NEAR_DIST + ( ( SCANNER_SPOTLIGHT_FAR_DIST - SCANNER_SPOTLIGHT_NEAR_DIST ) / 2 );
float flIdealHeightDiff = MSYNTH_SPOTLIGHT_NEAR_DIST;
if (IsEnemyPlayerInSuit())
{
flIdealHeightDiff *= 0.5;
}
Vector idealPos = IdealGoalForMovement(vTargetPos, GetAbsOrigin(), GetGoalDistance(), flIdealHeightDiff);
MoveToTarget(flInterval, idealPos);
//TODO: Re-implement?
/*
// ------------------------------------------------
// Also keep my distance from other squad members
// unless I'm inspecting
// ------------------------------------------------
if (m_pSquad &&
gpGlobals->curtime > m_fInspectEndTime)
{
CBaseEntity* pNearest = m_pSquad->NearestSquadMember(this);
if (pNearest)
{
Vector vNearestDir = (pNearest->GetLocalOrigin() - GetLocalOrigin());
if (vNearestDir.Length() < SCANNER_SQUAD_FLY_DIST)
{
vNearestDir = pNearest->GetLocalOrigin() - GetLocalOrigin();
VectorNormalize(vNearestDir);
vFlyDirection -= 0.5*vNearestDir;
}
}
}
// ---------------------------------------------------------
// Add evasion if I have taken damage recently
// ---------------------------------------------------------
if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime)
{
vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer());
}
*/
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : float
//-----------------------------------------------------------------------------
float CNPC_MSynth::GetGoalDistance(void)
{
if (m_flGoalOverrideDistance != 0.0f)
return m_flGoalOverrideDistance;
switch (m_nFlyMode)
{
case MSYNTH_FLY_PHOTO:
return (MSYNTH_PHOTO_NEAR_DIST + ((MSYNTH_PHOTO_FAR_DIST - MSYNTH_PHOTO_NEAR_DIST)));
break;
case MSYNTH_FLY_SPOT:
{
float goalDist = (MSYNTH_SPOTLIGHT_NEAR_DIST + ((MSYNTH_SPOTLIGHT_FAR_DIST - MSYNTH_SPOTLIGHT_NEAR_DIST)));
/*if (IsEnemyPlayerInSuit())
{
goalDist *= 0.5;
}*/
return goalDist;
}
break;
case MSYNTH_FLY_FOLLOW:
return (MSYNTH_FOLLOW_DIST);
break;
}
return BaseClass::GetGoalDistance();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_MSynth::MoveToPhotograph(float flInterval)
{
if (HaveInspectTarget() == false)
return;
//float flDesiredDist = SCANNER_PHOTO_NEAR_DIST + ( ( SCANNER_PHOTO_FAR_DIST - SCANNER_PHOTO_NEAR_DIST ) / 2 );
Vector idealPos = IdealGoalForMovement(InspectTargetPosition(), GetAbsOrigin(), GetGoalDistance(), 32.0f);
MoveToTarget(flInterval, idealPos);
//FIXME: Re-implement?
/*
// ------------------------------------------------
// Also keep my distance from other squad members
// unless I'm inspecting
// ------------------------------------------------
if (m_pSquad &&
gpGlobals->curtime > m_fInspectEndTime)
{
CBaseEntity* pNearest = m_pSquad->NearestSquadMember(this);
if (pNearest)
{
Vector vNearestDir = (pNearest->GetLocalOrigin() - GetLocalOrigin());
if (vNearestDir.Length() < SCANNER_SQUAD_FLY_DIST)
{
vNearestDir = pNearest->GetLocalOrigin() - GetLocalOrigin();
VectorNormalize(vNearestDir);
vFlyDirection -= 0.5*vNearestDir;
}
}
}
*/
}
//-----------------------------------------------------------------------------
// Purpose: This is a generic function (to be implemented by sub-classes) to
// handle specific interactions between different types of characters
// (For example the barnacle grabbing an NPC)
// Input : Constant for the type of interaction
// Output : true - if sub-class has a response for the interaction
// false - if sub-class has no response
//-----------------------------------------------------------------------------
bool CNPC_MSynth::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* pSourceEnt)
{
// TODO:: - doing this by just an interrupt contition would be a lot better!
if (interactionType == g_interactionScannerSupportEntity)
{
// Only accept help request if I'm not already busy
if (GetEnemy() == NULL && !HaveInspectTarget())
{
// Only accept if target is a reasonable distance away
CBaseEntity* pTarget = (CBaseEntity*)data;
float fTargetDist = (pTarget->GetLocalOrigin() - GetLocalOrigin()).Length();
if (fTargetDist < MSYNTH_SQUAD_HELP_DIST)
{
float fInspectTime = (((CNPC_MSynth*)pSourceEnt)->m_fInspectEndTime - gpGlobals->curtime);
SetInspectTargetToEnt(pTarget, fInspectTime);
if (random->RandomInt(0, 2) == 0)
{
SetSchedule(SCHED_CSCANNER_PHOTOGRAPH_HOVER);
}
else
{
SetSchedule(SCHED_CSCANNER_SPOTLIGHT_HOVER);
}
return true;
}
}
}
else if (interactionType == g_interactionScannerSupportPosition)
{
// Only accept help request if I'm not already busy
if (GetEnemy() == NULL && !HaveInspectTarget())
{
// Only accept if target is a reasonable distance away
Vector vInspectPos;
vInspectPos.x = ((Vector *)data)->x;
vInspectPos.y = ((Vector *)data)->y;
vInspectPos.z = ((Vector *)data)->z;
float fTargetDist = (vInspectPos - GetLocalOrigin()).Length();
if (fTargetDist < MSYNTH_SQUAD_HELP_DIST)
{
float fInspectTime = (((CNPC_MSynth*)pSourceEnt)->m_fInspectEndTime - gpGlobals->curtime);
SetInspectTargetToPos(vInspectPos, fInspectTime);
if (random->RandomInt(0, 2) == 0)
{
SetSchedule(SCHED_CSCANNER_PHOTOGRAPH_HOVER);
}
else
{
SetSchedule(SCHED_CSCANNER_SPOTLIGHT_HOVER);
}
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputDisableSpotlight(inputdata_t &inputdata)
{
m_bNoLight = true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : float
//-----------------------------------------------------------------------------
float CNPC_MSynth::GetHeadTurnRate(void)
{
if (GetEnemy())
return 800.0f;
if (HaveInspectTarget())
return 500.0f;
return BaseClass::GetHeadTurnRate();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputSetFollowTarget(inputdata_t &inputdata)
{
InspectTarget(inputdata, MSYNTH_FLY_FOLLOW);
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CNPC_MSynth::InputClearFollowTarget(inputdata_t &inputdata)
{
SetInspectTargetToEnt(NULL, 0);
m_nFlyMode = MSYNTH_FLY_PATROL;
}
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
AI_BEGIN_CUSTOM_NPC(npc_mortarsynth, CNPC_MSynth) // BJ npc_cscanner name fix
DECLARE_TASK(TASK_CSCANNER_SET_FLY_PHOTO)
DECLARE_TASK(TASK_CSCANNER_SET_FLY_SPOT)
DECLARE_TASK(TASK_CSCANNER_PHOTOGRAPH)
DECLARE_TASK(TASK_CSCANNER_ATTACK_PRE_FLASH)
DECLARE_TASK(TASK_CSCANNER_ATTACK_FLASH)
DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_ON)
DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_WAIT)
DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_OFF)
DECLARE_TASK(TASK_CSCANNER_CLEAR_INSPECT_TARGET)
DECLARE_TASK(TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET)
DECLARE_CONDITION(COND_CSCANNER_HAVE_INSPECT_TARGET)
DECLARE_CONDITION(COND_CSCANNER_INSPECT_DONE)
DECLARE_CONDITION(COND_CSCANNER_CAN_PHOTOGRAPH)
DECLARE_CONDITION(COND_CSCANNER_SPOT_ON_TARGET)
DECLARE_ACTIVITY(ACT_SCANNER_SMALL_FLINCH_ALERT)
DECLARE_ACTIVITY(ACT_SCANNER_SMALL_FLINCH_COMBAT)
DECLARE_ACTIVITY(ACT_SCANNER_INSPECT)
DECLARE_ACTIVITY(ACT_SCANNER_WALK_ALERT)
DECLARE_ACTIVITY(ACT_SCANNER_WALK_COMBAT)
DECLARE_ACTIVITY(ACT_SCANNER_FLARE)
DECLARE_ACTIVITY(ACT_SCANNER_RETRACT)
DECLARE_ACTIVITY(ACT_SCANNER_FLARE_PRONGS)
DECLARE_ACTIVITY(ACT_SCANNER_RETRACT_PRONGS)
DECLARE_ACTIVITY(ACT_SCANNER_FLARE_START)
DECLARE_ANIMEVENT(AE_MSYNTH_CLOSED)
DECLARE_INTERACTION(g_interactionScannerInspect)
DECLARE_INTERACTION(g_interactionScannerInspectBegin)
DECLARE_INTERACTION(g_interactionScannerInspectDone)
DECLARE_INTERACTION(g_interactionScannerInspectHandsUp)
DECLARE_INTERACTION(g_interactionScannerInspectShowArmband)
DECLARE_INTERACTION(g_interactionScannerSupportEntity)
DECLARE_INTERACTION(g_interactionScannerSupportPosition)
//=========================================================
// > SCHED_CSCANNER_PATROL
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_PATROL,
" Tasks"
" TASK_CSCANNER_CLEAR_INSPECT_TARGET 0"
" TASK_SCANNER_SET_FLY_PATROL 0"
" TASK_SET_TOLERANCE_DISTANCE 32"
" TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck
" TASK_GET_PATH_TO_RANDOM_NODE 2000"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_GIVE_WAY"
" COND_NEW_ENEMY"
" COND_SEE_ENEMY"
" COND_SEE_FEAR"
" COND_HEAR_COMBAT"
" COND_HEAR_DANGER"
" COND_HEAR_PLAYER"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
" COND_PROVOKED"
" COND_CSCANNER_HAVE_INSPECT_TARGET"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_SPOTLIGHT_HOVER
//
// Hover above target entity, trying to get spotlight
// on my target
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_SPOTLIGHT_HOVER,
" Tasks"
" TASK_CSCANNER_SET_FLY_SPOT 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_WALK "
" TASK_WAIT 1"
""
" Interrupts"
" COND_CSCANNER_SPOT_ON_TARGET"
" COND_CSCANNER_INSPECT_DONE"
" COND_SCANNER_FLY_BLOCKED"
" COND_NEW_ENEMY"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS
//
// Inspect a position once spotlight is on it
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS,
" Tasks"
" TASK_CSCANNER_SET_FLY_SPOT 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_SCANNER_INSPECT"
" TASK_SPEAK_SENTENCE 3" // Curious sound
" TASK_WAIT 5"
" TASK_CSCANNER_CLEAR_INSPECT_TARGET 0"
""
" Interrupts"
" COND_CSCANNER_INSPECT_DONE"
" COND_HEAR_DANGER"
" COND_HEAR_COMBAT"
" COND_NEW_ENEMY"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT
//
// Inspect a citizen once spotlight is on it
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT,
" Tasks"
" TASK_CSCANNER_SET_FLY_SPOT 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_SCANNER_INSPECT"
" TASK_SPEAK_SENTENCE 0" // Stop!
" TASK_WAIT 1"
" TASK_CSCANNER_SPOT_INSPECT_ON 0"
" TASK_WAIT 2"
" TASK_SPEAK_SENTENCE 1" // Hands on head or Show Armband!
" TASK_WAIT 1"
" TASK_CSCANNER_SPOT_INSPECT_WAIT 0"
" TASK_WAIT 5"
" TASK_SPEAK_SENTENCE 2" // Free to go!
" TASK_WAIT 1"
" TASK_CSCANNER_SPOT_INSPECT_OFF 0"
" TASK_CSCANNER_CLEAR_INSPECT_TARGET 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_PHOTOGRAPH_HOVER
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_PHOTOGRAPH_HOVER,
" Tasks"
" TASK_CSCANNER_SET_FLY_PHOTO 0"
" TASK_WAIT 2"
""
" Interrupts"
" COND_CSCANNER_INSPECT_DONE"
" COND_CSCANNER_CAN_PHOTOGRAPH"
" COND_SCANNER_FLY_BLOCKED"
" COND_NEW_ENEMY"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_PHOTOGRAPH
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_PHOTOGRAPH,
" Tasks"
" TASK_CSCANNER_SET_FLY_PHOTO 0"
" TASK_CSCANNER_PHOTOGRAPH 0"
""
" Interrupts"
" COND_CSCANNER_INSPECT_DONE"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_ATTACK_FLASH
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_ATTACK_FLASH,
" Tasks"
" TASK_SCANNER_SET_FLY_ATTACK 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
" TASK_CSCANNER_ATTACK_PRE_FLASH 0 "
" TASK_CSCANNER_ATTACK_FLASH 0"
" TASK_WAIT 0.5"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
//=========================================================
// > SCHED_CSCANNER_MOVE_TO_INSPECT
//=========================================================
DEFINE_SCHEDULE
(
SCHED_CSCANNER_MOVE_TO_INSPECT,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL"
" TASK_SET_TOLERANCE_DISTANCE 128"
" TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_SCANNER_FLY_CLEAR"
" COND_NEW_ENEMY"
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
)
AI_END_CUSTOM_NPC()
//-----------------------------------------------------------------------------
// Claw Scanner
//
// Scanner that always spawns as a claw scanner
//-----------------------------------------------------------------------------
class CNPC_ClawMSynth : public CNPC_MSynth
{
DECLARE_CLASS(CNPC_ClawMSynth, CNPC_MSynth);
public:
CNPC_ClawMSynth();
DECLARE_DATADESC();
};
BEGIN_DATADESC(CNPC_ClawMSynth)
END_DATADESC()
LINK_ENTITY_TO_CLASS(npc_clawmsynth, CNPC_ClawMSynth);
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CNPC_ClawMSynth::CNPC_ClawMSynth()
{
// override our superclass's setting
BecomeClawScanner();
}