mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-01-05 06:10:21 +03:00
1919 lines
55 KiB
C++
1919 lines
55 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Combine Alien Synth enemy
|
|
// 9 september 2021
|
|
//=============================================================================
|
|
|
|
#include "cbase.h"
|
|
#include "npcevent.h"
|
|
#include "ai_basenpc_physicsflyer.h"
|
|
#include "weapon_physcannon.h"
|
|
#include "hl2_player.h"
|
|
#include "npc_basemortarsynth.h"
|
|
#include "IEffects.h"
|
|
#include "explode.h"
|
|
#include "ai_route.h"
|
|
#include "gib.h" //added for dive bombmode explode gibs
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
ConVar g_debug_basemortarsynth("g_debug_basemortarsynth", "0", FCVAR_CHEAT);
|
|
|
|
BEGIN_DATADESC(CNPC_BaseMSynth)
|
|
DEFINE_EMBEDDED(m_KilledInfo),
|
|
DEFINE_SOUNDPATCH(m_pEngineSound),
|
|
|
|
DEFINE_FIELD(m_flFlyNoiseBase, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_flEngineStallTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_fNextFlySoundTime, FIELD_TIME),
|
|
DEFINE_FIELD(m_nFlyMode, FIELD_INTEGER),
|
|
|
|
DEFINE_FIELD(m_vecDiveBombDirection, FIELD_VECTOR),
|
|
DEFINE_FIELD(m_flDiveBombRollForce, FIELD_FLOAT),
|
|
|
|
// Physics Influence
|
|
DEFINE_FIELD(m_hPhysicsAttacker, FIELD_EHANDLE),
|
|
DEFINE_FIELD(m_flLastPhysicsInfluenceTime, FIELD_TIME),
|
|
|
|
DEFINE_FIELD(m_flGoalOverrideDistance, FIELD_FLOAT),
|
|
|
|
DEFINE_FIELD(m_flAttackNearDist, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_flAttackFarDist, FIELD_FLOAT),
|
|
DEFINE_FIELD(m_flAttackRange, FIELD_FLOAT),
|
|
|
|
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_bHasSpoken, FIELD_BOOLEAN ),
|
|
|
|
DEFINE_FIELD(m_pSmokeTrail, FIELD_CLASSPTR),
|
|
|
|
DEFINE_INPUTFUNC(FIELD_FLOAT, "SetDistanceOverride", InputSetDistanceOverride),
|
|
DEFINE_INPUTFUNC(FIELD_INTEGER, "SetFlightSpeed", InputSetFlightSpeed),
|
|
|
|
DEFINE_THINKFUNC(DiveBombSoundThink),
|
|
END_DATADESC()
|
|
|
|
ConVar sk_mortarsynth_bombmode_damage("sk_mortarsynth_bombmode_damage", "0");
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Think contexts
|
|
//-----------------------------------------------------------------------------
|
|
static const char *s_pDiveBombSoundThinkContextMsynth = "DiveBombSoundThinkContext";
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CNPC_BaseMSynth::CNPC_BaseMSynth()
|
|
{
|
|
#ifdef _DEBUG
|
|
m_vCurrentBanking.Init();
|
|
#endif
|
|
m_pEngineSound = NULL;
|
|
m_bHasSpoken = false;
|
|
|
|
m_flAttackNearDist = MSYNTH_ATTACK_NEAR_DIST;
|
|
m_flAttackFarDist = MSYNTH_ATTACK_FAR_DIST;
|
|
m_flAttackRange = MSYNTH_ATTACK_RANGE;
|
|
|
|
|
|
ReadDataFromFile(filesystem, this);
|
|
}
|
|
|
|
void CNPC_BaseMSynth::ReadDataFromFile(IFileSystem* filesystem, CBaseEntity *pCaller)
|
|
{
|
|
KeyValues *pkvProperties = new KeyValues("npc_mortarsynth");
|
|
//DevMsg("ReadDataFromFile: npc_mortarsynth.txt \n");
|
|
char ss[256];
|
|
Q_snprintf(ss, sizeof(ss), "scripts/npc/%s.txt", "npc_mortarsynth");
|
|
if (pkvProperties->LoadFromFile(filesystem, "scripts/npc/npc_mortarsynth.txt", "MOD"))
|
|
{
|
|
//DevMsg("LoadFromFile: npc_mortarsynth.txt \n");
|
|
if (pkvProperties)
|
|
{
|
|
//DevMsg("Loaded successfully\n");
|
|
|
|
KeyValues *pMain = pkvProperties->FindKey("Particles");
|
|
if (pMain)
|
|
{
|
|
if (strcmp("None", pMain->GetString("MSYNTH_PARTICLE_TRAIL", "None")) == 0)
|
|
Q_strncpy(MSYNTH_PARTICLE_TRAIL, "", sizeof(MSYNTH_PARTICLE_TRAIL));
|
|
else
|
|
{
|
|
Q_strncpy(MSYNTH_PARTICLE_TRAIL, pMain->GetString("MSYNTH_PARTICLE_TRAIL"), sizeof(MSYNTH_PARTICLE_TRAIL));
|
|
PrecacheParticleSystem(pMain->GetString("MSYNTH_PARTICLE_TRAIL", ""));
|
|
}
|
|
|
|
if (strcmp("None", pMain->GetString("MSYNTH_PARTICLE_GLOW", "None")) == 0)
|
|
Q_strncpy(MSYNTH_PARTICLE_GLOW, "", sizeof(MSYNTH_PARTICLE_GLOW));
|
|
else
|
|
{
|
|
Q_strncpy(MSYNTH_PARTICLE_GLOW, pMain->GetString("MSYNTH_PARTICLE_GLOW"), sizeof(MSYNTH_PARTICLE_GLOW));
|
|
PrecacheParticleSystem(pMain->GetString("MSYNTH_PARTICLE_GLOW", ""));
|
|
}
|
|
|
|
if (strcmp("None", pMain->GetString("MSYNTH_PARTICLE_EXPLOSION", "None")) == 0)
|
|
Q_strncpy(MSYNTH_PARTICLE_EXPLOSION, "", sizeof(MSYNTH_PARTICLE_EXPLOSION));
|
|
else
|
|
{
|
|
Q_strncpy(MSYNTH_PARTICLE_EXPLOSION, pMain->GetString("MSYNTH_PARTICLE_EXPLOSION"), sizeof(MSYNTH_PARTICLE_EXPLOSION));
|
|
PrecacheParticleSystem(pMain->GetString("MSYNTH_PARTICLE_EXPLOSION", ""));
|
|
}
|
|
|
|
//DevMsg("%s \n", MSYNTH_PARTICLE_TRAIL);
|
|
//DevMsg("%s \n", MSYNTH_PARTICLE_GLOW);
|
|
//DevMsg("%s \n", MSYNTH_PARTICLE_EXPLOSION);
|
|
}
|
|
}
|
|
}
|
|
pkvProperties->deleteThis();
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::Spawn(void)
|
|
{
|
|
#ifdef _XBOX
|
|
// Always fade the corpse
|
|
AddSpawnFlags(SF_NPC_FADE_CORPSE);
|
|
AddEffects(EF_NOSHADOW);
|
|
#endif // _XBOX
|
|
|
|
SetHullType(HULL_TINY_CENTERED);
|
|
SetHullSizeNormal();
|
|
|
|
SetSolid(SOLID_BBOX);
|
|
AddSolidFlags(FSOLID_NOT_STANDABLE);
|
|
|
|
SetMoveType(MOVETYPE_VPHYSICS);
|
|
|
|
m_bloodColor = DONT_BLEED;
|
|
SetViewOffset(Vector(0, 0, 10)); // Position of the eyes relative to NPC's origin.
|
|
m_flFieldOfView = 0.2;
|
|
m_NPCState = NPC_STATE_NONE;
|
|
|
|
SetNavType(NAV_FLY);
|
|
|
|
AddFlag(FL_FLY);
|
|
|
|
// This entity cannot be dissolved by the combine balls,
|
|
// nor does it get killed by the mega physcannon.
|
|
AddEFlags(EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL);
|
|
|
|
m_flGoalOverrideDistance = 0.0f;
|
|
|
|
m_nFlyMode = MSYNTH_FLY_PATROL;
|
|
AngleVectors(GetLocalAngles(), &m_vCurrentBanking);
|
|
m_fHeadYaw = 0;
|
|
m_pSmokeTrail = NULL;
|
|
|
|
SetCurrentVelocity(vec3_origin);
|
|
|
|
// Noise modifier
|
|
Vector bobAmount;
|
|
bobAmount.x = random->RandomFloat(-2.0f, 2.0f);
|
|
bobAmount.y = random->RandomFloat(-2.0f, 2.0f);
|
|
bobAmount.z = random->RandomFloat(2.0f, 4.0f);
|
|
if (random->RandomInt(0, 1))
|
|
{
|
|
bobAmount.z *= -1.0f;
|
|
}
|
|
SetNoiseMod(bobAmount);
|
|
|
|
// set flight speed
|
|
m_flSpeed = GetMaxSpeed();
|
|
|
|
// --------------------------------------------
|
|
|
|
CapabilitiesAdd(bits_CAP_MOVE_FLY | bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_SKIP_NAV_GROUND_CHECK);
|
|
|
|
NPCInit();
|
|
|
|
m_flFlyNoiseBase = random->RandomFloat(0, M_PI);
|
|
|
|
m_flNextAttack = gpGlobals->curtime;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::UpdateEfficiency(bool bInPVS)
|
|
{
|
|
SetEfficiency((GetSleepState() != AISS_AWAKE) ? AIE_DORMANT : AIE_NORMAL);
|
|
SetMoveEfficiency(AIME_NORMAL);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_BaseMSynth::GetAutoAimRadius()
|
|
{
|
|
if (g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE)
|
|
{
|
|
return 24.0f;
|
|
}
|
|
|
|
return 12.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called just before we are deleted.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::UpdateOnRemove(void)
|
|
{
|
|
// Stop combat loops if I'm alive. If I'm dead, the die sound will already have stopped it.
|
|
if (IsAlive() && m_bHasSpoken)
|
|
{
|
|
SentenceStop();
|
|
}
|
|
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the appropriate next schedule based on current condition
|
|
// bits.
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_BaseMSynth::SelectSchedule(void)
|
|
{
|
|
// ----------------------------------------------------
|
|
// 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 * m_iMaxHealth / 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())
|
|
{
|
|
// 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)
|
|
{
|
|
if (HasCondition(COND_CAN_MELEE_ATTACK1))
|
|
return SCHED_SCANNER_ATTACK;
|
|
}
|
|
|
|
// Otherwise fly in low for attack
|
|
return SCHED_SCANNER_ATTACK_HOVER;
|
|
}
|
|
|
|
// Default to patrolling around
|
|
return SCHED_SCANNER_PATROL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::OnScheduleChange(void)
|
|
{
|
|
m_flSpeed = GetMaxSpeed();
|
|
|
|
BaseClass::OnScheduleChange();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For innate melee attack
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_BaseMSynth::MeleeAttack1Conditions(float flDot, float flDist)
|
|
{
|
|
if (GetEnemy() == NULL)
|
|
{
|
|
return COND_NONE;
|
|
}
|
|
|
|
// Check too far to attack with 2D distance
|
|
float vEnemyDist2D = (GetEnemy()->GetLocalOrigin() - GetLocalOrigin()).Length2D();
|
|
|
|
if (m_flNextAttack > gpGlobals->curtime)
|
|
{
|
|
return COND_NONE;
|
|
}
|
|
else if (vEnemyDist2D > m_flAttackRange)
|
|
{
|
|
return COND_TOO_FAR_TO_ATTACK;
|
|
}
|
|
else if (flDot < 0.7)
|
|
{
|
|
return COND_NOT_FACING_ATTACK;
|
|
}
|
|
return COND_CAN_MELEE_ATTACK1;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : eOldState -
|
|
// eNewState -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::OnStateChange(NPC_STATE eOldState, NPC_STATE eNewState)
|
|
{
|
|
if ((eNewState == NPC_STATE_ALERT) || (eNewState == NPC_STATE_COMBAT))
|
|
{
|
|
SetPoseParameter(m_nPoseFlare, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
SetPoseParameter(m_nPoseFlare, 0);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pTask -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::StartTask(const Task_t *pTask)
|
|
{
|
|
switch (pTask->iTask)
|
|
{
|
|
case TASK_SCANNER_SET_FLY_PATROL:
|
|
{
|
|
// Fly in patrol mode and clear any
|
|
// remaining target entity
|
|
m_nFlyMode = MSYNTH_FLY_PATROL;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SCANNER_SET_FLY_CHASE:
|
|
{
|
|
m_nFlyMode = MSYNTH_FLY_CHASE;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_SCANNER_SET_FLY_ATTACK:
|
|
{
|
|
m_nFlyMode = MSYNTH_FLY_ATTACK;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
|
|
case TASK_SCANNER_SET_FLY_DIVE:
|
|
{
|
|
// Pick a direction to divebomb.
|
|
if (GetEnemy() != NULL)
|
|
{
|
|
// Fly towards my enemy
|
|
Vector vEnemyPos = GetEnemyLKP();
|
|
m_vecDiveBombDirection = vEnemyPos - GetLocalOrigin();
|
|
}
|
|
else
|
|
{
|
|
// Pick a random forward and down direction.
|
|
Vector forward;
|
|
GetVectors(&forward, NULL, NULL);
|
|
m_vecDiveBombDirection = forward + Vector(random->RandomFloat(-10, 10), random->RandomFloat(-10, 10), random->RandomFloat(-20, -10));
|
|
}
|
|
VectorNormalize(m_vecDiveBombDirection);
|
|
|
|
// Calculate a roll force.
|
|
m_flDiveBombRollForce = random->RandomFloat(20.0, 420.0);
|
|
if (random->RandomInt(0, 1))
|
|
{
|
|
m_flDiveBombRollForce *= -1;
|
|
}
|
|
|
|
DiveBombSoundThink();
|
|
|
|
m_nFlyMode = MSYNTH_FLY_DIVE;
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
BaseClass::StartTask(pTask);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Override to split in two when attacked
|
|
//------------------------------------------------------------------------------
|
|
int CNPC_BaseMSynth::OnTakeDamage_Alive(const CTakeDamageInfo &info)
|
|
{
|
|
// Start smoking when we're nearly dead
|
|
if (m_iHealth < (m_iMaxHealth - (m_iMaxHealth / 4)))
|
|
{
|
|
StartSmokeTrail();
|
|
}
|
|
|
|
return (BaseClass::OnTakeDamage_Alive(info));
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Override to split in two when attacked
|
|
//------------------------------------------------------------------------------
|
|
int CNPC_BaseMSynth::OnTakeDamage_Dying(const CTakeDamageInfo &info)
|
|
{
|
|
// do the damage
|
|
m_iHealth -= info.GetDamage();
|
|
|
|
if (m_iHealth < -40)
|
|
{
|
|
Gib();
|
|
return 1;
|
|
}
|
|
|
|
return VPhysicsTakeDamage(info);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::TraceAttack(const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator)
|
|
{
|
|
if (info.GetDamageType() & DMG_BULLET)
|
|
{
|
|
g_pEffects->Ricochet(ptr->endpos, ptr->plane.normal);
|
|
}
|
|
|
|
BaseClass::TraceAttack(info, vecDir, ptr, pAccumulator);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Take damage from being thrown by a physcannon
|
|
//-----------------------------------------------------------------------------
|
|
#define SCANNER_SMASH_SPEED 250.0 // How fast a scanner must slam into something to take full damage
|
|
void CNPC_BaseMSynth::TakeDamageFromPhyscannon(CBasePlayer *pPlayer)
|
|
{
|
|
CTakeDamageInfo info;
|
|
info.SetDamageType(DMG_GENERIC);
|
|
info.SetInflictor(this);
|
|
info.SetAttacker(pPlayer);
|
|
info.SetDamagePosition(GetAbsOrigin());
|
|
info.SetDamageForce(Vector(1.0, 1.0, 1.0));
|
|
|
|
// Convert velocity into damage.
|
|
Vector vel;
|
|
VPhysicsGetObject()->GetVelocity(&vel, NULL);
|
|
float flSpeed = vel.Length();
|
|
|
|
float flFactor = flSpeed / SCANNER_SMASH_SPEED;
|
|
|
|
// Clamp. Don't inflict negative damage or massive damage!
|
|
flFactor = clamp(flFactor, 0.0f, 2.0f);
|
|
float flDamage = m_iMaxHealth * flFactor;
|
|
|
|
#if 0
|
|
Msg("Doing %f damage for %f speed!\n", flDamage, flSpeed);
|
|
#endif
|
|
|
|
info.SetDamage(flDamage);
|
|
TakeDamage(info);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Take damage from physics impacts
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::TakeDamageFromPhysicsImpact(int index, gamevcollisionevent_t *pEvent)
|
|
{
|
|
CBaseEntity *pHitEntity = pEvent->pEntities[!index];
|
|
|
|
// NOTE: Augment the normal impact energy scale here.
|
|
float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 5.0f;
|
|
|
|
// Scale by the mapmaker's energyscale
|
|
flDamageScale *= m_impactEnergyScale;
|
|
|
|
int damageType = 0;
|
|
float damage = CalculateDefaultPhysicsDamage(index, pEvent, flDamageScale, true, damageType);
|
|
if (damage == 0)
|
|
return;
|
|
|
|
Vector damagePos;
|
|
pEvent->pInternalData->GetContactPoint(damagePos);
|
|
Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
|
|
if (damageForce == vec3_origin)
|
|
{
|
|
// This can happen if this entity is motion disabled, and can't move.
|
|
// Use the velocity of the entity that hit us instead.
|
|
damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
|
|
}
|
|
|
|
// FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
|
|
PhysCallbackDamage(this, CTakeDamageInfo(pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType), *pEvent, index);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Is the scanner being held?
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_BaseMSynth::IsHeldByPhyscannon()
|
|
{
|
|
return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Physics impact
|
|
//------------------------------------------------------------------------------
|
|
#define SCANNER_SMASH_TIME 0.75 // How long after being thrown from a physcannon that a manhack is eligible to die from impact
|
|
void CNPC_BaseMSynth::VPhysicsCollision(int index, gamevcollisionevent_t *pEvent)
|
|
{
|
|
BaseClass::VPhysicsCollision(index, pEvent);
|
|
|
|
// Take no impact damage while being carried.
|
|
if (IsHeldByPhyscannon())
|
|
return;
|
|
|
|
CBasePlayer *pPlayer = HasPhysicsAttacker(SCANNER_SMASH_TIME);
|
|
if (pPlayer)
|
|
{
|
|
TakeDamageFromPhyscannon(pPlayer);
|
|
return;
|
|
}
|
|
|
|
// It also can take physics damage from things thrown by the player.
|
|
int otherIndex = !index;
|
|
CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex];
|
|
if (pHitEntity)
|
|
{
|
|
if (pHitEntity->HasPhysicsAttacker(0.5f))
|
|
{
|
|
// It can take physics damage from things thrown by the player.
|
|
TakeDamageFromPhysicsImpact(index, pEvent);
|
|
}
|
|
else if (FClassnameIs(pHitEntity, "prop_combine_ball"))
|
|
{
|
|
// It also can take physics damage from a combine ball.
|
|
TakeDamageFromPhysicsImpact(index, pEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::Gib(void)
|
|
{
|
|
if (IsMarkedForDeletion())
|
|
return;
|
|
|
|
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");*/
|
|
// Sparks
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
Vector sparkPos = GetAbsOrigin();
|
|
sparkPos.x += random->RandomFloat(-12, 12);
|
|
sparkPos.y += random->RandomFloat(-12, 12);
|
|
sparkPos.z += random->RandomFloat(-12, 12);
|
|
g_pEffects->Sparks(sparkPos);
|
|
}
|
|
|
|
// Light
|
|
CBroadcastRecipientFilter filter;
|
|
te->DynamicLight(filter, 0.0, &WorldSpaceCenter(), 255, 180, 100, 0, 100, 0.1, 0);
|
|
|
|
// Cover the gib spawn
|
|
ExplosionCreate(WorldSpaceCenter(), GetAbsAngles(), this, 64, 64, false, true);
|
|
|
|
// Turn off any smoke trail
|
|
if (m_pSmokeTrail)
|
|
{
|
|
m_pSmokeTrail->m_ParticleLifetime = 0;
|
|
UTIL_Remove(m_pSmokeTrail);
|
|
m_pSmokeTrail = NULL;
|
|
}
|
|
|
|
// FIXME: This is because we couldn't save/load the CTakeDamageInfo.
|
|
// because it's midnight before the teamwide playtest. Real solution
|
|
// is to add a datadesc to CTakeDamageInfo
|
|
if (m_KilledInfo.GetInflictor())
|
|
{
|
|
BaseClass::Event_Killed(m_KilledInfo);
|
|
}
|
|
|
|
UTIL_Remove(this);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPhysGunUser -
|
|
// bPunting -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::OnPhysGunPickup(CBasePlayer *pPhysGunUser, PhysGunPickup_t reason)
|
|
{
|
|
m_hPhysicsAttacker = pPhysGunUser;
|
|
m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
|
|
|
|
if (reason == PUNTED_BY_CANNON)
|
|
{
|
|
// There's about to be a massive change in velocity.
|
|
// Think immediately to handle changes in m_vCurrentVelocity;
|
|
SetNextThink(gpGlobals->curtime + 0.01f);
|
|
|
|
m_flEngineStallTime = gpGlobals->curtime + 2.0f;
|
|
ScannerEmitSound("DiveBomb");
|
|
}
|
|
else
|
|
{
|
|
SetCondition(COND_SCANNER_GRABBED_BY_PHYSCANNON);
|
|
ClearCondition(COND_SCANNER_RELEASED_FROM_PHYSCANNON);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *pPhysGunUser -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::OnPhysGunDrop(CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason)
|
|
{
|
|
m_hPhysicsAttacker = pPhysGunUser;
|
|
m_flLastPhysicsInfluenceTime = gpGlobals->curtime;
|
|
|
|
ClearCondition(COND_SCANNER_GRABBED_BY_PHYSCANNON);
|
|
SetCondition(COND_SCANNER_RELEASED_FROM_PHYSCANNON);
|
|
|
|
if (Reason == LAUNCHED_BY_CANNON)
|
|
{
|
|
m_flEngineStallTime = gpGlobals->curtime + 2.0f;
|
|
|
|
// There's about to be a massive change in velocity.
|
|
// Think immediately to handle changes in m_vCurrentVelocity;
|
|
SetNextThink(gpGlobals->curtime + 0.01f);
|
|
ScannerEmitSound("DiveBomb");
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Do we have a physics attacker?
|
|
//------------------------------------------------------------------------------
|
|
CBasePlayer *CNPC_BaseMSynth::HasPhysicsAttacker(float dt)
|
|
{
|
|
// If the player is holding me now, or I've been recently thrown
|
|
// then return a pointer to that player
|
|
if (IsHeldByPhyscannon() || (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime))
|
|
{
|
|
return m_hPhysicsAttacker;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::StopLoopingSounds(void)
|
|
{
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
controller.SoundDestroy(m_pEngineSound);
|
|
m_pEngineSound = NULL;
|
|
|
|
BaseClass::StopLoopingSounds();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pInflictor -
|
|
// pAttacker -
|
|
// flDamage -
|
|
// bitsDamageType -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::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;
|
|
|
|
// Interrupt whatever schedule I'm on
|
|
SetCondition(COND_SCHEDULE_DONE);
|
|
|
|
// If I have an enemy and I'm up high, do a dive bomb (unless dissolved)
|
|
if (GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false)
|
|
{
|
|
Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin();
|
|
if ((vecDelta.z > 120) && (vecDelta.Length() > 360))
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
Gib();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::AttackDivebomb(void)
|
|
{
|
|
ScannerEmitSound("DiveBomb");
|
|
|
|
m_takedamage = DAMAGE_NO;
|
|
|
|
StartSmokeTrail();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose: Checks to see if we hit anything while dive bombing.
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::AttackDivebombCollide(float flInterval)
|
|
{
|
|
//
|
|
// Trace forward to see if I hit anything
|
|
//
|
|
Vector checkPos = GetAbsOrigin() + (GetCurrentVelocity() * flInterval);
|
|
trace_t tr;
|
|
CBaseEntity* pHitEntity = NULL;
|
|
AI_TraceHull(GetAbsOrigin(), checkPos, GetHullMins(), GetHullMaxs(), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
if (tr.m_pEnt)
|
|
{
|
|
pHitEntity = tr.m_pEnt;
|
|
|
|
// Did I hit an entity that isn't another scanner?
|
|
if (pHitEntity && pHitEntity->Classify() != CLASS_SCANNER)
|
|
{
|
|
if (!pHitEntity->ClassMatches("item_battery"))
|
|
{
|
|
//if (!pHitEntity->IsWorld())
|
|
//{
|
|
//CTakeDamageInfo info(this, this, sk_mortarsynth_bombmode_damage.GetFloat(), DMG_CLUB);
|
|
//CalculateMeleeDamageForce(&info, (tr.endpos - tr.startpos), tr.endpos);
|
|
//pHitEntity->TakeDamage(info);
|
|
|
|
// BJ since it only take damage ON TOUCH player i changed to radius method
|
|
// BJ Not every time works, sometimes world hit rather than player so i moved under
|
|
|
|
//}
|
|
DevMsg("Msynth bombmode dmg \n");
|
|
RadiusDamage(CTakeDamageInfo(this, this, sk_mortarsynth_bombmode_damage.GetFloat(), DMG_BLAST), GetAbsOrigin(), 256, CLASS_COMBINE, NULL); // BJ Self Explode damage
|
|
Gib();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
// We've hit something so deflect our velocity based on the surface
|
|
// norm of what we've hit
|
|
if (flInterval > 0)
|
|
{
|
|
float moveLen = (1.0 - tr.fraction)*(GetAbsOrigin() - checkPos).Length();
|
|
Vector vBounceVel = moveLen*tr.plane.normal / flInterval;
|
|
|
|
// If I'm right over the ground don't push down
|
|
if (vBounceVel.z < 0)
|
|
{
|
|
float floorZ = GetFloorZ(GetAbsOrigin());
|
|
if (abs(GetAbsOrigin().z - floorZ) < 36)
|
|
{
|
|
vBounceVel.z = 0;
|
|
}
|
|
}
|
|
SetCurrentVelocity(GetCurrentVelocity() + vBounceVel);
|
|
}
|
|
CBaseCombatCharacter* pBCC = ToBaseCombatCharacter(pHitEntity);
|
|
|
|
if (pBCC)
|
|
{
|
|
// Spawn some extra blood where we hit
|
|
SpawnBlood(tr.endpos, g_vecAttackDir, pBCC->BloodColor(), sk_mortarsynth_bombmode_damage.GetFloat());
|
|
}
|
|
else
|
|
{
|
|
if (!(m_spawnflags & SF_NPC_GAG))
|
|
{
|
|
// <<TEMP>> need better sound here...
|
|
ScannerEmitSound("Shoot");
|
|
}
|
|
// For sparks we must trace a line in the direction of the surface norm
|
|
// that we hit.
|
|
checkPos = GetAbsOrigin() - (tr.plane.normal * 24);
|
|
|
|
AI_TraceLine(GetAbsOrigin(), checkPos, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
if (tr.fraction != 1.0)
|
|
{
|
|
g_pEffects->Sparks(tr.endpos);
|
|
|
|
CBroadcastRecipientFilter filter;
|
|
te->DynamicLight(filter, 0.0,
|
|
&GetAbsOrigin(), 255, 180, 100, 0, 50, 0.1, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::PlayFlySound(void)
|
|
{
|
|
if (IsMarkedForDeletion())
|
|
return;
|
|
|
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
|
|
|
|
//Setup the sound if we're not already
|
|
if (m_pEngineSound == NULL)
|
|
{
|
|
// Create the sound
|
|
CPASAttenuationFilter filter(this);
|
|
|
|
m_pEngineSound = controller.SoundCreate(filter, entindex(), CHAN_STATIC, GetEngineSound(), ATTN_NORM);
|
|
|
|
Assert(m_pEngineSound);
|
|
|
|
// Start the engine sound
|
|
controller.Play(m_pEngineSound, 0.0f, 100.0f);
|
|
controller.SoundChangeVolume(m_pEngineSound, 1.0f, 2.0f);
|
|
}
|
|
|
|
float speed = GetCurrentVelocity().Length();
|
|
float flVolume = 0.25f + (0.75f*(speed / GetMaxSpeed()));
|
|
int iPitch = MIN(255, 80 + (20 * (speed / GetMaxSpeed())));
|
|
|
|
//Update our pitch and volume based on our speed
|
|
controller.SoundChangePitch(m_pEngineSound, iPitch, 0.1f);
|
|
controller.SoundChangeVolume(m_pEngineSound, flVolume, 0.1f);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::ScannerEmitSound(const char *pszSoundName)
|
|
{
|
|
//CFmtStr snd;
|
|
//snd.sprintf("%s.%s", GetScannerSoundPrefix(), pszSoundName);
|
|
|
|
m_bHasSpoken = true;
|
|
|
|
//EmitSound(snd.Access());
|
|
EmitSound(pszSoundName);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::SpeakSentence(int sentenceType)
|
|
{
|
|
if (sentenceType == MSYNTH_SENTENCE_ATTENTION)
|
|
{
|
|
ScannerEmitSound(MSYNTH_SPEECH1_SOUND);
|
|
}
|
|
else if (sentenceType == MSYNTH_SENTENCE_HANDSUP)
|
|
{
|
|
ScannerEmitSound(MSYNTH_SPEECH2_SOUND);
|
|
}
|
|
else if (sentenceType == MSYNTH_SENTENCE_PROCEED)
|
|
{
|
|
ScannerEmitSound(MSYNTH_SPEECH3_SOUND);
|
|
}
|
|
else if (sentenceType == MSYNTH_SENTENCE_CURIOUS)
|
|
{
|
|
ScannerEmitSound(MSYNTH_SPEECH4_SOUND);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::InputSetFlightSpeed(inputdata_t &inputdata)
|
|
{
|
|
//FIXME: Currently unsupported
|
|
|
|
/*
|
|
m_flFlightSpeed = inputdata.value.Int();
|
|
m_bFlightSpeedOverridden = (m_flFlightSpeed > 0);
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::StartSmokeTrail(void)
|
|
{
|
|
if (m_pSmokeTrail != NULL)
|
|
return;
|
|
|
|
m_pSmokeTrail = SmokeTrail::CreateSmokeTrail();
|
|
|
|
if (m_pSmokeTrail)
|
|
{
|
|
m_pSmokeTrail->m_SpawnRate = 10;
|
|
m_pSmokeTrail->m_ParticleLifetime = 1;
|
|
m_pSmokeTrail->m_StartSize = 8;
|
|
m_pSmokeTrail->m_EndSize = 50;
|
|
m_pSmokeTrail->m_SpawnRadius = 10;
|
|
m_pSmokeTrail->m_MinSpeed = 15;
|
|
m_pSmokeTrail->m_MaxSpeed = 25;
|
|
|
|
m_pSmokeTrail->m_StartColor.Init(0.5f, 0.5f, 0.5f);
|
|
m_pSmokeTrail->m_EndColor.Init(0, 0, 0);
|
|
m_pSmokeTrail->SetLifetime(500.0f);
|
|
m_pSmokeTrail->FollowEntity(this);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::BlendPhyscannonLaunchSpeed()
|
|
{
|
|
// Blend out desired velocity when launched by the physcannon
|
|
if (!VPhysicsGetObject())
|
|
return;
|
|
|
|
if (HasPhysicsAttacker(SCANNER_SMASH_TIME) && !IsHeldByPhyscannon())
|
|
{
|
|
Vector vecCurrentVelocity;
|
|
VPhysicsGetObject()->GetVelocity(&vecCurrentVelocity, NULL);
|
|
float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / SCANNER_SMASH_TIME;
|
|
flLerpFactor = clamp(flLerpFactor, 0.0f, 1.0f);
|
|
flLerpFactor = SimpleSplineRemapVal(flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f);
|
|
flLerpFactor *= flLerpFactor;
|
|
VectorLerp(vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::MoveExecute_Alive(float flInterval)
|
|
{
|
|
// Amount of noise to add to flying
|
|
float noiseScale = 3.0f;
|
|
|
|
// -------------------------------------------
|
|
// Avoid obstacles, unless I'm dive bombing
|
|
// -------------------------------------------
|
|
if (m_nFlyMode != MSYNTH_FLY_DIVE)
|
|
{
|
|
SetCurrentVelocity(GetCurrentVelocity() + VelocityToAvoidObstacles(flInterval));
|
|
}
|
|
// If I am dive bombing add more noise to my flying
|
|
else
|
|
{
|
|
AttackDivebombCollide(flInterval);
|
|
noiseScale *= 4;
|
|
}
|
|
|
|
IPhysicsObject *pPhysics = VPhysicsGetObject();
|
|
|
|
if (pPhysics && pPhysics->IsAsleep())
|
|
{
|
|
pPhysics->Wake();
|
|
}
|
|
|
|
// Add time-coherent noise to the current velocity so that it never looks bolted in place.
|
|
AddNoiseToVelocity(noiseScale);
|
|
|
|
AdjustScannerVelocity();
|
|
|
|
float maxSpeed = GetEnemy() ? (GetMaxSpeed() * 2.0f) : GetMaxSpeed();
|
|
if (m_nFlyMode == MSYNTH_FLY_DIVE)
|
|
{
|
|
maxSpeed = -1;
|
|
}
|
|
|
|
// Limit fall speed
|
|
LimitSpeed(maxSpeed);
|
|
|
|
// Blend out desired velocity when launched by the physcannon
|
|
BlendPhyscannonLaunchSpeed();
|
|
|
|
// Update what we're looking at
|
|
UpdateHead(flInterval);
|
|
|
|
// Control the tail based on our vertical travel
|
|
float tailPerc = clamp(GetCurrentVelocity().z, -150, 250);
|
|
tailPerc = SimpleSplineRemapVal(tailPerc, -150, 250, -25, 80);
|
|
|
|
SetPoseParameter(m_nPoseTail, tailPerc);
|
|
|
|
// Spin the dynamo based upon our speed
|
|
float flCurrentDynamo = GetPoseParameter(m_nPoseDynamo);
|
|
float speed = GetCurrentVelocity().Length();
|
|
float flDynamoSpeed = (maxSpeed > 0 ? speed / maxSpeed : 1.0) * 60;
|
|
flCurrentDynamo -= flDynamoSpeed;
|
|
if (flCurrentDynamo < -180.0)
|
|
{
|
|
flCurrentDynamo += 360.0;
|
|
}
|
|
SetPoseParameter(m_nPoseDynamo, flCurrentDynamo);
|
|
|
|
PlayFlySound();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Handles movement towards the last move target.
|
|
// Input : flInterval -
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_BaseMSynth::OverridePathMove(CBaseEntity *pMoveTarget, float flInterval)
|
|
{
|
|
// Save our last patrolling direction
|
|
Vector lastPatrolDir = GetNavigator()->GetCurWaypointPos() - GetAbsOrigin();
|
|
|
|
// Continue on our path
|
|
if (ProgressFlyPath(flInterval, pMoveTarget, (MASK_NPCSOLID | CONTENTS_WATER), false, 64) == AINPP_COMPLETE)
|
|
{
|
|
if (IsCurSchedule(SCHED_SCANNER_PATROL))
|
|
{
|
|
m_vLastPatrolDir = lastPatrolDir;
|
|
VectorNormalize(m_vLastPatrolDir);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flInterval -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_BaseMSynth::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 (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)
|
|
{
|
|
trace_t tr;
|
|
AI_TraceHull(GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
float fTargetDist = (1.0f - tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length();
|
|
|
|
if ((tr.m_pEnt == pMoveTarget) || (fTargetDist < 50))
|
|
{
|
|
if (g_debug_basemortarsynth.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_basemortarsynth.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;
|
|
}
|
|
}
|
|
// ----------------------------------------------
|
|
// If attacking
|
|
// ----------------------------------------------
|
|
else if (m_nFlyMode == MSYNTH_FLY_ATTACK)
|
|
{
|
|
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:
|
|
// Input : &goalPos -
|
|
// &startPos -
|
|
// idealRange -
|
|
// idealHeight -
|
|
// Output : Vector
|
|
//-----------------------------------------------------------------------------
|
|
Vector CNPC_BaseMSynth::IdealGoalForMovement(const Vector &goalPos, const Vector &startPos, float idealRange, float idealHeightDiff)
|
|
{
|
|
Vector vMoveDir;
|
|
|
|
if (GetGoalDirection(&vMoveDir) == false)
|
|
{
|
|
vMoveDir = (goalPos - startPos);
|
|
vMoveDir.z = 0;
|
|
VectorNormalize(vMoveDir);
|
|
}
|
|
|
|
// Move up from the position by the desired amount
|
|
Vector vIdealPos = goalPos + Vector(0, 0, idealHeightDiff) + (vMoveDir * -idealRange);
|
|
|
|
// Trace down and make sure we can fit here
|
|
trace_t tr;
|
|
AI_TraceHull(vIdealPos, vIdealPos - Vector(0, 0, MinGroundDist()), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
|
|
|
|
// Move up otherwise
|
|
if (tr.fraction < 1.0f)
|
|
{
|
|
vIdealPos.z += (MinGroundDist() * (1.0f - tr.fraction));
|
|
}
|
|
|
|
//FIXME: We also need to make sure that we fit here at all, and if not, chose a new spot
|
|
|
|
// Debug tools
|
|
if (g_debug_basemortarsynth.GetBool())
|
|
{
|
|
NDebugOverlay::Cross3D(goalPos, -Vector(8, 8, 8), Vector(8, 8, 8), 255, 255, 0, true, 0.1f);
|
|
NDebugOverlay::Cross3D(startPos, -Vector(8, 8, 8), Vector(8, 8, 8), 255, 0, 255, true, 0.1f);
|
|
NDebugOverlay::Cross3D(vIdealPos, -Vector(8, 8, 8), Vector(8, 8, 8), 255, 255, 255, true, 0.1f);
|
|
NDebugOverlay::Line(startPos, goalPos, 0, 255, 0, true, 0.1f);
|
|
|
|
NDebugOverlay::Cross3D(goalPos + (vMoveDir * -idealRange), -Vector(8, 8, 8), Vector(8, 8, 8), 255, 255, 255, true, 0.1f);
|
|
NDebugOverlay::Line(goalPos, goalPos + (vMoveDir * -idealRange), 255, 255, 0, true, 0.1f);
|
|
NDebugOverlay::Line(goalPos + (vMoveDir * -idealRange), vIdealPos, 255, 255, 0, true, 0.1f);
|
|
}
|
|
|
|
return vIdealPos;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flInterval -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::MoveToAttack(float flInterval)
|
|
{
|
|
if (GetEnemy() == NULL)
|
|
return;
|
|
|
|
if (flInterval <= 0)
|
|
return;
|
|
|
|
Vector vTargetPos = GetEnemyLKP();
|
|
|
|
//float flDesiredDist = m_flAttackNearDist + ( ( m_flAttackFarDist - m_flAttackNearDist ) / 2 );
|
|
|
|
Vector idealPos = IdealGoalForMovement(vTargetPos, GetAbsOrigin(), GetGoalDistance(), m_flAttackNearDist);
|
|
|
|
MoveToTarget(flInterval, idealPos);
|
|
|
|
//FIXME: Re-implement?
|
|
|
|
/*
|
|
// ---------------------------------------------------------
|
|
// Add evasion if I have taken damage recently
|
|
// ---------------------------------------------------------
|
|
if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime)
|
|
{
|
|
vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer());
|
|
}
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Accelerates toward a given position.
|
|
// Input : flInterval - Time interval over which to move.
|
|
// vecMoveTarget - Position to move toward.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::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
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Danger sounds.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::DiveBombSoundThink()
|
|
{
|
|
Vector vecPosition, vecVelocity;
|
|
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
|
|
|
|
if (pPhysicsObject == NULL)
|
|
return;
|
|
|
|
pPhysicsObject->GetPosition(&vecPosition, NULL);
|
|
pPhysicsObject->GetVelocity(&vecVelocity, NULL);
|
|
|
|
CBasePlayer *pPlayer = AI_GetSinglePlayer();
|
|
if (pPlayer)
|
|
{
|
|
Vector vecDelta;
|
|
VectorSubtract(pPlayer->GetAbsOrigin(), vecPosition, vecDelta);
|
|
VectorNormalize(vecDelta);
|
|
if (DotProduct(vecDelta, vecVelocity) > 0.5f)
|
|
{
|
|
Vector vecEndPoint;
|
|
VectorMA(vecPosition, 2.0f * TICK_INTERVAL, vecVelocity, vecEndPoint);
|
|
float flDist = CalcDistanceToLineSegment(pPlayer->GetAbsOrigin(), vecPosition, vecEndPoint);
|
|
if (flDist < 200.0f)
|
|
{
|
|
ScannerEmitSound("DiveBombFlyby");
|
|
SetContextThink(&CNPC_BaseMSynth::DiveBombSoundThink, gpGlobals->curtime + 0.5f, s_pDiveBombSoundThinkContextMsynth);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
SetContextThink(&CNPC_BaseMSynth::DiveBombSoundThink, gpGlobals->curtime + 2.0f * TICK_INTERVAL, s_pDiveBombSoundThinkContextMsynth);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flInterval -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::MoveToDivebomb(float flInterval)
|
|
{
|
|
float myAccel = 1600;
|
|
float myDecay = 0.05f; // decay current velocity to 10% in 1 second
|
|
|
|
// Fly towards my enemy
|
|
Vector vEnemyPos = GetEnemyLKP();
|
|
Vector vFlyDirection = vEnemyPos - GetLocalOrigin();
|
|
VectorNormalize(vFlyDirection);
|
|
|
|
// Set net velocity
|
|
MoveInDirection(flInterval, m_vecDiveBombDirection, myAccel, myAccel, myDecay);
|
|
|
|
// Spin out of control.
|
|
Vector forward;
|
|
VPhysicsGetObject()->LocalToWorldVector(&forward, Vector(1.0, 0.0, 0.0));
|
|
AngularImpulse torque = forward * m_flDiveBombRollForce;
|
|
VPhysicsGetObject()->ApplyTorqueCenter(torque);
|
|
|
|
// BUGBUG: why Y axis and not Z?
|
|
Vector up;
|
|
VPhysicsGetObject()->LocalToWorldVector(&up, Vector(0.0, 1.0, 0.0));
|
|
VPhysicsGetObject()->ApplyForceCenter(up * 2000);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_BaseMSynth::IsEnemyPlayerInSuit()
|
|
{
|
|
if (GetEnemy() && GetEnemy()->IsPlayer())
|
|
{
|
|
CHL2_Player *pPlayer = NULL;
|
|
pPlayer = (CHL2_Player *)GetEnemy();
|
|
|
|
if (pPlayer && pPlayer->IsSuitEquipped())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_BaseMSynth::GetGoalDistance(void)
|
|
{
|
|
if (m_flGoalOverrideDistance != 0.0f)
|
|
return m_flGoalOverrideDistance;
|
|
|
|
switch (m_nFlyMode)
|
|
{
|
|
case MSYNTH_FLY_ATTACK:
|
|
{
|
|
float goalDist = (m_flAttackNearDist + ((m_flAttackFarDist - m_flAttackNearDist) / 2));
|
|
if (IsEnemyPlayerInSuit())
|
|
{
|
|
goalDist *= 0.5;
|
|
}
|
|
return goalDist;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return 128.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &vOut -
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_BaseMSynth::GetGoalDirection(Vector *vOut)
|
|
{
|
|
CBaseEntity *pTarget = GetTarget();
|
|
|
|
if (pTarget == NULL)
|
|
return false;
|
|
|
|
if (FClassnameIs(pTarget, "info_hint_air") || FClassnameIs(pTarget, "info_target"))
|
|
{
|
|
AngleVectors(pTarget->GetAbsAngles(), vOut);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
Vector CNPC_BaseMSynth::VelocityToEvade(CBaseCombatCharacter *pEnemy)
|
|
{
|
|
if (pEnemy)
|
|
{
|
|
// -----------------------------------------
|
|
// Keep out of enemy's shooting position
|
|
// -----------------------------------------
|
|
Vector vEnemyFacing = pEnemy->BodyDirection2D();
|
|
Vector vEnemyDir = pEnemy->EyePosition() - GetLocalOrigin();
|
|
VectorNormalize(vEnemyDir);
|
|
float fDotPr = DotProduct(vEnemyFacing, vEnemyDir);
|
|
|
|
if (fDotPr < -0.9)
|
|
{
|
|
Vector vDirUp(0, 0, 1);
|
|
Vector vDir;
|
|
CrossProduct(vEnemyFacing, vDirUp, vDir);
|
|
|
|
Vector crossProduct;
|
|
CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
|
|
if (crossProduct.y < 0)
|
|
{
|
|
vDir = vDir * -1;
|
|
}
|
|
return (vDir);
|
|
}
|
|
else if (fDotPr < -0.85)
|
|
{
|
|
Vector vDirUp(0, 0, 1);
|
|
Vector vDir;
|
|
CrossProduct(vEnemyFacing, vDirUp, vDir);
|
|
|
|
Vector crossProduct;
|
|
CrossProduct(vEnemyFacing, vEnemyDir, crossProduct);
|
|
if (random->RandomInt(0, 1))
|
|
{
|
|
vDir = vDir * -1;
|
|
}
|
|
return (vDir);
|
|
}
|
|
}
|
|
return vec3_origin;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
int CNPC_BaseMSynth::DrawDebugTextOverlays(void)
|
|
{
|
|
int nOffset = BaseClass::DrawDebugTextOverlays();
|
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT)
|
|
{
|
|
Vector vel;
|
|
GetVelocity(&vel, NULL);
|
|
|
|
char tempstr[512];
|
|
Q_snprintf(tempstr, sizeof(tempstr), "speed (max): %.2f (%.2f)", vel.Length(), m_flSpeed);
|
|
EntityText(nOffset, tempstr, 0);
|
|
nOffset++;
|
|
}
|
|
|
|
return nOffset;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : float
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_BaseMSynth::GetHeadTurnRate(void)
|
|
{
|
|
if (GetEnemy())
|
|
return 800.0f;
|
|
|
|
return 350.0f;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns true on success, false on failure.
|
|
//-----------------------------------------------------------------------------
|
|
inline CBaseEntity *CNPC_BaseMSynth::EntityToWatch(void)
|
|
{
|
|
return (GetTarget() != NULL) ? GetTarget() : GetEnemy(); // Okay if NULL
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : flInterval -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::UpdateHead(float flInterval)
|
|
{
|
|
float yaw = GetPoseParameter(m_nPoseFaceHoriz);
|
|
float pitch = GetPoseParameter(m_nPoseFaceVert);
|
|
|
|
CBaseEntity *pTarget = EntityToWatch();
|
|
|
|
Vector vLookPos;
|
|
|
|
if (!HasCondition(COND_IN_PVS) || GetAttachment("eyes", vLookPos) == false)
|
|
{
|
|
vLookPos = EyePosition();
|
|
}
|
|
|
|
if (pTarget != NULL)
|
|
{
|
|
Vector lookDir = pTarget->EyePosition() - vLookPos;
|
|
VectorNormalize(lookDir);
|
|
|
|
if (DotProduct(lookDir, BodyDirection3D()) < 0.0f)
|
|
{
|
|
SetPoseParameter(m_nPoseFaceHoriz, UTIL_Approach(0, yaw, 10));
|
|
SetPoseParameter(m_nPoseFaceVert, UTIL_Approach(0, pitch, 10));
|
|
|
|
return;
|
|
}
|
|
|
|
float facingYaw = VecToYaw(BodyDirection3D());
|
|
float yawDiff = VecToYaw(lookDir);
|
|
yawDiff = UTIL_AngleDiff(yawDiff, facingYaw + yaw);
|
|
|
|
float facingPitch = UTIL_VecToPitch(BodyDirection3D());
|
|
float pitchDiff = UTIL_VecToPitch(lookDir);
|
|
pitchDiff = UTIL_AngleDiff(pitchDiff, facingPitch + pitch);
|
|
|
|
SetPoseParameter(m_nPoseFaceHoriz, UTIL_Approach(yaw + yawDiff, yaw, 50));
|
|
SetPoseParameter(m_nPoseFaceVert, UTIL_Approach(pitch + pitchDiff, pitch, 50));
|
|
}
|
|
else
|
|
{
|
|
SetPoseParameter(m_nPoseFaceHoriz, UTIL_Approach(0, yaw, 10));
|
|
SetPoseParameter(m_nPoseFaceVert, UTIL_Approach(0, pitch, 10));
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &linear -
|
|
// &angular -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::ClampMotorForces(Vector &linear, AngularImpulse &angular)
|
|
{
|
|
// limit reaction forces
|
|
if (m_nFlyMode != MSYNTH_FLY_DIVE)
|
|
{
|
|
linear.x = clamp(linear.x, -500, 500);
|
|
linear.y = clamp(linear.y, -500, 500);
|
|
linear.z = clamp(linear.z, -500, 500);
|
|
}
|
|
|
|
// If we're dive bombing, we need to drop faster than normal
|
|
if (m_nFlyMode != MSYNTH_FLY_DIVE)
|
|
{
|
|
// Add in weightlessness
|
|
linear.z += 800;
|
|
}
|
|
|
|
angular.z = clamp(angular.z, -GetHeadTurnRate(), GetHeadTurnRate());
|
|
if (m_nFlyMode == MSYNTH_FLY_DIVE)
|
|
{
|
|
// Disable pitch and roll motors while crashing.
|
|
angular.x = 0;
|
|
angular.y = 0;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : &inputdata -
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::InputSetDistanceOverride(inputdata_t &inputdata)
|
|
{
|
|
m_flGoalOverrideDistance = inputdata.value.Float();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Emit sounds specific to the NPC's state.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::AlertSound(void)
|
|
{
|
|
ScannerEmitSound(MSYNTH_ALERT_SOUND);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Purpose:
|
|
//------------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::DeathSound(const CTakeDamageInfo &info)
|
|
{
|
|
ScannerEmitSound(MSYNTH_DIE_SOUND);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Overridden so that scanners play battle sounds while fighting.
|
|
// Output : Returns TRUE on success, FALSE on failure.
|
|
//-----------------------------------------------------------------------------
|
|
bool CNPC_BaseMSynth::ShouldPlayIdleSound(void)
|
|
{
|
|
if (HasSpawnFlags(SF_NPC_GAG))
|
|
return false;
|
|
|
|
if (random->RandomInt(0, 25) != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Plays sounds while idle or in combat.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::IdleSound(void)
|
|
{
|
|
if (m_NPCState == NPC_STATE_COMBAT)
|
|
{
|
|
// dvs: the combat sounds should be related to what is happening, rather than random
|
|
ScannerEmitSound(MSYNTH_IDLE2_SOUND);
|
|
}
|
|
else
|
|
{
|
|
ScannerEmitSound(MSYNTH_IDLE_SOUND);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Plays a sound when hurt.
|
|
//-----------------------------------------------------------------------------
|
|
void CNPC_BaseMSynth::PainSound(const CTakeDamageInfo &info)
|
|
{
|
|
ScannerEmitSound(MSYNTH_PAIN_SOUND);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
float CNPC_BaseMSynth::GetMaxSpeed()
|
|
{
|
|
return MSYNTH_MAX_SPEED;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// Schedules
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
AI_BEGIN_CUSTOM_NPC(npc_basescanner, CNPC_BaseMSynth)
|
|
|
|
DECLARE_TASK(TASK_SCANNER_SET_FLY_PATROL)
|
|
DECLARE_TASK(TASK_SCANNER_SET_FLY_CHASE)
|
|
DECLARE_TASK(TASK_SCANNER_SET_FLY_ATTACK)
|
|
DECLARE_TASK(TASK_SCANNER_SET_FLY_DIVE)
|
|
|
|
DECLARE_CONDITION(COND_SCANNER_FLY_CLEAR)
|
|
DECLARE_CONDITION(COND_SCANNER_FLY_BLOCKED)
|
|
DECLARE_CONDITION(COND_SCANNER_RELEASED_FROM_PHYSCANNON)
|
|
DECLARE_CONDITION(COND_SCANNER_GRABBED_BY_PHYSCANNON)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_PATROL
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_PATROL,
|
|
|
|
" Tasks"
|
|
" 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_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_ATTACK
|
|
//
|
|
// This task does nothing. Translate it in your derived
|
|
// class to perform your attack.
|
|
//
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_ATTACK,
|
|
|
|
" Tasks"
|
|
" TASK_SCANNER_SET_FLY_ATTACK 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT 0.1"
|
|
""
|
|
" Interrupts"
|
|
" COND_TOO_FAR_TO_ATTACK"
|
|
" COND_SCANNER_FLY_BLOCKED"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_ATTACK_HOVER
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_ATTACK_HOVER,
|
|
|
|
" Tasks"
|
|
" TASK_SCANNER_SET_FLY_ATTACK 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT 0.1"
|
|
""
|
|
" Interrupts"
|
|
" COND_TOO_FAR_TO_ATTACK"
|
|
" COND_SCANNER_FLY_BLOCKED"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_ATTACK_DIVEBOMB
|
|
//
|
|
// Only done when scanner is dead
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_ATTACK_DIVEBOMB,
|
|
|
|
" Tasks"
|
|
" TASK_SCANNER_SET_FLY_DIVE 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT 10"
|
|
""
|
|
" Interrupts"
|
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_CHASE_ENEMY
|
|
//
|
|
// Different interrupts than normal chase enemy.
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_CHASE_ENEMY,
|
|
|
|
" Tasks"
|
|
" TASK_SCANNER_SET_FLY_CHASE 0"
|
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL"
|
|
" TASK_SET_TOLERANCE_DISTANCE 120"
|
|
" TASK_GET_PATH_TO_ENEMY 0"
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
""
|
|
""
|
|
" Interrupts"
|
|
" COND_SCANNER_FLY_CLEAR"
|
|
" COND_NEW_ENEMY"
|
|
" COND_ENEMY_DEAD"
|
|
" COND_LOST_ENEMY"
|
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_CHASE_TARGET
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_CHASE_TARGET,
|
|
|
|
" Tasks"
|
|
" TASK_SCANNER_SET_FLY_CHASE 0"
|
|
" TASK_SET_TOLERANCE_DISTANCE 64"
|
|
" TASK_GET_PATH_TO_TARGET 0" //FIXME: This is wrong!
|
|
" TASK_RUN_PATH 0"
|
|
" TASK_WAIT_FOR_MOVEMENT 0"
|
|
""
|
|
" Interrupts"
|
|
" COND_SCANNER_FLY_CLEAR"
|
|
" COND_NEW_ENEMY"
|
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_FOLLOW_HOVER
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_FOLLOW_HOVER,
|
|
|
|
" Tasks"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT 0.1"
|
|
""
|
|
" Interrupts"
|
|
" COND_SCANNER_FLY_BLOCKED"
|
|
" COND_SCANNER_GRABBED_BY_PHYSCANNON"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_SCANNER_HELD_BY_PHYSCANNON
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_SCANNER_HELD_BY_PHYSCANNON,
|
|
|
|
" Tasks"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT 5.0"
|
|
""
|
|
" Interrupts"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_SCANNER_RELEASED_FROM_PHYSCANNON"
|
|
)
|
|
|
|
AI_END_CUSTOM_NPC()
|